From 58c22b7465fb5e6280213dcb3b959a1f99adc099 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Wed, 7 May 2025 16:53:57 -0500 Subject: [PATCH 01/55] feat: remove old/deprecated files --- .browserslistrc | 7 - .editorconfig | 5 - .eslintrc.yml | 35 --- .github/workflows/addtoprojects.yml | 21 -- .github/workflows/contributors.yml | 23 -- .gitignore | 27 -- .npmrc | 1 - .nvmrc | 1 - .prettierignore | 5 - .storybook/_attach.js | 27 -- .storybook/emulsifyTheme.js | 12 - .storybook/main.js | 8 - .storybook/manager.js | 7 - .storybook/preview-head.html | 5 - .storybook/preview.js | 18 -- .storybook/setupTwig.js | 30 -- .storybook/webpack.config.js | 76 ------ .stylelintrc | 21 -- .travis.yml | 22 -- 404.php | 13 - LICENSE | 7 - README.md | 53 ---- a11y.config.js | 62 ----- archive.php | 40 --- author.php | 21 -- babel.config.js | 19 -- bin/install-wp-tests.sh | 152 ----------- .../00-base/00-defaults/_01-variables.scss | 39 --- .../00-base/00-defaults/_02-breakpoints.scss | 68 ----- .../00-base/00-defaults/_03-mixins.scss | 68 ----- .../00-base/01-colors/_00-color-vars.scss | 23 -- .../00-base/01-colors/_01-colors-used.scss | 98 ------- .../_02-colors-component-library.scss | 56 ---- .../00-base/01-colors/colors.stories.js | 14 - components/00-base/01-colors/colors.twig | 27 -- components/00-base/01-colors/colors.yml | 30 -- components/00-base/02-motion/_motion.scss | 118 -------- .../00-base/02-motion/motion.stories.js | 18 -- components/00-base/02-motion/motion.twig | 11 - components/00-base/02-motion/motion.yml | 5 - components/00-base/03-site/_base.scss | 31 --- components/01-atoms/buttons/_buttons.scss | 100 ------- .../01-atoms/buttons/buttons.stories.js | 29 -- .../buttons/react/Button.component.js | 32 --- .../01-atoms/buttons/twig/button-alt.yml | 4 - .../01-atoms/buttons/twig/button-alt2.yml | 4 - components/01-atoms/buttons/twig/button.twig | 22 -- components/01-atoms/buttons/twig/button.yml | 2 - .../forms/checkbox/_checkbox-item.twig | 5 - .../01-atoms/forms/checkbox/_checkbox.scss | 4 - .../01-atoms/forms/checkbox/checkbox.twig | 15 - .../01-atoms/forms/checkbox/checkbox.yml | 6 - components/01-atoms/forms/forms.stories.js | 28 -- .../01-atoms/forms/radio/_radio-item.twig | 5 - components/01-atoms/forms/radio/_radio.scss | 4 - components/01-atoms/forms/radio/radio.twig | 15 - components/01-atoms/forms/radio/radio.yml | 6 - .../01-atoms/forms/select/_select-item.twig | 1 - components/01-atoms/forms/select/_select.scss | 72 ----- components/01-atoms/forms/select/select.twig | 18 -- components/01-atoms/forms/select/select.yml | 6 - .../forms/textfields/_textfields.scss | 47 ---- .../01-atoms/forms/textfields/textfields.twig | 37 --- components/01-atoms/images/icons/_icon.twig | 43 --- components/01-atoms/images/icons/_icons.scss | 26 -- components/01-atoms/images/icons/icons.md | 45 --- components/01-atoms/images/icons/icons.twig | 15 - .../01-atoms/images/icons/svgxuse.min.js | 12 - components/01-atoms/images/image/_image.scss | 18 -- components/01-atoms/images/image/_image.twig | 18 -- .../01-atoms/images/image/_picture.twig | 21 -- components/01-atoms/images/image/figure.twig | 24 -- components/01-atoms/images/image/figure.yml | 5 - components/01-atoms/images/image/image.yml | 5 - .../images/image/responsive-image.twig | 32 --- components/01-atoms/images/images.stories.js | 34 --- components/01-atoms/links/link/_link.scss | 11 - components/01-atoms/links/link/link.twig | 28 -- components/01-atoms/links/link/link.yml | 4 - .../01-atoms/links/link/links.stories.js | 14 - .../01-atoms/lists/_list-item-definition.twig | 5 - components/01-atoms/lists/_list-item.twig | 22 -- components/01-atoms/lists/_lists.scss | 32 --- components/01-atoms/lists/dl.twig | 28 -- components/01-atoms/lists/dl.yml | 9 - components/01-atoms/lists/lists.stories.js | 24 -- components/01-atoms/lists/ol.twig | 26 -- components/01-atoms/lists/ol.yml | 6 - components/01-atoms/lists/ul.twig | 26 -- components/01-atoms/lists/ul.yml | 6 - components/01-atoms/tables/_tables.scss | 94 ------- components/01-atoms/tables/tables.stories.js | 12 - components/01-atoms/tables/tables.twig | 40 --- .../01-atoms/text/headings/_heading.twig | 34 --- .../01-atoms/text/headings/_headings.scss | 195 ------------- .../01-atoms/text/headings/headings.twig | 11 - components/01-atoms/text/text.stories.js | 26 -- .../01-atoms/text/text/01-paragraph.twig | 20 -- .../01-atoms/text/text/02-blockquote.twig | 23 -- .../text/text/03-inline-elements.twig | 45 --- components/01-atoms/text/text/05-pre.twig | 15 - components/01-atoms/text/text/06-hr.twig | 1 - components/01-atoms/text/text/_text.scss | 39 --- components/01-atoms/text/text/blockquote.yml | 1 - components/01-atoms/video/_video.scss | 20 -- components/01-atoms/video/video-full.yml | 3 - components/01-atoms/video/video.twig | 23 -- components/01-atoms/video/video.yml | 1 - components/01-atoms/video/videos.stories.js | 18 -- components/02-molecules/card/_card.scss | 35 --- components/02-molecules/card/card-bg.yml | 2 - components/02-molecules/card/card.twig | 115 -------- components/02-molecules/card/card.yml | 8 - components/02-molecules/card/cards.stories.js | 20 -- components/02-molecules/cta/_cta.scss | 6 - components/02-molecules/cta/cta.twig | 7 - components/02-molecules/cta/cta.yml | 2 - components/02-molecules/cta/ctas.stories.js | 14 - components/02-molecules/menus/_menu-item.twig | 37 --- components/02-molecules/menus/_menu-list.twig | 15 - components/02-molecules/menus/_menu.twig | 46 ---- .../menus/breadcrumbs/_breadcrumbs.scss | 41 --- .../menus/breadcrumbs/breadcrumbs.twig | 27 -- .../menus/breadcrumbs/breadcrumbs.yml | 6 - .../menus/inline/_inline-menu.scss | 26 -- .../menus/inline/inline-menu.twig | 4 - .../02-molecules/menus/inline/inline-menu.yml | 7 - .../menus/main-menu/_00-main-menu.scss | 93 ------- .../menus/main-menu/_01-main-menu-item.scss | 85 ------ .../menus/main-menu/_02-main-menu-link.scss | 74 ----- .../menus/main-menu/_03-main-menu-toggle.scss | 61 ----- .../02-molecules/menus/main-menu/main-menu.js | 27 -- .../menus/main-menu/main-menu.twig | 25 -- .../menus/main-menu/main-menu.yml | 38 --- .../02-molecules/menus/menus.stories.js | 33 --- .../menus/social/_social-menu-item.twig | 8 - .../menus/social/_social-menu-link.twig | 13 - .../menus/social/_social-menu.scss | 26 -- .../menus/social/social-menu.twig | 13 - .../02-molecules/menus/social/social-menu.yml | 10 - components/02-molecules/pager/_pager.scss | 75 ----- components/02-molecules/pager/pager-both.yml | 16 -- components/02-molecules/pager/pager-prev.yml | 13 - .../02-molecules/pager/pager.stories.js | 31 --- components/02-molecules/pager/pager.twig | 71 ----- components/02-molecules/pager/pager.yml | 13 - components/02-molecules/status/_status.scss | 27 -- .../02-molecules/status/status.stories.js | 14 - components/02-molecules/status/status.twig | 42 --- components/02-molecules/status/status.yml | 7 - components/02-molecules/tabs/_tab.scss | 44 --- components/02-molecules/tabs/_tab.twig | 32 --- components/02-molecules/tabs/_tabs.scss | 41 --- components/02-molecules/tabs/tabs.js | 56 ---- components/02-molecules/tabs/tabs.stories.js | 18 -- components/02-molecules/tabs/tabs.twig | 45 --- components/02-molecules/tabs/tabs.yml | 7 - .../03-organisms/grid/_grid-examples.twig | 23 -- components/03-organisms/grid/_grid-item.scss | 23 -- components/03-organisms/grid/_grid.scss | 12 - components/03-organisms/grid/grid-cards.yml | 24 -- components/03-organisms/grid/grid-ctas.yml | 9 - components/03-organisms/grid/grid.twig | 27 -- components/03-organisms/grid/grid.yml | 6 - components/03-organisms/grid/grids.stories.js | 26 -- .../site/site-footer/_site-footer.scss | 33 --- .../site/site-footer/site-footer.twig | 17 -- .../site-header/_site-header-branding.twig | 12 - .../site/site-header/_site-header.scss | 22 -- .../site/site-header/site-header.twig | 26 -- components/03-organisms/site/site.stories.js | 38 --- components/04-templates/_default.scss | 73 ----- components/04-templates/_default.twig | 44 --- components/04-templates/_html-header.twig | 15 - components/04-templates/full-width.twig | 6 - components/04-templates/layouts.stories.js | 28 -- .../placeholder/_place-holder.scss | 17 -- .../placeholder/place-holder.stories.js | 12 - .../placeholder/place-holder.twig | 7 - components/04-templates/with-sidebar.twig | 13 - .../05-pages/content-types/article.twig | 55 ---- .../content-types/content-types.stories.js | 34 --- components/05-pages/landing-pages/home.twig | 50 ---- .../landing-pages/landing-pages.stories.js | 35 --- components/style.scss | 10 - composer.json | 35 --- footer.php | 21 -- functions.php | 4 - header.php | 15 - images/icons/facebook.svg | 1 - images/icons/instagram.svg | 1 - images/icons/menu.svg | 1 - images/icons/twitter.svg | 1 - images/logo.png | Bin 335111 -> 0 bytes inc/setup.php | 257 ------------------ index.php | 23 -- package.json | 80 ------ page.php | 28 -- phpunit.xml | 20 -- postcss.config.js | 7 - prettier.config.js | 4 - screenshot.png | Bin 386624 -> 0 bytes scripts/a11y.js | 89 ------ scripts/a11y.test.js | 157 ----------- search.php | 18 -- sidebar.php | 9 - single.php | 20 -- static/no-timber.html | 10 - static/site.js | 5 - style.css | 15 - templates/404.twig | 5 - templates/archive.twig | 9 - templates/author.twig | 7 - templates/comment-form.twig | 28 -- templates/comment.twig | 21 -- templates/index.twig | 9 - templates/menu.twig | 10 - templates/page-plugin.twig | 7 - templates/page.twig | 14 - templates/partial/pagination.twig | 1 - templates/search.twig | 13 - templates/sidebar.twig | 1 - templates/single-password.twig | 9 - templates/single.twig | 45 --- templates/tease-post.twig | 13 - templates/tease.twig | 13 - tests/bootstrap.php | 22 -- tests/test-timber-starter-theme.php | 55 ---- webpack/.stylelintrc | 24 -- webpack/app.js | 1 - webpack/css.js | 1 - webpack/loaders.js | 61 ----- webpack/plugins.js | 41 --- webpack/svgSprite.js | 5 - webpack/webpack.common.js | 54 ---- webpack/webpack.dev.js | 7 - webpack/webpack.prod.js | 6 - 237 files changed, 6422 deletions(-) delete mode 100644 .browserslistrc delete mode 100644 .editorconfig delete mode 100644 .eslintrc.yml delete mode 100644 .github/workflows/addtoprojects.yml delete mode 100644 .github/workflows/contributors.yml delete mode 100644 .gitignore delete mode 100644 .npmrc delete mode 100644 .nvmrc delete mode 100644 .prettierignore delete mode 100644 .storybook/_attach.js delete mode 100644 .storybook/emulsifyTheme.js delete mode 100644 .storybook/main.js delete mode 100644 .storybook/manager.js delete mode 100644 .storybook/preview-head.html delete mode 100644 .storybook/preview.js delete mode 100644 .storybook/setupTwig.js delete mode 100644 .storybook/webpack.config.js delete mode 100644 .stylelintrc delete mode 100644 .travis.yml delete mode 100755 404.php delete mode 100755 LICENSE delete mode 100644 README.md delete mode 100644 a11y.config.js delete mode 100755 archive.php delete mode 100755 author.php delete mode 100644 babel.config.js delete mode 100644 bin/install-wp-tests.sh delete mode 100644 components/00-base/00-defaults/_01-variables.scss delete mode 100644 components/00-base/00-defaults/_02-breakpoints.scss delete mode 100644 components/00-base/00-defaults/_03-mixins.scss delete mode 100644 components/00-base/01-colors/_00-color-vars.scss delete mode 100644 components/00-base/01-colors/_01-colors-used.scss delete mode 100644 components/00-base/01-colors/_02-colors-component-library.scss delete mode 100644 components/00-base/01-colors/colors.stories.js delete mode 100644 components/00-base/01-colors/colors.twig delete mode 100644 components/00-base/01-colors/colors.yml delete mode 100644 components/00-base/02-motion/_motion.scss delete mode 100644 components/00-base/02-motion/motion.stories.js delete mode 100644 components/00-base/02-motion/motion.twig delete mode 100644 components/00-base/02-motion/motion.yml delete mode 100644 components/00-base/03-site/_base.scss delete mode 100644 components/01-atoms/buttons/_buttons.scss delete mode 100644 components/01-atoms/buttons/buttons.stories.js delete mode 100644 components/01-atoms/buttons/react/Button.component.js delete mode 100644 components/01-atoms/buttons/twig/button-alt.yml delete mode 100644 components/01-atoms/buttons/twig/button-alt2.yml delete mode 100644 components/01-atoms/buttons/twig/button.twig delete mode 100644 components/01-atoms/buttons/twig/button.yml delete mode 100644 components/01-atoms/forms/checkbox/_checkbox-item.twig delete mode 100644 components/01-atoms/forms/checkbox/_checkbox.scss delete mode 100644 components/01-atoms/forms/checkbox/checkbox.twig delete mode 100644 components/01-atoms/forms/checkbox/checkbox.yml delete mode 100644 components/01-atoms/forms/forms.stories.js delete mode 100644 components/01-atoms/forms/radio/_radio-item.twig delete mode 100644 components/01-atoms/forms/radio/_radio.scss delete mode 100644 components/01-atoms/forms/radio/radio.twig delete mode 100644 components/01-atoms/forms/radio/radio.yml delete mode 100644 components/01-atoms/forms/select/_select-item.twig delete mode 100644 components/01-atoms/forms/select/_select.scss delete mode 100644 components/01-atoms/forms/select/select.twig delete mode 100644 components/01-atoms/forms/select/select.yml delete mode 100644 components/01-atoms/forms/textfields/_textfields.scss delete mode 100644 components/01-atoms/forms/textfields/textfields.twig delete mode 100644 components/01-atoms/images/icons/_icon.twig delete mode 100644 components/01-atoms/images/icons/_icons.scss delete mode 100644 components/01-atoms/images/icons/icons.md delete mode 100644 components/01-atoms/images/icons/icons.twig delete mode 100644 components/01-atoms/images/icons/svgxuse.min.js delete mode 100644 components/01-atoms/images/image/_image.scss delete mode 100644 components/01-atoms/images/image/_image.twig delete mode 100644 components/01-atoms/images/image/_picture.twig delete mode 100644 components/01-atoms/images/image/figure.twig delete mode 100644 components/01-atoms/images/image/figure.yml delete mode 100644 components/01-atoms/images/image/image.yml delete mode 100644 components/01-atoms/images/image/responsive-image.twig delete mode 100644 components/01-atoms/images/images.stories.js delete mode 100644 components/01-atoms/links/link/_link.scss delete mode 100644 components/01-atoms/links/link/link.twig delete mode 100644 components/01-atoms/links/link/link.yml delete mode 100644 components/01-atoms/links/link/links.stories.js delete mode 100644 components/01-atoms/lists/_list-item-definition.twig delete mode 100644 components/01-atoms/lists/_list-item.twig delete mode 100644 components/01-atoms/lists/_lists.scss delete mode 100644 components/01-atoms/lists/dl.twig delete mode 100644 components/01-atoms/lists/dl.yml delete mode 100644 components/01-atoms/lists/lists.stories.js delete mode 100644 components/01-atoms/lists/ol.twig delete mode 100644 components/01-atoms/lists/ol.yml delete mode 100644 components/01-atoms/lists/ul.twig delete mode 100644 components/01-atoms/lists/ul.yml delete mode 100644 components/01-atoms/tables/_tables.scss delete mode 100644 components/01-atoms/tables/tables.stories.js delete mode 100644 components/01-atoms/tables/tables.twig delete mode 100644 components/01-atoms/text/headings/_heading.twig delete mode 100644 components/01-atoms/text/headings/_headings.scss delete mode 100644 components/01-atoms/text/headings/headings.twig delete mode 100644 components/01-atoms/text/text.stories.js delete mode 100644 components/01-atoms/text/text/01-paragraph.twig delete mode 100644 components/01-atoms/text/text/02-blockquote.twig delete mode 100644 components/01-atoms/text/text/03-inline-elements.twig delete mode 100644 components/01-atoms/text/text/05-pre.twig delete mode 100644 components/01-atoms/text/text/06-hr.twig delete mode 100644 components/01-atoms/text/text/_text.scss delete mode 100644 components/01-atoms/text/text/blockquote.yml delete mode 100644 components/01-atoms/video/_video.scss delete mode 100644 components/01-atoms/video/video-full.yml delete mode 100644 components/01-atoms/video/video.twig delete mode 100644 components/01-atoms/video/video.yml delete mode 100644 components/01-atoms/video/videos.stories.js delete mode 100644 components/02-molecules/card/_card.scss delete mode 100644 components/02-molecules/card/card-bg.yml delete mode 100644 components/02-molecules/card/card.twig delete mode 100644 components/02-molecules/card/card.yml delete mode 100644 components/02-molecules/card/cards.stories.js delete mode 100644 components/02-molecules/cta/_cta.scss delete mode 100644 components/02-molecules/cta/cta.twig delete mode 100644 components/02-molecules/cta/cta.yml delete mode 100644 components/02-molecules/cta/ctas.stories.js delete mode 100644 components/02-molecules/menus/_menu-item.twig delete mode 100644 components/02-molecules/menus/_menu-list.twig delete mode 100644 components/02-molecules/menus/_menu.twig delete mode 100644 components/02-molecules/menus/breadcrumbs/_breadcrumbs.scss delete mode 100644 components/02-molecules/menus/breadcrumbs/breadcrumbs.twig delete mode 100644 components/02-molecules/menus/breadcrumbs/breadcrumbs.yml delete mode 100644 components/02-molecules/menus/inline/_inline-menu.scss delete mode 100644 components/02-molecules/menus/inline/inline-menu.twig delete mode 100644 components/02-molecules/menus/inline/inline-menu.yml delete mode 100644 components/02-molecules/menus/main-menu/_00-main-menu.scss delete mode 100644 components/02-molecules/menus/main-menu/_01-main-menu-item.scss delete mode 100644 components/02-molecules/menus/main-menu/_02-main-menu-link.scss delete mode 100644 components/02-molecules/menus/main-menu/_03-main-menu-toggle.scss delete mode 100644 components/02-molecules/menus/main-menu/main-menu.js delete mode 100644 components/02-molecules/menus/main-menu/main-menu.twig delete mode 100644 components/02-molecules/menus/main-menu/main-menu.yml delete mode 100644 components/02-molecules/menus/menus.stories.js delete mode 100644 components/02-molecules/menus/social/_social-menu-item.twig delete mode 100644 components/02-molecules/menus/social/_social-menu-link.twig delete mode 100644 components/02-molecules/menus/social/_social-menu.scss delete mode 100644 components/02-molecules/menus/social/social-menu.twig delete mode 100644 components/02-molecules/menus/social/social-menu.yml delete mode 100644 components/02-molecules/pager/_pager.scss delete mode 100644 components/02-molecules/pager/pager-both.yml delete mode 100644 components/02-molecules/pager/pager-prev.yml delete mode 100644 components/02-molecules/pager/pager.stories.js delete mode 100644 components/02-molecules/pager/pager.twig delete mode 100644 components/02-molecules/pager/pager.yml delete mode 100644 components/02-molecules/status/_status.scss delete mode 100644 components/02-molecules/status/status.stories.js delete mode 100644 components/02-molecules/status/status.twig delete mode 100644 components/02-molecules/status/status.yml delete mode 100644 components/02-molecules/tabs/_tab.scss delete mode 100644 components/02-molecules/tabs/_tab.twig delete mode 100644 components/02-molecules/tabs/_tabs.scss delete mode 100644 components/02-molecules/tabs/tabs.js delete mode 100644 components/02-molecules/tabs/tabs.stories.js delete mode 100644 components/02-molecules/tabs/tabs.twig delete mode 100644 components/02-molecules/tabs/tabs.yml delete mode 100644 components/03-organisms/grid/_grid-examples.twig delete mode 100644 components/03-organisms/grid/_grid-item.scss delete mode 100644 components/03-organisms/grid/_grid.scss delete mode 100644 components/03-organisms/grid/grid-cards.yml delete mode 100644 components/03-organisms/grid/grid-ctas.yml delete mode 100644 components/03-organisms/grid/grid.twig delete mode 100644 components/03-organisms/grid/grid.yml delete mode 100644 components/03-organisms/grid/grids.stories.js delete mode 100644 components/03-organisms/site/site-footer/_site-footer.scss delete mode 100644 components/03-organisms/site/site-footer/site-footer.twig delete mode 100644 components/03-organisms/site/site-header/_site-header-branding.twig delete mode 100644 components/03-organisms/site/site-header/_site-header.scss delete mode 100644 components/03-organisms/site/site-header/site-header.twig delete mode 100644 components/03-organisms/site/site.stories.js delete mode 100644 components/04-templates/_default.scss delete mode 100644 components/04-templates/_default.twig delete mode 100644 components/04-templates/_html-header.twig delete mode 100644 components/04-templates/full-width.twig delete mode 100644 components/04-templates/layouts.stories.js delete mode 100644 components/04-templates/placeholder/_place-holder.scss delete mode 100644 components/04-templates/placeholder/place-holder.stories.js delete mode 100644 components/04-templates/placeholder/place-holder.twig delete mode 100644 components/04-templates/with-sidebar.twig delete mode 100644 components/05-pages/content-types/article.twig delete mode 100644 components/05-pages/content-types/content-types.stories.js delete mode 100644 components/05-pages/landing-pages/home.twig delete mode 100644 components/05-pages/landing-pages/landing-pages.stories.js delete mode 100644 components/style.scss delete mode 100644 composer.json delete mode 100644 footer.php delete mode 100644 functions.php delete mode 100644 header.php delete mode 100644 images/icons/facebook.svg delete mode 100644 images/icons/instagram.svg delete mode 100755 images/icons/menu.svg delete mode 100755 images/icons/twitter.svg delete mode 100644 images/logo.png delete mode 100644 inc/setup.php delete mode 100755 index.php delete mode 100644 package.json delete mode 100755 page.php delete mode 100644 phpunit.xml delete mode 100644 postcss.config.js delete mode 100644 prettier.config.js delete mode 100644 screenshot.png delete mode 100755 scripts/a11y.js delete mode 100644 scripts/a11y.test.js delete mode 100755 search.php delete mode 100644 sidebar.php delete mode 100755 single.php delete mode 100644 static/no-timber.html delete mode 100755 static/site.js delete mode 100644 style.css delete mode 100644 templates/404.twig delete mode 100644 templates/archive.twig delete mode 100644 templates/author.twig delete mode 100644 templates/comment-form.twig delete mode 100644 templates/comment.twig delete mode 100644 templates/index.twig delete mode 100644 templates/menu.twig delete mode 100644 templates/page-plugin.twig delete mode 100644 templates/page.twig delete mode 100644 templates/partial/pagination.twig delete mode 100644 templates/search.twig delete mode 100644 templates/sidebar.twig delete mode 100644 templates/single-password.twig delete mode 100644 templates/single.twig delete mode 100644 templates/tease-post.twig delete mode 100644 templates/tease.twig delete mode 100644 tests/bootstrap.php delete mode 100644 tests/test-timber-starter-theme.php delete mode 100644 webpack/.stylelintrc delete mode 100644 webpack/app.js delete mode 100644 webpack/css.js delete mode 100644 webpack/loaders.js delete mode 100644 webpack/plugins.js delete mode 100644 webpack/svgSprite.js delete mode 100644 webpack/webpack.common.js delete mode 100644 webpack/webpack.dev.js delete mode 100644 webpack/webpack.prod.js diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 0a3af40..0000000 --- a/.browserslistrc +++ /dev/null @@ -1,7 +0,0 @@ -# Browsers that we support - -last 1 version -> 1% -maintained node versions -not dead -ie 11 diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index cf0dff6..0000000 --- a/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -# Unix-style newlines with a newline ending every file -[*] -indent_style = space -indent_size = 2 -insert_final_newline = true diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index d780b54..0000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,35 +0,0 @@ -extends: - - airbnb - - prettier - - plugin:prettier/recommended -plugins: - - react - - prettier -env: - es6: true - browser: true -globals: - expect: true - it: true - describe: true - Attach: true -parser: babel-eslint -rules: - strict: 0 - react/jsx-filename-extension: [1, { 'extensions': ['.js', '.jsx'] }] - import/no-extraneous-dependencies: 0 - react/prefer-stateless-function: 0 - prettier/prettier: error - jsx-a11y/label-has-for: - - 2 - - required: - some: - - nesting - - id -ignorePatterns: - - '**/*.min.js' -overrides: - - files: - - '*.stories.js' - rules: - react/no-danger: 0 diff --git a/.github/workflows/addtoprojects.yml b/.github/workflows/addtoprojects.yml deleted file mode 100644 index 7bebb51..0000000 --- a/.github/workflows/addtoprojects.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Add to projects - -on: - issues: - types: - - opened - pull_request: - types: - - opened - -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v0.3.0 - with: - # You can target a repository in a different organization - # to the issue - project-url: https://github.com/orgs/emulsify-ds/projects/6 - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml deleted file mode 100644 index c74d157..0000000 --- a/.github/workflows/contributors.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Add contributors -on: - schedule: - - cron: "20 20 * * *" - push: - branches: - - master - -jobs: - add-contributors: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: BobAnkh/add-contributors@master - with: - CONTRIBUTOR: "### Contributors" - COLUMN_PER_ROW: "6" - ACCESS_TOKEN: ${{secrets.ADD_TO_PROJECT_PAT}} - IMG_WIDTH: "100" - FONT_SIZE: "14" - PATH: "/README.md" - COMMIT_MESSAGE: "docs(README): update contributors" - AVATAR_SHAPE: "round" diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d7fd7a1..0000000 --- a/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -wp-content - -# Ignore compiled files. -dist - -# Yarn/NPM -node_modules/ -yarn.lock -yarn-error.log - -# Storybook -.out - -# Gatsby -.gatsby-context.js -public -.cache/ - -# System -.DS_Store - -# Git deploy -.publish - -# Composer -composer.lock -/vendor/ diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 5fca0d5..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -scripts-prepend-node-path=true diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 958b5a3..0000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v14 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index b97d4b2..0000000 --- a/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -dist -.out -.coverage -node_modules -*.min.js \ No newline at end of file diff --git a/.storybook/_attach.js b/.storybook/_attach.js deleted file mode 100644 index 9d54bcb..0000000 --- a/.storybook/_attach.js +++ /dev/null @@ -1,27 +0,0 @@ -// Simple Attach.behaviors usage for Storybook - -window.Attach = { behaviors: {} }; - -(function(Attach) { - Attach.throwError = function(error) { - setTimeout(function() { - throw error; - }, 0); - }; - - Attach.attachBehaviors = function(context, settings) { - context = context || document; - settings = settings; - const behaviors = Attach.behaviors; - - Object.keys(behaviors).forEach(function(i) { - if (typeof behaviors[i].attach === 'function') { - try { - behaviors[i].attach(context, settings); - } catch (e) { - Attach.throwError(e); - } - } - }); - }; -})(Attach); diff --git a/.storybook/emulsifyTheme.js b/.storybook/emulsifyTheme.js deleted file mode 100644 index f6d12a8..0000000 --- a/.storybook/emulsifyTheme.js +++ /dev/null @@ -1,12 +0,0 @@ -// Documentation on theming Storybook: https://storybook.js.org/docs/configurations/theming/ - -import { create } from '@storybook/theming'; - -export default create({ - base: 'light', - // Branding - brandTitle: 'Emulsify', - brandUrl: 'https://emulsify.info', - brandImage: - 'https://raw.githubusercontent.com/emulsify-ds/emulsify-design-system/master/images/logo.png', -}); diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index 45ac09f..0000000 --- a/.storybook/main.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - stories: ['../components/**/*.stories.js'], - addons: [ - '@storybook/addon-a11y', - '@storybook/addon-actions/register', - '@storybook/addon-links/register', - ], -}; diff --git a/.storybook/manager.js b/.storybook/manager.js deleted file mode 100644 index 7010e25..0000000 --- a/.storybook/manager.js +++ /dev/null @@ -1,7 +0,0 @@ -import { addons } from '@storybook/addons'; - -import emulsifyTheme from './emulsifyTheme'; - -addons.setConfig({ - theme: emulsifyTheme, -}); diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html deleted file mode 100644 index ec63390..0000000 --- a/.storybook/preview-head.html +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index 2e7e438..0000000 --- a/.storybook/preview.js +++ /dev/null @@ -1,18 +0,0 @@ -import { addDecorator } from '@storybook/react'; -import { useEffect } from '@storybook/client-api'; -import Twig from 'twig'; -import { setupTwig } from './setupTwig'; - -// GLOBAL CSS -import '../components/style.scss'; - -// Script to "attach" DOM scripts for Storybook. -import './_attach.js'; - -// addDecorator deprecated, but not sure how to use this otherwise. -addDecorator((storyFn) => { - useEffect(() => Attach.attachBehaviors(), []); - return storyFn(); -}); - -setupTwig(Twig); diff --git a/.storybook/setupTwig.js b/.storybook/setupTwig.js deleted file mode 100644 index 1056922..0000000 --- a/.storybook/setupTwig.js +++ /dev/null @@ -1,30 +0,0 @@ -const { resolve } = require('path'); -const twigBEM = require('bem-twig-extension'); -const twigAddAttributes = require('add-attributes-twig-extension'); - -module.exports.namespaces = { - atoms: resolve(__dirname, '../', 'components/01-atoms'), - molecules: resolve(__dirname, '../', 'components/02-molecules'), - organisms: resolve(__dirname, '../', 'components/03-organisms'), - templates: resolve(__dirname, '../', 'components/04-templates'), -}; - -/** - * Configures and extends a standard twig object. - * - * @param {Twig} twig - twig object that should be configured and extended. - * - * @returns {Twig} configured twig object. - */ -module.exports.setupTwig = function setupTwig(twig) { - twig.cache(); - twig.extendFunction("function", function() { - return ""; - }); - twig.extendFunction("_e", function() { - return ""; - }); - twigBEM(twig); - twigAddAttributes(twig); - return twig; -}; diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js deleted file mode 100644 index 7b540b3..0000000 --- a/.storybook/webpack.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const path = require('path'); -const globImporter = require('node-sass-glob-importer'); -const _StyleLintPlugin = require('stylelint-webpack-plugin'); -const { namespaces } = require('./setupTwig'); - -module.exports = async ({ config }) => { - - // Twig - config.module.rules.push({ - test: /\.twig$/, - use: [ - { - loader: 'twig-loader', - options: { - twigOptions: { - namespaces, - }, - }, - }, - ], - }); - - // SCSS - config.module.rules.push({ - test: /\.s[ac]ss$/i, - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - sourceMap: true, - }, - }, - { - loader: 'sass-loader', - options: { - sourceMap: true, - sassOptions: { - importer: globImporter(), - }, - }, - }, - ], - }); - - config.plugins.push( - new _StyleLintPlugin({ - configFile: path.resolve(__dirname, '../', 'webpack/.stylelintrc'), - context: path.resolve(__dirname, '../', 'components'), - files: '**/*.scss', - failOnError: false, - quiet: false, - }), - ); - - // YAML - config.module.rules.push({ - test: /\.ya?ml$/, - loader: 'js-yaml-loader', - }); - - // JS - config.module.rules.push({ - test: /\.js$/, - exclude: /node_modules/, - loader: 'eslint-loader', - options: { - cache: true, - global: [ - 'Attach' - ] - }, - }); - - return config; -}; diff --git a/.stylelintrc b/.stylelintrc deleted file mode 100644 index 948644b..0000000 --- a/.stylelintrc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": [ - "stylelint-config-standard", - "stylelint-config-prettier" - ], - "rules": { - "at-rule-no-unknown": [ true, { - "ignoreAtRules": [ - "include", - "mixin", - "extend", - "if", - "each", - "content", - "import" - ] - }], - "no-descending-specificity": null, - "function-calc-no-invalid": null - } -} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0e64c4a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -sudo: false - -dist: xenial - -services: mysql - -language: php - -php: - - 5.6.30 - - 7.3 - -env: - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 - -before_script: - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION - - composer install - -script: - - vendor/phpunit/phpunit/phpunit diff --git a/404.php b/404.php deleted file mode 100755 index e56dea2..0000000 --- a/404.php +++ /dev/null @@ -1,13 +0,0 @@ - [!WARNING] -> Emulsify for WordPress is no longer supported. If you'd like to be a contributor and bring it back to life, [reach out](https://www.emulsify.info/). - -# Emulsify Starter Theme - -## Installation - -### Composer -- `composer require emulsify-ds/emulsify-wordpress-theme` -- `cd` into theme; run `yarn setup` -- Login to WordPress and make Emulsify your active theme. - -### Manual -- Download this repo and put it in your WordPress `themes` folder. -- `cd` into theme; run `yarn setup` -- Login to WordPress and make Emulsify your active theme. - -### Contributors - - - - - - - - -
- - Randy -
- Randy Oest -
-
- - Joe -
- Joe Tower -
-
- - Brian -
- Brian Lewis -
-
- - mikeethedude/ -
- mikeethedude -
-
diff --git a/a11y.config.js b/a11y.config.js deleted file mode 100644 index 0cfbfd0..0000000 --- a/a11y.config.js +++ /dev/null @@ -1,62 +0,0 @@ -module.exports = { - storybookBuildDir: '.out', - pa11y: { - includeNotices: false, - includeWarnings: false, - runners: ['axe'], - }, - // A11y linting is done on a component-by-component - // basis, which results in the linter reporting some errors that - // should be ignored. These codes and descriptions allow for those - // errors to be targeted specifically. - ignore: { - codes: ['landmark-one-main', 'page-has-heading-one'], - descriptions: ['Ensures all page content is contained by landmarks'], - }, - // List of storybook component IDs defined and used in this project. - components: [ - 'base-colors--palettes', - 'base-motion--usage', - 'atoms-button--react', - 'atoms-button--twig', - 'atoms-button--twig-alt', - 'atoms-forms--checkboxes', - 'atoms-forms--radio-buttons', - 'atoms-forms--select-dropdowns', - 'atoms-forms--textfields-examples', - 'atoms-images--images', - 'atoms-images--figures', - 'atoms-images--icons', - 'atoms-links--links', - 'atoms-lists--definition-list', - 'atoms-lists--unordered-list', - 'atoms-lists--ordered-list', - 'atoms-tables--table', - 'atoms-text--headings-examples', - 'atoms-text--blockquote-example', - 'atoms-text--preformatted', - 'atoms-text--random', - 'atoms-videos--wide', - 'atoms-videos--full', - 'molecules-cards--card-example', - 'molecules-cards--card-with-background', - 'molecules-cta--cta-example', - 'molecules-menus--breadcrumbs', - 'molecules-menus--inline', - 'molecules-menus--main', - 'molecules-menus--social', - 'molecules-menus-pager--pager-example', - 'molecules-status--status-examples', - 'molecules-tabs--js-tabs', - 'organisms-grids--default-grid', - 'organisms-grids--card-grid', - 'organisms-grids--cta-grid', - 'organisms-site--footer', - 'organisms-site--header', - 'templates-layouts--full-width', - 'templates-layouts--with-sidebar', - 'templates-place-holder--place-holder', - 'pages-content-types--article', - 'pages-landing-pages--home', - ], -}; diff --git a/archive.php b/archive.php deleted file mode 100755 index 148512a..0000000 --- a/archive.php +++ /dev/null @@ -1,40 +0,0 @@ -query_vars['author'] ) ) { - $author = new Timber\User( $wp_query->query_vars['author'] ); - $context['author'] = $author; - $context['title'] = 'Author Archives: ' . $author->name(); -} -Timber::render( array( 'author.twig', 'archive.twig' ), $context ); diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index c04d73d..0000000 --- a/babel.config.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = (api) => { - api.cache(true); - - const presets = [ - '@babel/preset-react', - [ - '@babel/preset-env', - { - corejs: '3', - useBuiltIns: 'usage', - }, - ], - 'minify', - ]; - - const comments = false; - - return { presets, comments }; -}; diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh deleted file mode 100644 index eba7bb4..0000000 --- a/bin/install-wp-tests.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env bash - -if [ $# -lt 3 ]; then - echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" - exit 1 -fi - -DB_NAME=$1 -DB_USER=$2 -DB_PASS=$3 -DB_HOST=${4-localhost} -WP_VERSION=${5-latest} -SKIP_DB_CREATE=${6-false} - -TMPDIR=${TMPDIR-/tmp} -TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") -WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} -WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} - -download() { - if [ `which curl` ]; then - curl -s "$1" > "$2"; - elif [ `which wget` ]; then - wget -nv -O "$2" "$1" - fi -} - -if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then - WP_TESTS_TAG="branches/$WP_VERSION" -elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then - if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then - # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x - WP_TESTS_TAG="tags/${WP_VERSION%??}" - else - WP_TESTS_TAG="tags/$WP_VERSION" - fi -elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - WP_TESTS_TAG="trunk" -else - # http serves a single offer, whereas https serves multiple. we only want one - download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json - grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json - LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') - if [[ -z "$LATEST_VERSION" ]]; then - echo "Latest WordPress version could not be found" - exit 1 - fi - WP_TESTS_TAG="tags/$LATEST_VERSION" -fi - -set -ex - -install_wp() { - - if [ -d $WP_CORE_DIR ]; then - return; - fi - - mkdir -p $WP_CORE_DIR - - if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - mkdir -p $TMPDIR/wordpress-nightly - download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip - unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ - mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR - else - if [ $WP_VERSION == 'latest' ]; then - local ARCHIVE_NAME='latest' - elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then - # https serves multiple offers, whereas http serves single. - download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json - if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then - # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x - LATEST_VERSION=${WP_VERSION%??} - else - # otherwise, scan the releases and get the most up to date minor version of the major release - local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` - LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) - fi - if [[ -z "$LATEST_VERSION" ]]; then - local ARCHIVE_NAME="wordpress-$WP_VERSION" - else - local ARCHIVE_NAME="wordpress-$LATEST_VERSION" - fi - else - local ARCHIVE_NAME="wordpress-$WP_VERSION" - fi - download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz - tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR - fi - - download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php -} - -install_test_suite() { - # portable in-place argument for both GNU sed and Mac OSX sed - if [[ $(uname -s) == 'Darwin' ]]; then - local ioption='-i .bak' - else - local ioption='-i' - fi - - # set up testing suite if it doesn't yet exist - if [ ! -d $WP_TESTS_DIR ]; then - # set up testing suite - mkdir -p $WP_TESTS_DIR - svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes - svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data - fi - - if [ ! -f wp-tests-config.php ]; then - download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php - # remove all forward slashes in the end - WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") - sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php - fi - -} - -install_db() { - - if [ ${SKIP_DB_CREATE} = "true" ]; then - return 0 - fi - - # parse DB_HOST for port or socket references - local PARTS=(${DB_HOST//\:/ }) - local DB_HOSTNAME=${PARTS[0]}; - local DB_SOCK_OR_PORT=${PARTS[1]}; - local EXTRA="" - - if ! [ -z $DB_HOSTNAME ] ; then - if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then - EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" - elif ! [ -z $DB_SOCK_OR_PORT ] ; then - EXTRA=" --socket=$DB_SOCK_OR_PORT" - elif ! [ -z $DB_HOSTNAME ] ; then - EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" - fi - fi - - # create database - mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA -} - -install_wp -install_test_suite -install_db \ No newline at end of file diff --git a/components/00-base/00-defaults/_01-variables.scss b/components/00-base/00-defaults/_01-variables.scss deleted file mode 100644 index 3a9d52f..0000000 --- a/components/00-base/00-defaults/_01-variables.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* - * General Variables - * Note: colors should go into - * components/00-base/01-colors/_color-vars.css - * and breakpoint related vars into - * components/00-base/base/_breakpoints.css -*/ - -// Fonts -$font-body: georgia, times, 'Times New Roman', serif; -$font-heading: 'HelveticaNeue', 'Helvetica', 'Arial', sans-serif; - -// Spacing -$space: 1rem; - -// Times -$space-double: $space * 2; -$space-triple: $space * 3; -$space-quadruple: $space * 4; -$space-quintuple: $space * 5; -$space-sextuple: $space * 6; -$space-septuple: $space * 7; - -// Divided -$space-one-half: $space/2; -$space-one-third: $space/3; -$space-one-fourth: $space/4; -$space-one-fifth: $space/5; -$space-one-sixth: $space/6; -$space-one-seventh: $space/7; -$space-one-eighth: $space/8; - -/* - * Use this on the outer wrapper of page-level elements. - * It ensures consistent spacing between elements on the page. - */ -@mixin space-stack-page { - margin-bottom: $space-double; -} diff --git a/components/00-base/00-defaults/_02-breakpoints.scss b/components/00-base/00-defaults/_02-breakpoints.scss deleted file mode 100644 index 4b52208..0000000 --- a/components/00-base/00-defaults/_02-breakpoints.scss +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Breakpoint Configuration - * @see https://github.com/Team-Sass/breakpoint/wiki - * - */ - -///////////////////// -// Global Breakpoints - -// Avoid using in favor of atomic, content-specific, breakpoints. -// These should be used for generic code, like layouts and typography, only. - -$xs: 320px; -$small: 480px; -$medium: 720px; -$large: 920px; -$xl: 1224px; -// The max-width breakpoint is used when the design should be applied at whatever the -// largest breakpoint is regardless of actual pixel value. e.g. removing outer margin on body wrapper -$max-width: $xl; - -/// Mixin - xs Breakpoint -/// Allows easier @include xs {} syntax -@mixin xs { - @include breakpoint($xs) { - @content; - } -} - -/// Mixin - small Breakpoint -/// Allows easier @include small {} syntax -@mixin small { - @include breakpoint($small) { - @content; - } -} - -/// Mixin - medium Breakpoint -/// Allows easier @include medium {} syntax -@mixin medium { - @include breakpoint($medium) { - @content; - } -} - -/// Mixin - large Breakpoint -/// Allows easier @include large {} syntax -@mixin large { - @include breakpoint($large) { - @content; - } -} - -/// Mixin - xl Breakpoint -/// Allows easier @include xl {} syntax -@mixin xl { - @include breakpoint($xl) { - @content; - } -} - -/// Mixin - max-width Breakpoint -/// Allows easier @include max-width {} syntax -@mixin max-width { - @include breakpoint($max-width) { - @content; - } -} diff --git a/components/00-base/00-defaults/_03-mixins.scss b/components/00-base/00-defaults/_03-mixins.scss deleted file mode 100644 index c36b696..0000000 --- a/components/00-base/00-defaults/_03-mixins.scss +++ /dev/null @@ -1,68 +0,0 @@ -/* - * General Mixins (put specific ones in component files where applicable) -*/ - -/* Mixin - Clearfix. - * Adds clearfix based on http://bourbon.io/docs/#clearfix - * use example = @include cleafix -*/ - -@mixin body-copy { - font-family: $font-body; - font-size: 1rem; - line-height: 1.6; -} - -@mixin clearfix { - &::after { - clear: both; - content: ''; - display: table; - } -} - -$outer-container-break: $small; - -/// Mixin - Wrapper -/// Outer container mixin for large screens -@mixin wrapper( - $container-max-width: $max-width, - $outer-container-break: $small, - $v-margin: 0, - $v-padding: 0, - $h-padding: $space, - $h-padding-large: $h-padding -) { - max-width: #{$container-max-width}; - width: 100%; - margin: #{$v-margin} auto; - padding: #{$v-padding} #{$h-padding}; - - @include breakpoint($outer-container-break) { - padding: #{$v-padding} #{$h-padding-large}; - } - - @include breakpoint($container-max-width) { - padding-left: calc( - #{$h-padding-large} + calc(-50vw + calc(#{$container-max-width} / 2)) - ); - padding-right: calc( - #{$h-padding-large} + calc(-50vw + calc(#{$container-max-width} / 2)) - ); - } -} - -// Mixin - Standard Margin -@mixin margin { - margin-bottom: 1em; -} - -@mixin no-bottom { - margin-bottom: 0; -} - -@mixin list-reset { - list-style: none; - margin: 0; - padding: 0; -} diff --git a/components/00-base/01-colors/_00-color-vars.scss b/components/00-base/01-colors/_00-color-vars.scss deleted file mode 100644 index 236f0b3..0000000 --- a/components/00-base/01-colors/_00-color-vars.scss +++ /dev/null @@ -1,23 +0,0 @@ -// Color variables - it is better to use the colors in -// components/00-base/01-colors/_01-colors-used.scss rather than -// these colors directly. - -// Grayscale -$white: white; -$near-white: #f2f2f2; -$gray-lightest: #e5e5e5; -$gray-lighter: #ccc; -$gray-light: #888; -$gray: #666; -$gray-dark: #4c4c4c; -$gray-darker: #333; -$black: black; - -// Status -$yellow-light: #f9fb93; -$red-light: #fbe3e4; -$green-light: #cfefc2; - -// Project -$purple: #610c63; -$blue: #005de0; diff --git a/components/00-base/01-colors/_01-colors-used.scss b/components/00-base/01-colors/_01-colors-used.scss deleted file mode 100644 index 1216324..0000000 --- a/components/00-base/01-colors/_01-colors-used.scss +++ /dev/null @@ -1,98 +0,0 @@ -// Color Usage variables - use these (via the `clr` function below) -// rather than color variables directly. -$defaultColors: ( - text: $gray, - text-inverse: $white, - background: $white, - background-section: $gray-lightest, - primary: $blue, - secondary: $purple, - accent: $gray-dark, - accent-high: $black, - highlight: $gray-lighter, - highlight-high: $gray-lightest, - muted: $near-white, - warning: $yellow-light, - error: $red-light, - message: $green-light, -); - -$darkColors: ( - text: $white, - text-inverse: $gray, - background: $gray, - background-inverse: $gray-light, - background-section: $gray-dark, - primary: $blue, - secondary: $purple, - accent: $gray-lightest, - accent-high: $white, - highlight: $gray-dark, - highlight-high: $gray-darker, - muted: $gray-darker, - warning: $yellow-light, - error: $red-light, - message: $green-light, -); - -/////////// -// Usage // -/////////// -// This color function makes it easy for you to call a color by it's functional -// name anywhere you need. -// E.g. to use the `text` color, you type: `color: clr(text);` -// -// How it works: -// This function works in conjunction with the dynamic css custom properties -// declarations below. The item called in the function is prefixed with -// `var(--c-` to call the custom property for that color. -// E.g. `color: clr(text);` will return `color: var(--c-text);` -// Note: (The `--c-` is added via the `@each` loops below) -@function clr($colorChoice) { - @return var(--c-#{$colorChoice}); -} - -// Create CSS custom properties -// This is what powers the built-in OS light/dark mode switching. -// For each color variable above (e.g. `text` and `background`) a css custom -// property will be created. (e.g. `--c-text:` and `--c-background:`). -// -// The first `@each` below will create a custom property for each item in the -// `$defaultColors` map. So put all of your default colors there. -// -// The `prefers-color-scheme: dark` media query below will then create custom -// properties for each of the colors in the `$darkColors` map only if the -// browser supports that feature. If there is an exact match (e.g. `text`) in -// both maps, the one from `$darkColors` will be used on machines that have -// opted to use the 'dark mode' on their OS. -:root { - // Create custom properties for default colors - @each $name, $color in $defaultColors { - --c-#{$name}: #{$color}; - } - - // Create custom properties for dark colors, an duse them if the user's OS has dark mode enabled - @media (prefers-color-scheme: dark) { - @each $name, $color in $darkColors { - --c-#{$name}: #{$color}; - } - } - - // Use the default color scheme for the "Default Theme" section of the - // component library regardless of OS setting. - // This is required to show the correct colors in the component library at all times - [data-theme='default'] { - @each $name, $color in $defaultColors { - --c-#{$name}: #{$color}; - } - } - - // Use the dark color scheme for the "Dark Theme" section of the component - // library regardless of OS setting. - // This is required to show the correct colors in the component library at all times - [data-theme='dark'] { - @each $name, $color in $darkColors { - --c-#{$name}: #{$color}; - } - } -} diff --git a/components/00-base/01-colors/_02-colors-component-library.scss b/components/00-base/01-colors/_02-colors-component-library.scss deleted file mode 100644 index 56fe12c..0000000 --- a/components/00-base/01-colors/_02-colors-component-library.scss +++ /dev/null @@ -1,56 +0,0 @@ -// These styles only affect the colors "Usage" page in the component library -.cl-colors { - padding: 1rem; -} - -.cl-colors__list { - display: flex; - flex-wrap: wrap; - margin: 0 0 2rem; - padding: 0; -} - -.cl-colors__item { - list-style: none; - padding: 1rem 2rem; - transition: all 0.4s; - flex: 1 1 20%; - min-width: 150px; - min-height: 150px; - display: flex; - justify-content: center; - align-items: flex-end; -} - -// Dynamically set swatch text color based on the lightness of the background color -@function set-color( - $color, - $text-primary: accent-high, - $text-secondary: muted -) { - @if (lightness($color) > 50) { - @return clr($text-primary); - } @else { - @return clr($text-secondary); - } -} - -// Style default color swatches -@each $name, $color in $defaultColors { - .cl-colors__item--default { - &-#{$name} { - background-color: clr($name); - color: set-color($color); - } - } -} - -// Style dark color swatches -@each $name, $color in $darkColors { - .cl-colors__item--dark { - &-#{$name} { - background-color: clr($name); - color: set-color($color, muted, accent-high); - } - } -} diff --git a/components/00-base/01-colors/colors.stories.js b/components/00-base/01-colors/colors.stories.js deleted file mode 100644 index 54869bd..0000000 --- a/components/00-base/01-colors/colors.stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import colors from './colors.twig'; - -import colorsData from './colors.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Base/Colors' }; - -export const Palettes = () => ( -
-); diff --git a/components/00-base/01-colors/colors.twig b/components/00-base/01-colors/colors.twig deleted file mode 100644 index 3a27599..0000000 --- a/components/00-base/01-colors/colors.twig +++ /dev/null @@ -1,27 +0,0 @@ -{% set colors_base_class = "cl-colors" %} - -
- {% include "@atoms/text/headings/_heading.twig" with { - heading_level: 2, - heading: colors_heading, - } %} - - {% for palette in palettes %} - {% set palette_machine = palette.palette_heading|lower %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_level: 3, - heading: palette.palette_heading, - } %} -
    - {% for color in palette.colors %} - {% set color_machine = color.name|lower|replace({' ': '-'}) %} - {% if color.color_reverse == TRUE %} - {% set colors_title_modifiers = ['reverse'] %} - {% endif %} -
  • - {{ color.name }}
    -
  • - {% endfor %} -
- {% endfor %} -
diff --git a/components/00-base/01-colors/colors.yml b/components/00-base/01-colors/colors.yml deleted file mode 100644 index a6e98ad..0000000 --- a/components/00-base/01-colors/colors.yml +++ /dev/null @@ -1,30 +0,0 @@ -colors_heading: 'Palettes:' -palettes: - - palette_heading: 'Default' - colors: - - name: 'Text' - - name: 'Background' - - name: 'Primary' - - name: 'Secondary' - - name: 'Accent' - - name: 'Accent High' - - name: 'Highlight' - - name: 'Highlight High' - - name: 'Muted' - - name: 'Warning' - - name: 'Error' - - name: 'Message' - - palette_heading: 'Dark' - colors: - - name: 'Text' - - name: 'Background' - - name: 'Primary' - - name: 'Secondary' - - name: 'Accent' - - name: 'Accent High' - - name: 'Highlight' - - name: 'Highlight High' - - name: 'Muted' - - name: 'Warning' - - name: 'Error' - - name: 'Message' diff --git a/components/00-base/02-motion/_motion.scss b/components/00-base/02-motion/_motion.scss deleted file mode 100644 index 67d41b6..0000000 --- a/components/00-base/02-motion/_motion.scss +++ /dev/null @@ -1,118 +0,0 @@ -/* Mixin - Transition */ -$transition-duration-base: 0.3s; -$transition-timing-function-base: ease-in-out; - -@mixin transition( - $transition-property: all, - $transition-duration: $transition-duration-base, - $transition-timing-function: $transition-timing-function-base -) { - transition: $transition-property $transition-duration - $transition-timing-function; -} - -// Demo UI -.motion { - padding: 0 1rem; -} - -.motion__grid { - @media (min-width: 920px) { - display: flex; - flex-wrap: wrap; - } -} - -.motion__grid-item { - @media (min-width: 920px) { - margin-bottom: 1rem; - margin-right: 1rem; - width: 49%; - - &:nth-child(2n) { - margin-right: 0; - } - } -} - -.demo-motion { - background-color: clr(highlight-high); - border-radius: 2px; - cursor: pointer; - padding: 3rem; - text-align: center; - - &::before { - content: 'Duration: #{$transition-duration-base}'; - display: block; - } - - &::after { - content: 'Timing Function: #{$transition-timing-function-base}'; - } - - span { - display: block; - margin: 0.25rem; - } -} - -.demo-motion--fade { - @include transition; - - &:hover { - background-color: clr(accent-high); - color: clr(muted); - } -} - -.demo-motion--slide-up { - @include transition(transform, 0.4s, ease-in); - - &::before { - content: 'Duration: 0.4s'; - display: block; - } - - &::after { - content: 'Timing Function: ease-in'; - } - - &:hover { - transform: translateY(-10px); - } -} - -.demo-motion--slide-down { - @include transition(transform, 0.2s, linear); - - &::before { - content: 'Duration: 0.2s'; - display: block; - } - - &::after { - content: 'Timing Function: linear'; - } - - &:hover { - transform: translateY(10px); - } -} - -.demo-motion--expand { - @include transition(transform, 0.3s, cubic-bezier(0.17, 0.67, 0.83, 0.67)); - - &::before { - content: 'Duration: 0.3s'; - display: block; - } - - &::after { - content: 'Timing Function: cubic-bezier(.17,.67,.83,.67)'; - } - - &:hover { - transform: scale(1.03); - } -} diff --git a/components/00-base/02-motion/motion.stories.js b/components/00-base/02-motion/motion.stories.js deleted file mode 100644 index 14135b7..0000000 --- a/components/00-base/02-motion/motion.stories.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -import motion from './motion.twig'; - -import motionData from './motion.yml'; - -/** - * Add storybook definition for Animations. - */ -export default { title: 'Base/Motion' }; - -export const Usage = () => ( -
-); diff --git a/components/00-base/02-motion/motion.twig b/components/00-base/02-motion/motion.twig deleted file mode 100644 index 594c491..0000000 --- a/components/00-base/02-motion/motion.twig +++ /dev/null @@ -1,11 +0,0 @@ -
-

Motion

-
- {% for motion in motions %} -
-

{{ motion.name }}

-
-
- {% endfor %} -
-
diff --git a/components/00-base/02-motion/motion.yml b/components/00-base/02-motion/motion.yml deleted file mode 100644 index 6f1c886..0000000 --- a/components/00-base/02-motion/motion.yml +++ /dev/null @@ -1,5 +0,0 @@ -motions: - - name: 'Fade' - - name: 'Slide Up' - - name: 'Slide Down' - - name: 'Expand' diff --git a/components/00-base/03-site/_base.scss b/components/00-base/03-site/_base.scss deleted file mode 100644 index 6dbf12e..0000000 --- a/components/00-base/03-site/_base.scss +++ /dev/null @@ -1,31 +0,0 @@ -html { - box-sizing: border-box; -} - -body { - background-color: clr(background); - color: clr(text); - - @include body-copy; -} - -*, -*::after, -*::before { - box-sizing: inherit; -} - -.main { - @include wrapper; - - display: block; /* Fix for IE weirdness */ -} - -.visually-hidden { - position: absolute !important; - clip: rect(1px, 1px, 1px, 1px); - overflow: hidden; - height: 1px; - width: 1px; - word-wrap: normal; -} diff --git a/components/01-atoms/buttons/_buttons.scss b/components/01-atoms/buttons/_buttons.scss deleted file mode 100644 index 5df5faa..0000000 --- a/components/01-atoms/buttons/_buttons.scss +++ /dev/null @@ -1,100 +0,0 @@ -// Sass map to define colors. Each set should have a "Dark" variation when required. -// e.g. `default` and `default-dark`. Or `purple` and `purple-dark`. -$button-colors: ( - primary: ( - text: clr(text-inverse), - bg: clr(primary), - text-hover: clr(primary), - bg-hover: clr(highlight), - ), - primary-dark: ( - text: clr(text), - bg: clr(primary), - text-hover: clr(text), - bg-hover: clr(highlight-high), - ), - secondary: ( - text: clr(text-inverse), - bg: clr(secondary), - text-hover: clr(secondary), - bg-hover: clr(highlight), - ), - secondary-dark: ( - text: clr(text), - bg: clr(secondary), - text-hover: clr(text), - bg-hover: clr(highlight-high), - ), -); - -// Mixin to define colors for one or more schemes. -// Simply pass the color scheme to the mixin to get the colors defined in the map. -// e.g. @include buttonColors(default) will get all of the colors defined in the "default" section. -@mixin buttonColors( - $scheme, - $pallate: map-get($button-colors, $scheme), - $map: $button-colors -) { - color: map-get($pallate, text); - background-color: map-get($pallate, bg); - - &:visited { - color: map-get($pallate, text); - } - - &:hover { - color: map-get($pallate, text-hover); - background-color: map-get($pallate, bg-hover); - } -} - -// The button-base mixin contains styles that apply to all buttons -// regardless of color or size. -@mixin button-base { - border: none; - cursor: pointer; - display: inline-block; - text-decoration: none; - text-align: center; - text-transform: uppercase; -} - -// Button color variations -// Note how we include dark mode options for each -@mixin button-color-primary { - @include buttonColors(primary); - - @media (prefers-color-scheme: dark) { - @include buttonColors(primary-dark); - } -} - -@mixin button-color-secondary { - @include buttonColors(secondary); - - @media (prefers-color-scheme: dark) { - @include buttonColors(secondary-dark); - } -} - -// Button size variations -@mixin button-medium { - line-height: 1.4; - padding: $space-one-half $space; -} - -@mixin button-large { - line-height: 2; - padding: $space $space-double; -} - -.button { - @include button-base; - @include button-color-primary; - @include button-medium; - - &--alt { - @include button-color-secondary; - @include button-large; - } -} diff --git a/components/01-atoms/buttons/buttons.stories.js b/components/01-atoms/buttons/buttons.stories.js deleted file mode 100644 index ae00e8f..0000000 --- a/components/01-atoms/buttons/buttons.stories.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import Button from './react/Button.component'; - -import button from './twig/button.twig'; - -import buttonData from './twig/button.yml'; -import buttonAltData from './twig/button-alt.yml'; -import buttonAlt2Data from './twig/button-alt2.yml'; - -/** - * Storybook Definition. - */ -export default { - component: Button, - title: 'Atoms/Button', -}; - -export const react = () => ; - -export const twig = () => ( -
-); -export const twigAlt = () => ( -
-); -export const twigAlt2 = () => ( -
-); diff --git a/components/01-atoms/buttons/react/Button.component.js b/components/01-atoms/buttons/react/Button.component.js deleted file mode 100644 index 74d5d9a..0000000 --- a/components/01-atoms/buttons/react/Button.component.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @file Button.component.js - * Exports a button component. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -/** - * Component that renders a button with a click handler. - */ -const Button = (props) => { - const { onClick, children } = props; - - return ( - - ); -}; - -Button.propTypes = { - onClick: PropTypes.func, - children: PropTypes.node, -}; - -Button.defaultProps = { - children: null, - onClick: () => {}, -}; - -export default Button; diff --git a/components/01-atoms/buttons/twig/button-alt.yml b/components/01-atoms/buttons/twig/button-alt.yml deleted file mode 100644 index 73aedee..0000000 --- a/components/01-atoms/buttons/twig/button-alt.yml +++ /dev/null @@ -1,4 +0,0 @@ -button_content: 'Alternative Button' -button_url: '#' -button_modifiers: - - 'alt' diff --git a/components/01-atoms/buttons/twig/button-alt2.yml b/components/01-atoms/buttons/twig/button-alt2.yml deleted file mode 100644 index bb71b3e..0000000 --- a/components/01-atoms/buttons/twig/button-alt2.yml +++ /dev/null @@ -1,4 +0,0 @@ -button_content: 'Second Alternative Button' -button_url: '#' -button_modifiers: - - 'alt-2' diff --git a/components/01-atoms/buttons/twig/button.twig b/components/01-atoms/buttons/twig/button.twig deleted file mode 100644 index ba6c3fa..0000000 --- a/components/01-atoms/buttons/twig/button.twig +++ /dev/null @@ -1,22 +0,0 @@ -{# -/** - * Available variables: - * - button_content - the content of the button (typically text) - * - * Available blocks: - * - button_content - used to replace the content of the button with something other than text - * for example: to insert an icon - */ -#} - -{% set button_base_class = button_base_class|default('button') %} - -{% set additional_attributes = { - class: bem(button_base_class, button_modifiers, button_blockname), -} %} - - diff --git a/components/01-atoms/buttons/twig/button.yml b/components/01-atoms/buttons/twig/button.yml deleted file mode 100644 index 1ddc203..0000000 --- a/components/01-atoms/buttons/twig/button.yml +++ /dev/null @@ -1,2 +0,0 @@ -button_content: "Default Button" -button_url: "#" diff --git a/components/01-atoms/forms/checkbox/_checkbox-item.twig b/components/01-atoms/forms/checkbox/_checkbox-item.twig deleted file mode 100644 index db5a47e..0000000 --- a/components/01-atoms/forms/checkbox/_checkbox-item.twig +++ /dev/null @@ -1,5 +0,0 @@ -
  • - -
  • diff --git a/components/01-atoms/forms/checkbox/_checkbox.scss b/components/01-atoms/forms/checkbox/_checkbox.scss deleted file mode 100644 index 09a07e0..0000000 --- a/components/01-atoms/forms/checkbox/_checkbox.scss +++ /dev/null @@ -1,4 +0,0 @@ -.form-item--checkboxes, -.form-item--checkbox__item { - @include list-reset; -} diff --git a/components/01-atoms/forms/checkbox/checkbox.twig b/components/01-atoms/forms/checkbox/checkbox.twig deleted file mode 100644 index 2f4d67e..0000000 --- a/components/01-atoms/forms/checkbox/checkbox.twig +++ /dev/null @@ -1,15 +0,0 @@ -{% if not attributes %} -
    - Options as Checkboxes -
      -{% endif %} - {% for key, checkbox in checkboxes %} - {% include "@atoms/forms/checkbox/_checkbox-item.twig" with { - checkbox_item: checkbox.title, - checkbox_item_checked: checkbox.checked, - } %} - {% endfor %} -{% if not attributes %} -
    -
    -{% endif %} diff --git a/components/01-atoms/forms/checkbox/checkbox.yml b/components/01-atoms/forms/checkbox/checkbox.yml deleted file mode 100644 index 93a73fd..0000000 --- a/components/01-atoms/forms/checkbox/checkbox.yml +++ /dev/null @@ -1,6 +0,0 @@ -checkboxes: - - title: 'Option 1' - checked: 'checked' - - title: 'Option 2' - - title: 'Option 3' - - title: 'Option 4' diff --git a/components/01-atoms/forms/forms.stories.js b/components/01-atoms/forms/forms.stories.js deleted file mode 100644 index 53889d0..0000000 --- a/components/01-atoms/forms/forms.stories.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import checkbox from './checkbox/checkbox.twig'; -import radio from './radio/radio.twig'; -import select from './select/select.twig'; -import textfields from './textfields/textfields.twig'; - -import checkboxData from './checkbox/checkbox.yml'; -import radioData from './radio/radio.yml'; -import selectOptionsData from './select/select.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Forms' }; - -export const checkboxes = () => ( -
    -); -export const radioButtons = () => ( -
    -); -export const selectDropdowns = () => ( -
    -); -export const textfieldsExamples = () => ( -
    -); diff --git a/components/01-atoms/forms/radio/_radio-item.twig b/components/01-atoms/forms/radio/_radio-item.twig deleted file mode 100644 index 33b95e7..0000000 --- a/components/01-atoms/forms/radio/_radio-item.twig +++ /dev/null @@ -1,5 +0,0 @@ -
  • - -
  • diff --git a/components/01-atoms/forms/radio/_radio.scss b/components/01-atoms/forms/radio/_radio.scss deleted file mode 100644 index 872994e..0000000 --- a/components/01-atoms/forms/radio/_radio.scss +++ /dev/null @@ -1,4 +0,0 @@ -.form-item--radios, -.form-item--radio__item { - @include list-reset; -} diff --git a/components/01-atoms/forms/radio/radio.twig b/components/01-atoms/forms/radio/radio.twig deleted file mode 100644 index cc65c2c..0000000 --- a/components/01-atoms/forms/radio/radio.twig +++ /dev/null @@ -1,15 +0,0 @@ -{% if not attributes %} -
    - Options as Radio Buttons -
      -{% endif %} - {% for key, radio in radios %} - {% include "@atoms/forms/radio/_radio-item.twig" with { - radio_item: radio.title, - radio_item_checked: radio.checked - } %} - {% endfor %} -{% if not attributes %} -
    -
    -{% endif %} diff --git a/components/01-atoms/forms/radio/radio.yml b/components/01-atoms/forms/radio/radio.yml deleted file mode 100644 index 278b78e..0000000 --- a/components/01-atoms/forms/radio/radio.yml +++ /dev/null @@ -1,6 +0,0 @@ -radios: - - title: 'Option 1' - checked: 'checked' - - title: 'Option 2' - - title: 'Option 3' - - title: 'Option 4' diff --git a/components/01-atoms/forms/select/_select-item.twig b/components/01-atoms/forms/select/_select-item.twig deleted file mode 100644 index a38afcb..0000000 --- a/components/01-atoms/forms/select/_select-item.twig +++ /dev/null @@ -1 +0,0 @@ - diff --git a/components/01-atoms/forms/select/_select.scss b/components/01-atoms/forms/select/_select.scss deleted file mode 100644 index eb3e8a6..0000000 --- a/components/01-atoms/forms/select/_select.scss +++ /dev/null @@ -1,72 +0,0 @@ -// CSS-only select styling (from https://github.com/filamentgroup/select-css) - -.form-item__dropdown { - border: 1px solid clr(muted); - display: block; - position: relative; - - @media (prefers-color-scheme: dark) { - background-color: clr(background-inverse); - } - - &::after { - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 9px solid clr(accent); - content: ' '; - position: absolute; - top: 42%; - right: 1em; - z-index: 2; - - /* These hacks make the select behind the arrow clickable in some browsers */ - pointer-events: none; - display: none; - } - - &:hover { - border-color: clr(accent-high); - } -} - -.form-item__select { - border: 1px solid clr(muted); - height: 41px; // set height required for discrepancy between .form-item__dropdown border and the select :focus border - font-size: 16px; - margin: 0; - outline: none; - padding: 0.6em 0.8em 0.5em; - width: 100%; - - :focus { - outline: none; - color: clr(accent); - } -} - -@supports (-webkit-appearance: none) or (appearance: none) or - ((-moz-appearance: none) and (mask-type: alpha)) { - /* Show custom arrow */ - .form-item__dropdown::after { - display: block; - } - - /* Remove select styling */ - .form-item__select { - padding-right: 2em; /* Match-01 */ - - /* inside @supports so that iOS <= 8 display the native arrow */ - background: none; /* Match-04 */ - - /* inside @supports so that Android <= 4.3 display the native arrow */ - border: 1px solid transparent; /* Match-05 */ - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - - &:focus { - border-color: clr(accent); - border-radius: 0; - } - } -} diff --git a/components/01-atoms/forms/select/select.twig b/components/01-atoms/forms/select/select.twig deleted file mode 100644 index 7a903d8..0000000 --- a/components/01-atoms/forms/select/select.twig +++ /dev/null @@ -1,18 +0,0 @@ -{% if not attributes %} -
    - -
    - -
    -
    -{% endif %} diff --git a/components/01-atoms/forms/select/select.yml b/components/01-atoms/forms/select/select.yml deleted file mode 100644 index 597ac2f..0000000 --- a/components/01-atoms/forms/select/select.yml +++ /dev/null @@ -1,6 +0,0 @@ -select: - - title: 'Choose an Option' - - title: 'Option 1' - - title: 'Option 2' - - title: 'Option 3' - - title: 'Option 4' diff --git a/components/01-atoms/forms/textfields/_textfields.scss b/components/01-atoms/forms/textfields/_textfields.scss deleted file mode 100644 index 32c52aa..0000000 --- a/components/01-atoms/forms/textfields/_textfields.scss +++ /dev/null @@ -1,47 +0,0 @@ -.form-item { - color: clr(text); - margin-bottom: 1em; - max-width: 32em; - - @include clearfix; -} - -.form-item__label { - display: block; - font-weight: 600; - - @include xs { - display: block; - margin-right: 2%; - padding: 0.6em 0; - } -} - -.form-item__textfield { - border: 1px solid clr(highlight-high); - padding: 0.6em; - max-width: 100%; - - &:focus { - border-color: clr(accent-high); - } - - &::placeholder { - color: clr(highlight); - } -} - -.form-item__description { - margin-top: 0.3em; -} - -.form-fieldset { - border: none; - margin-bottom: 2em; - padding: 0; - - .form-item { - margin-left: 1em; - max-width: 31em; - } -} diff --git a/components/01-atoms/forms/textfields/textfields.twig b/components/01-atoms/forms/textfields/textfields.twig deleted file mode 100644 index b67f470..0000000 --- a/components/01-atoms/forms/textfields/textfields.twig +++ /dev/null @@ -1,37 +0,0 @@ -
    - - -
    - -
    - - -
    - Enter the password that accompanies your username. -
    -
    - -
    -
    - Web and Email Address (fieldset example) -
    - - -
    - -
    - - -
    -
    -
    - -
    - - -
    - -
    - - -
    diff --git a/components/01-atoms/images/icons/_icon.twig b/components/01-atoms/images/icons/_icon.twig deleted file mode 100644 index ee5ee58..0000000 --- a/components/01-atoms/images/icons/_icon.twig +++ /dev/null @@ -1,43 +0,0 @@ -{# -/** - * Available variables: - * - icon_base_class - base class name - * - icon_modifiers - modifiers for icons (array) - * - icon_blockname - blockname prepended to the base classname - * - icon_name - the name of the icon - * - icon_role - a11y role - * - icon_title - a11y title - * - icon_desc - a11y description - */ -#} -{# If IE 11 support is needed, reference `./svgxuse.min.js` in the theme. #} - -{% set icon_base_class = icon_base_class|default('icon') %} -{# If `directory` is defined, set the path relative for Wordpress. - # Otherwise, set the path relative to the Component Library. #} -{% set icon_url = theme ? theme.link ~ '/dist/' %} - - diff --git a/components/01-atoms/images/icons/_icons.scss b/components/01-atoms/images/icons/_icons.scss deleted file mode 100644 index cb71fb7..0000000 --- a/components/01-atoms/images/icons/_icons.scss +++ /dev/null @@ -1,26 +0,0 @@ -// Storybook styling -.icons-demo { - display: flex; - - .icon { - height: 100px; - padding: $space; - width: 100px; - - @media (prefers-color-scheme: dark) { - fill: clr(accent-high); - } - } - - pre { - background-color: clr(muted); - font-size: 0.8rem; - margin: 0; - text-align: center; - } -} - -.icons-demo__item { - border: 1px solid clr(muted); - margin: $space-one-fourth; -} diff --git a/components/01-atoms/images/icons/icons.md b/components/01-atoms/images/icons/icons.md deleted file mode 100644 index 851fb54..0000000 --- a/components/01-atoms/images/icons/icons.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Icons ---- - -### How to use icons - -We are using an SVG sprite generator (details [here](https://una.im/svg-icons)), which automatically takes individual SVGs from `/images/icons/src` and generates `/dist/img/sprite/sprite.svg`. Simply run `yarn build` to add your individual SVGs to this sprite (this is automatically run when running `yarn develop`). - -**Usage** - -The SVG component is found here: `/components/01-atoms/images/icons/_icon.twig`. See available variables in that file as well as instructions for Wordpress. Examples of usage below: - -Simple: (no BEM renaming) - -``` -{% include "@atoms/images/icons/_icon.twig" with { - icon_name: 'bars', -} %} -``` - -... creates... - -``` - - - -``` - -Complex (BEM classes): - -``` -{% include "@atoms/images/icons/_icon.twig" with { - icon_base_class: 'icon', - icon_blockname: 'toggle-expand', - icon_name: 'bars', -} %} -``` - -... creates... - -``` - - - -``` diff --git a/components/01-atoms/images/icons/icons.twig b/components/01-atoms/images/icons/icons.twig deleted file mode 100644 index 46cca23..0000000 --- a/components/01-atoms/images/icons/icons.twig +++ /dev/null @@ -1,15 +0,0 @@ -
    - {% for item in items %} -
    - {% include "@atoms/images/icons/_icon.twig" with { - icon_name: item.value, - icon_role: item.role, - icon_title: item.title, - icon_desc: item.desc, - icon_decorative: item.decorative, - } %} -
    {{ item.value }}
    -
    - {% endfor %} -
    -
    diff --git a/components/01-atoms/images/icons/svgxuse.min.js b/components/01-atoms/images/icons/svgxuse.min.js deleted file mode 100644 index ce2099f..0000000 --- a/components/01-atoms/images/icons/svgxuse.min.js +++ /dev/null @@ -1,12 +0,0 @@ -/*! - * @copyright Copyright (c) 2017 IcoMoon.io - * @license Licensed under MIT license - * See https://github.com/Keyamoon/svgxuse - * @version 1.2.6 - */ -(function(){if("undefined"!==typeof window&&window.addEventListener){var e=Object.create(null),l,d=function(){clearTimeout(l);l=setTimeout(n,100)},m=function(){},t=function(){window.addEventListener("resize",d,!1);window.addEventListener("orientationchange",d,!1);if(window.MutationObserver){var k=new MutationObserver(d);k.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0});m=function(){try{k.disconnect(),window.removeEventListener("resize",d,!1),window.removeEventListener("orientationchange", -d,!1)}catch(v){}}}else document.documentElement.addEventListener("DOMSubtreeModified",d,!1),m=function(){document.documentElement.removeEventListener("DOMSubtreeModified",d,!1);window.removeEventListener("resize",d,!1);window.removeEventListener("orientationchange",d,!1)}},u=function(k){function e(a){if(void 0!==a.protocol)var c=a;else c=document.createElement("a"),c.href=a;return c.protocol.replace(/:/g,"")+c.host}if(window.XMLHttpRequest){var d=new XMLHttpRequest;var m=e(location);k=e(k);d=void 0=== -d.withCredentials&&""!==k&&k!==m?XDomainRequest||void 0:XMLHttpRequest}return d};var n=function(){function d(){--q;0===q&&(m(),t())}function l(a){return function(){!0!==e[a.base]&&(a.useEl.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href","#"+a.hash),a.useEl.hasAttribute("href")&&a.useEl.setAttribute("href","#"+a.hash))}}function p(a){return function(){var c=document.body,b=document.createElement("x");a.onload=null;b.innerHTML=a.responseText;if(b=b.getElementsByTagName("svg")[0])b.setAttribute("aria-hidden", -"true"),b.style.position="absolute",b.style.width=0,b.style.height=0,b.style.overflow="hidden",c.insertBefore(b,c.firstChild);d()}}function n(a){return function(){a.onerror=null;a.ontimeout=null;d()}}var a,c,q=0;m();var f=document.getElementsByTagName("use");for(c=0;c diff --git a/components/01-atoms/images/image/_picture.twig b/components/01-atoms/images/image/_picture.twig deleted file mode 100644 index 22646ca..0000000 --- a/components/01-atoms/images/image/_picture.twig +++ /dev/null @@ -1,21 +0,0 @@ -{% set picture_base_class = picture_base_class|default('picture') %} - - - {% if sources %} - {# - Internet Explorer 9 doesn't recognise source elements that are wrapped in - picture tags. See http://scottjehl.github.io/picturefill/#ie9 - #} - - {% for source_attributes in sources %} - - {% endfor %} - - {% endif %} - {# The controlling image, with the fallback image in srcset. #} - {% include "@atoms/images/image/_image.twig" with { - image_blockname: picture_image_blockname|default(picture_blockname), - } %} - diff --git a/components/01-atoms/images/image/figure.twig b/components/01-atoms/images/image/figure.twig deleted file mode 100644 index afea978..0000000 --- a/components/01-atoms/images/image/figure.twig +++ /dev/null @@ -1,24 +0,0 @@ -{% set image_figure_base_class = image_figure_base_class|default('figure') %} -{% set image_link_base_class = image_link_base_class|default('link') %} - -
    - {% if image_url %} - - {% endif %} - {% block figure_content %} - {% include "@atoms/images/image/responsive-image.twig" with { - responsive_image_blockname: responsive_image_blockname|default(image_figure_base_class), - } %} - {% endblock %} - {% if image_url %} - - {% endif %} - - {% if image_caption %} -
    - {{ image_caption }} -
    - {% endif %} -
    diff --git a/components/01-atoms/images/image/figure.yml b/components/01-atoms/images/image/figure.yml deleted file mode 100644 index 8c808b7..0000000 --- a/components/01-atoms/images/image/figure.yml +++ /dev/null @@ -1,5 +0,0 @@ -output_image_tag: true -image_url: '#' -image_src: 'https://placeimg.com/1200/200/tech' -image_alt: 'This is the alt text' -image_caption: 'This is an image caption.' diff --git a/components/01-atoms/images/image/image.yml b/components/01-atoms/images/image/image.yml deleted file mode 100644 index 6cd49c8..0000000 --- a/components/01-atoms/images/image/image.yml +++ /dev/null @@ -1,5 +0,0 @@ -output_image_tag: true -image_srcset: 'https://placeimg.com/320/180/any 320w, https://placeimg.com/480/270/any 480w, https://placeimg.com/640/360/any 640w, https://placeimg.com/800/450/any 800w, https://placeimg.com/960/540/any 960w, https://placeimg.com/1120/630/any 1120w, https://placeimg.com/1280/720/any 1280w, https://placeimg.com/1440/810/any 1440w, https://placeimg.com/1600/900/any 1600w, https://placeimg.com/1760/990/any 1760w, https://placeimg.com/1920/1080/any 1920w, https://placeimg.com/2080/1170/any 2080w, https://placeimg.com/2240/1260/any 2240w' -image_sizes: '100vw' -image_src: 'https://placeimg.com/320/180/any' -image_alt: 'A 16 by 9 image' diff --git a/components/01-atoms/images/image/responsive-image.twig b/components/01-atoms/images/image/responsive-image.twig deleted file mode 100644 index 99cb3e7..0000000 --- a/components/01-atoms/images/image/responsive-image.twig +++ /dev/null @@ -1,32 +0,0 @@ -{# - # Available variables: - # - sources: The attributes of the tags for this tag. - # - img_element: The controlling image, with the fallback image in srcset. - # - output_image_tag: Whether or not to output an tag instead of a tag. - #} - -{% set responsive_image_base_class = responsive_image_base_class|default('image') %} - -{% if output_image_tag %} - {% include "@atoms/images/image/_image.twig" with { - image_base_class: responsive_image_base_class, - image_modifiers: responsive_image_modifiers, - image_blockname: responsive_image_blockname, - image_srcset: image_srcset|default(img_element['#attributes'].srcset), - image_sizes: image_sizes|default(img_element['#attributes'].sizes), - image_src: image_src|default(img_element['#uri']), - image_alt: image_alt|default(img_element['#alt']), - image_title: image_title|default(img_element['#title']), - } %} -{% else %} - {% include "@atoms/images/image/_picture.twig" with { - picture_base_class: responsive_image_base_class, - picture_modifiers: responsive_image_modifiers, - picture_blockname: responsive_image_blockname, - image_srcset: image_srcset|default(img_element['#attributes'].srcset), - image_sizes: image_sizes|default(img_element['#attributes'].sizes), - image_src: image_src|default(img_element['#uri']), - image_alt: image_alt|default(img_element['#alt']), - image_title: image_title|default(img_element['#title']), - } %} -{% endif %} diff --git a/components/01-atoms/images/images.stories.js b/components/01-atoms/images/images.stories.js deleted file mode 100644 index 66290bb..0000000 --- a/components/01-atoms/images/images.stories.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; - -import image from './image/responsive-image.twig'; -import figure from './image/figure.twig'; -import iconTwig from './icons/icons.twig'; - -import imageData from './image/image.yml'; -import figureData from './image/figure.yml'; - -const svgIcons = require.context('../../../images/icons/', true, /\.svg$/); - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Images' }; - -export const images = () => ( -
    -); -export const figures = () => ( -
    -); - -const items = []; -svgIcons.keys().forEach((key) => { - const iconName = svgIcons(key).split('static/media/').pop().split('.')[0]; - const icon = {}; - icon.value = iconName; - items.push(icon); -}); - -export const icons = () => ( -
    -); diff --git a/components/01-atoms/links/link/_link.scss b/components/01-atoms/links/link/_link.scss deleted file mode 100644 index 1637caf..0000000 --- a/components/01-atoms/links/link/_link.scss +++ /dev/null @@ -1,11 +0,0 @@ -@mixin link { - color: clr(accent); - - &:hover { - color: clr(accent-high); - } -} - -.link { - @include link; -} diff --git a/components/01-atoms/links/link/link.twig b/components/01-atoms/links/link/link.twig deleted file mode 100644 index e181bf6..0000000 --- a/components/01-atoms/links/link/link.twig +++ /dev/null @@ -1,28 +0,0 @@ -{# -/** - * Available variables: - * - link_content - the content of the link (typically text) - * - link_url - the url this link should poing to - * - link_attributes - array of attribute,value pairs - * - link_base_class - the base class name - * - link_modifiers - array of modifiers to add to the base classname - * - link_blockname - blockname prepended to the base classname - * - * Available blocks: - * - link_content - used to replace the content of the link - * Example: to insert the image component - */ -#} -{% set link_base_class = link_base_class|default('link') %} - - - {% block link_content %} - {{ link_content }} - {% endblock %} - diff --git a/components/01-atoms/links/link/link.yml b/components/01-atoms/links/link/link.yml deleted file mode 100644 index f7c96ef..0000000 --- a/components/01-atoms/links/link/link.yml +++ /dev/null @@ -1,4 +0,0 @@ -link_url: 'https://github.com/emulsify-ds/emulsify-design-system' -link_content: 'This is my link text' -link_attributes: - 'target': '_blank' diff --git a/components/01-atoms/links/link/links.stories.js b/components/01-atoms/links/link/links.stories.js deleted file mode 100644 index 3bd99f8..0000000 --- a/components/01-atoms/links/link/links.stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import link from './link.twig'; - -import linkData from './link.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Links' }; - -export const links = () => ( -
    -); diff --git a/components/01-atoms/lists/_list-item-definition.twig b/components/01-atoms/lists/_list-item-definition.twig deleted file mode 100644 index 6126376..0000000 --- a/components/01-atoms/lists/_list-item-definition.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% set dl_term_base_class = dl_term_base_class|default('dl-term') %} -{% set dl_def_base_class = dl_def_base_class|default('dl-def') %} - -
    {{ dl_term }}
    -
    {{ dl_def }}
    diff --git a/components/01-atoms/lists/_list-item.twig b/components/01-atoms/lists/_list-item.twig deleted file mode 100644 index b13db98..0000000 --- a/components/01-atoms/lists/_list-item.twig +++ /dev/null @@ -1,22 +0,0 @@ -{# -/** - * Available variables: - * - li_base_class - the base classname - * - li_modifiers - array of modifiers to add to the base classname - * - li_blockname - blockname prepended to the base classname - * - list_item_label - (optional) a label before the list item itself - * - list_item_content - the content of the list_item (typically text) - * - * Available blocks: - * - list_item_content - used to replace the content of the list_item with something other than text - * for example: to insert the image and/or link components - */ -#} -{% set li_base_class = li_base_class|default('list-item') %} - -
  • - {% block list_item_content %} - {% if list_item_label %}{{ list_item_label }} {% endif %} - {{ list_item_content }} - {% endblock %} -
  • diff --git a/components/01-atoms/lists/_lists.scss b/components/01-atoms/lists/_lists.scss deleted file mode 100644 index f5a22c2..0000000 --- a/components/01-atoms/lists/_lists.scss +++ /dev/null @@ -1,32 +0,0 @@ -/* Mixin - list-reset - * Reset list item defaults when no margin, padding, list styles needed -*/ - -ul, -ol { - padding-left: 1em; -} - -ul { - list-style-type: disc; -} - -ol { - list-style-type: decimal; -} - -.list-item { - margin-bottom: 0.2em; - padding-left: 1em; -} - -/* TODO: BEM selectors should be applied to wysiwyg-created content */ - -.text-long { - ol, - ul { - li { - @extend .list-item; - } - } -} diff --git a/components/01-atoms/lists/dl.twig b/components/01-atoms/lists/dl.twig deleted file mode 100644 index 2cd1a8f..0000000 --- a/components/01-atoms/lists/dl.twig +++ /dev/null @@ -1,28 +0,0 @@ -{# -/** - * Available variables: - * - dl_base_class - the base classname - * - dl_modifiers - array of modifiers to add to the base classname - * - dl_blockname - blockname prepended to the base classname - * - * - listItems - TBD - */ -#} -{% set dl_base_class = dl_base_class|default('dl') %} - -
    - {% block list_content %} - {% for dl_item in dl_items %} - {% include "@atoms/lists/_list-item-definition.twig" with { - dl_term: dl_item.dl_term, - dl_def: dl_item.dl_def, - dl_term_base_class: dl_item.dl_term_base_class, - dl_def_base_class: dl_item.dl_def_base_class, - dl_term_modifiers: dl_item.dl_term_modifiers, - dl_def_modifiers: dl_item.dl_def_modifiers, - dl_term_blockname: dl_item.dl_term_blockname, - dl_def_blockname: dl_item.dl_def_blockname, - } %} - {% endfor %} - {% endblock %} -
    diff --git a/components/01-atoms/lists/dl.yml b/components/01-atoms/lists/dl.yml deleted file mode 100644 index 1eac9e6..0000000 --- a/components/01-atoms/lists/dl.yml +++ /dev/null @@ -1,9 +0,0 @@ -dl_items: - - dl_term: 'Definition List' - dl_def: 'A number of connected items or names written or printed consecutively, typically one below the other.' - - dl_term: 'This is a term.' - dl_def: 'This is the definition of that term, which both live in a dl.' - - dl_term: 'Here is another term.' - dl_def: 'And it gets a definition too, which is this line.' - - dl_term: 'Here is one last term.' - dl_def: 'With the final definition.' diff --git a/components/01-atoms/lists/lists.stories.js b/components/01-atoms/lists/lists.stories.js deleted file mode 100644 index 8399dc2..0000000 --- a/components/01-atoms/lists/lists.stories.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -import dl from './dl.twig'; -import ul from './ul.twig'; -import ol from './ol.twig'; - -import dlData from './dl.yml'; -import ulData from './ul.yml'; -import olData from './ol.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Lists' }; - -export const definitionList = () => ( -
    -); -export const unorderedList = () => ( -
    -); -export const orderedList = () => ( -
    -); diff --git a/components/01-atoms/lists/ol.twig b/components/01-atoms/lists/ol.twig deleted file mode 100644 index 7fd24aa..0000000 --- a/components/01-atoms/lists/ol.twig +++ /dev/null @@ -1,26 +0,0 @@ -{# -/** - * Available variables: - * - ol_base_class - the base classname - * - ol_modifiers - array of modifiers to add to the base classname - * - ol_blockname - blockname prepended to the base classname - * - * - listItems - TBD - */ -#} -{% set ol_base_class = ol_base_class|default('ol') %} - -
      - {% block list_content %} - {% for ol_item in ol_items %} - {% include "@atoms/lists/_list-item.twig" with { - list_item_label: ol_item.label, - list_item_content: ol_item.content, - li_base_class: ol_item.li_base_class, - li_base_class: ol_item.li_base_class, - li_modifiers: ol_item.li_modifiers, - li_blockname: ol_item.li_blockname, - } %} - {% endfor %} - {% endblock %} -
    diff --git a/components/01-atoms/lists/ol.yml b/components/01-atoms/lists/ol.yml deleted file mode 100644 index b9bd06d..0000000 --- a/components/01-atoms/lists/ol.yml +++ /dev/null @@ -1,6 +0,0 @@ -ol_items: - - content: 'This is the first item in the ordered list.' - - content: 'And here is the item that goes with the label.' - label: 'This is the optional label' - - content: "Here's the third item." - - content: "And here's the last item." diff --git a/components/01-atoms/lists/ul.twig b/components/01-atoms/lists/ul.twig deleted file mode 100644 index af61047..0000000 --- a/components/01-atoms/lists/ul.twig +++ /dev/null @@ -1,26 +0,0 @@ -{# -/** - * Available variables: - * - ul_base_class - the base classname - * - ul_modifiers - array of modifiers to add to the base classname - * - ul_blockname - blockname prepended to the base classname - * - * - listItems - TBD - */ -#} -{% set ul_base_class = ul_base_class|default('ul') %} - -
      - {% block list_content %} - {% for ul_item in ul_items %} - {% include "@atoms/lists/_list-item.twig" with { - list_item_label: ul_item.label, - list_item_content: ul_item.content, - li_base_class: ul_item.li_base_class, - li_base_class: ul_item.li_base_class, - li_modifiers: ul_item.li_modifiers, - li_blockname: ul_item.li_blockname, - } %} - {% endfor %} - {% endblock %} -
    diff --git a/components/01-atoms/lists/ul.yml b/components/01-atoms/lists/ul.yml deleted file mode 100644 index 7604fec..0000000 --- a/components/01-atoms/lists/ul.yml +++ /dev/null @@ -1,6 +0,0 @@ -ul_items: - - content: 'This is the first item in the unordered list.' - - content: 'And here is the item that goes with the label.' - label: 'This is the optional label' - - content: "Here's the third item." - - content: "And here's the last item." diff --git a/components/01-atoms/tables/_tables.scss b/components/01-atoms/tables/_tables.scss deleted file mode 100644 index 615d19c..0000000 --- a/components/01-atoms/tables/_tables.scss +++ /dev/null @@ -1,94 +0,0 @@ -.table { - border: 1px solid clr(highlight-high); - border-radius: 4px; - border-spacing: 0; - background-color: clr(muted); - margin: 1em 0; - width: 100%; -} - -.table__heading-cell, -.table__cell { - border: none; - border-bottom: 1px solid clr(highlight-high); - border-right: 1px solid clr(highlight-high); - padding: 1em; - - &:last-child { - border-right: none; - } -} - -.table__heading-cell { - color: clr(accent-high); - font-weight: 700; - padding: 1.2em; - text-align: left; -} - -.table__row { - &:nth-child(odd) { - background-color: clr(background); - } - - /* Top Row - Non-BEM but always contained */ - &:first-child { - th:first-child, - td:first-child { - border-radius: 5px 0 0; - } - - th:last-child, - td:last-child { - border-radius: 0 5px 0 0; - } - } - - /* Bottom Row - Non-BEM but always contained */ - &:last-child { - tbody & { - th, - td { - border-bottom: none; - } - } - - td:first-child { - border-radius: 0 0 0 5px; - } - - td:last-child { - border-radius: 0 0 5px; - } - } -} - -.table__footer-cell, -.table__footer-cell:first-child { - border-bottom: none; - border-top: 1px solid clr(highlight-high); -} - -/* TODO: BEM selectors should be applied to wysiwyg-created content */ - -.text-long { - table { - @extend .table; - } - - th { - @extend .table__heading-cell; - } - - td { - @extend .table__cell; - } - - tr { - @extend .table__row; - } - - tfoot th { - @extend .table__footer-cell; - } -} diff --git a/components/01-atoms/tables/tables.stories.js b/components/01-atoms/tables/tables.stories.js deleted file mode 100644 index e965ae8..0000000 --- a/components/01-atoms/tables/tables.stories.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import tables from './tables.twig'; - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Tables' }; - -export const table = () => ( -
    -); diff --git a/components/01-atoms/tables/tables.twig b/components/01-atoms/tables/tables.twig deleted file mode 100644 index 3142fb6..0000000 --- a/components/01-atoms/tables/tables.twig +++ /dev/null @@ -1,40 +0,0 @@ -{# -/** - * This is strictly an exmaple file to demonstrate markup in Storybook. - * It is not intended to be included in any other component. - */ -#} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    This is a heading
    Row 1Information about somethingAnd this is important
    Row 2Information about somethingAnd this is important
    Row 3Information about somethingAnd this is important
    Row 4Information about somethingAnd this is important
    This is a table footer
    diff --git a/components/01-atoms/text/headings/_heading.twig b/components/01-atoms/text/headings/_heading.twig deleted file mode 100644 index 4e744d1..0000000 --- a/components/01-atoms/text/headings/_heading.twig +++ /dev/null @@ -1,34 +0,0 @@ -{# -/** - * Available variables: - * - heading_level - the header level 1-6 (produces h1, h2, etc.) - * - * - heading_base_class - the base class - * - heading_modifiers - array of modifiers to add to the base classname - * - heading_blockname - blockname prepended to the base classname - * - * - heading - the content of the heading (typically text) - * - * - heading_url - (optional) the url the heading should poing to - * - heading_link_attributes - key/value attributes to pass to link - * - heading_link_base_class - override the link base class - * - heading_link_modifiers - override the link modifiers - * - heading_link_blockname - override the link block name (defaults to heading_base_class) - */ -#} -{% set heading_base_class = heading_base_class|default('h' ~ heading_level) %} - - - {% if heading_url %} - {% include "@atoms/links/link/link.twig" with { - link_content: heading, - link_url: heading_url, - link_attributes: heading_link_attributes, - link_base_class: heading_link_base_class, - link_modifiers: heading_link_modifiers, - link_blockname: heading_link_blockname|default(heading_base_class), - } %} - {% else %} - {{ heading }} - {% endif %} - diff --git a/components/01-atoms/text/headings/_headings.scss b/components/01-atoms/text/headings/_headings.scss deleted file mode 100644 index 4108c3b..0000000 --- a/components/01-atoms/text/headings/_headings.scss +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Generic header styles: - * All arguments are optional. If not defined, the defaults below will be used -*/ - -$color-heading: clr(accent); - -@mixin heading-xl( - $font-family: $font-heading, - $font-size: 2rem, - $line-height: 1.4, - $color: $color-heading, - $color-link: inherit, - $color-link-hover: inherit, - $font-weight: 700, - $margin: 0 0 0.5em -) { - color: $color; - font-family: $font-family; - font-weight: $font-weight; - font-style: normal; - font-size: $font-size; - line-height: $line-height; - margin: $margin; - width: auto; - - &__link { - color: $color-link; - - &:hover { - color: $color-link-hover; - } - } -} - -@mixin heading-large( - $font-family: $font-heading, - $font-size: 1.8rem, - $line-height: 1.4, - $color: $color-heading, - $color-link: inherit, - $color-link-hover: inherit, - $font-weight: 700, - $margin: 0 0 0.5em -) { - color: $color; - font-family: $font-family; - font-weight: $font-weight; - font-style: normal; - font-size: $font-size; - line-height: $line-height; - margin: $margin; - width: auto; - - &__link { - color: $color-link; - - &:hover { - color: $color-link-hover; - } - } -} - -@mixin heading-medium( - $font-family: $font-heading, - $font-size: 1.4rem, - $line-height: 1.6, - $color: $color-heading, - $color-link: inherit, - $color-link-hover: inherit, - $font-weight: 700, - $margin: 0 0 0.5em -) { - color: $color; - font-family: $font-family; - font-weight: $font-weight; - font-style: normal; - font-size: $font-size; - line-height: $line-height; - margin: $margin; - width: auto; - - &__link { - color: $color-link; - - &:hover { - color: $color-link-hover; - } - } -} - -@mixin heading-small( - $font-family: $font-heading, - $font-size: 1.2rem, - $line-height: 1.6, - $color: $color-heading, - $color-link: inherit, - $color-link-hover: inherit, - $font-weight: 600, - $margin: 0 0 0.5em -) { - color: $color; - font-family: $font-family; - font-weight: $font-weight; - font-style: normal; - font-size: $font-size; - line-height: $line-height; - margin: $margin; - width: auto; - - &__link { - color: $color-link; - - &:hover { - color: $color-link-hover; - } - } -} - -@mixin heading-xs( - $font-family: $font-heading, - $font-size: 1.1rem, - $line-height: 2, - $color: $color-heading, - $color-link: inherit, - $color-link-hover: inherit, - $font-weight: 600, - $margin: 0 0 0.5em -) { - color: $color; - font-family: $font-family; - font-weight: $font-weight; - font-style: normal; - font-size: $font-size; - line-height: $line-height; - margin: $margin; - width: auto; - - &__link { - color: $color-link; - - &:hover { - color: $color-link-hover; - } - } -} - -.h1 { - @include heading-xl; -} - -.h2 { - @include heading-large; -} - -.h3 { - @include heading-medium; -} - -.h4 { - @include heading-small; -} - -.h5, -.h6 { - @include heading-xs; -} - -/* TODO: BEM selectors should be applied to wysiwyg-created content */ - -.text-long { - h1 { - @extend .h1; - } - - h2 { - @extend .h2; - } - - h3 { - @extend .h3; - } - - h4 { - @extend .h4; - } - - h5 { - @extend .h5; - } - - h6 { - @extend .h6; - } -} diff --git a/components/01-atoms/text/headings/headings.twig b/components/01-atoms/text/headings/headings.twig deleted file mode 100644 index 0808151..0000000 --- a/components/01-atoms/text/headings/headings.twig +++ /dev/null @@ -1,11 +0,0 @@ -
    - {% for i in 1..6 %} -
    -

    h{{ i }}

    - This is a Header {{ i }} example -
    - {% endfor %} -
    -{% if meta %} -

    {{ meta.description }}

    -{% endif %} diff --git a/components/01-atoms/text/text.stories.js b/components/01-atoms/text/text.stories.js deleted file mode 100644 index 8f0391b..0000000 --- a/components/01-atoms/text/text.stories.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -import headings from './headings/headings.twig'; -import blockquote from './text/02-blockquote.twig'; -import pre from './text/05-pre.twig'; -import paragraph from './text/03-inline-elements.twig'; - -import blockquoteData from './text/blockquote.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Text' }; - -export const headingsExamples = () => ( -
    -); -export const blockquoteExample = () => ( -
    -); -export const preformatted = () => ( -
    -); -export const random = () => ( -
    -); diff --git a/components/01-atoms/text/text/01-paragraph.twig b/components/01-atoms/text/text/01-paragraph.twig deleted file mode 100644 index c2422c0..0000000 --- a/components/01-atoms/text/text/01-paragraph.twig +++ /dev/null @@ -1,20 +0,0 @@ -{# -/** - * Available variables: - * - paragraph_base_class - the base classname - * - paragraph_modifiers - array of modifiers to add to the base classname - * - paragraph_blockname - blockname prepended to the base classname - * - paragraph_content - the content of the paragraph (typically text) - * - * Available blocks: - * - paragraph_content - used to replace the content of the paragraph with something other than plain text - * for example: A formatted text field in Wordpress - */ -#} -{% set paragraph_base_class = paragraph_base_class|default('paragraph') %} - -

    - {% block paragraph_content %} - {{ paragraph_content }} - {% endblock %} -

    diff --git a/components/01-atoms/text/text/02-blockquote.twig b/components/01-atoms/text/text/02-blockquote.twig deleted file mode 100644 index 4939a4b..0000000 --- a/components/01-atoms/text/text/02-blockquote.twig +++ /dev/null @@ -1,23 +0,0 @@ -{# -/** - * Available variables: - * - blockquote_base_class - the base classname - * - blockquote_modifiers - array of modifiers to add to the base classname - * - blockquote_blockname - blockname prepended to the base classname - * - blockquote_url - the url this blockquote should point to - * - blockquote_content - the content of the blockquote (typically text) - * - * Available blocks: - * - blockquote_content - used to replace the content of the blockquote with something other than text - * for example: A formatted text field in Wordpress - */ -#} -{% set blockquote_base_class = blockquote_base_class|default('blockquote') %} - -
    - {% block blockquote_content %} - {{ blockquote_content }} - {% endblock %} -
    diff --git a/components/01-atoms/text/text/03-inline-elements.twig b/components/01-atoms/text/text/03-inline-elements.twig deleted file mode 100644 index 8c10017..0000000 --- a/components/01-atoms/text/text/03-inline-elements.twig +++ /dev/null @@ -1,45 +0,0 @@ -{# -/** - * This is strictly an exmaple file to demonstrate markup in Storybook. - * It is not intended to be included in any other component. - */ -#} -
    -

    Strong is used to indicate strong importance

    - -

    This text has added emphasis

    - -

    The b element is stylistically different text from normal text, without any special importance

    - -

    The i element is text that is set off from the normal text

    - -

    The u element is text with an unarticulated, though explicitly rendered, non-textual annotation

    - -

    This text is deleted and This text is inserted

    - -

    This text has a strikethrough

    - -

    Superscript®

    - -

    Subscript for things like H2O

    - -

    This text is small for fine print, etc.

    - -

    Abbreviation: HTML

    - -

    Keybord input: Cmd

    - -

    This text is a short inline quotation

    - -

    This is a citation

    - -

    The dfn element indicates a definition.

    - -

    The mark element indicates a highlight

    - -

    This is what inline code looks like.

    - -

    This is sample output from a computer program

    - -

    The variarble element, such as x = y

    -
    diff --git a/components/01-atoms/text/text/05-pre.twig b/components/01-atoms/text/text/05-pre.twig deleted file mode 100644 index cdebd17..0000000 --- a/components/01-atoms/text/text/05-pre.twig +++ /dev/null @@ -1,15 +0,0 @@ -{# -/** - * This is strictly an exmaple file to demonstrate markup in Storybook. - * It is not intended to be included in any other component. - */ -#} -
    -P R E F O R M A T T E D T E X T
    -! " # $ % & ' ( ) * + , - . /
    -0 1 2 3 4 5 6 7 8 9 : ; < = > ?
    -@ A B C D E F G H I J K L M N O
    -P Q R S T U V W X Y Z [ \ ] ^ _
    -` a b c d e f g h i j k l m n o
    -p q r s t u v w x y z { | } ~
    -
    diff --git a/components/01-atoms/text/text/06-hr.twig b/components/01-atoms/text/text/06-hr.twig deleted file mode 100644 index 50e14d6..0000000 --- a/components/01-atoms/text/text/06-hr.twig +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/components/01-atoms/text/text/_text.scss b/components/01-atoms/text/text/_text.scss deleted file mode 100644 index a4bdda7..0000000 --- a/components/01-atoms/text/text/_text.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* Create a mixin for paragraph styles that can be implemented - * in components with other classnames. -*/ -@mixin paragraph($margin: 0 0 1em) { - margin: $margin; -} - -.paragraph { - @include paragraph; -} - -.blockquote { - font-style: italic; - border-left: solid 3px clr(accent); - margin-left: 1em; - padding-left: 1em; -} - -.hr { - border-style: solid; - border-width: 1px 0 0; - color: currentColor; -} - -/* TODO: BEM selectors should be applied to wysiwyg-created content */ - -.text-long { - p { - @extend .paragraph; - } - - blockquote { - @extend .blockquote; - } - - hr { - @extend .hr; - } -} diff --git a/components/01-atoms/text/text/blockquote.yml b/components/01-atoms/text/text/blockquote.yml deleted file mode 100644 index 4fcc874..0000000 --- a/components/01-atoms/text/text/blockquote.yml +++ /dev/null @@ -1 +0,0 @@ -blockquote_content: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.' diff --git a/components/01-atoms/video/_video.scss b/components/01-atoms/video/_video.scss deleted file mode 100644 index 11da429..0000000 --- a/components/01-atoms/video/_video.scss +++ /dev/null @@ -1,20 +0,0 @@ -/* Responsive Video using CSS only */ -.video { - height: 0; - overflow: hidden; - padding-top: 35px; - padding-bottom: 56.25%; /* 56.25% = 16x9 */ - position: relative; - - iframe { - height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; - } -} - -.video--full { - padding-bottom: 75%; /* 75% = 4x3 */ -} diff --git a/components/01-atoms/video/video-full.yml b/components/01-atoms/video/video-full.yml deleted file mode 100644 index 183f2d9..0000000 --- a/components/01-atoms/video/video-full.yml +++ /dev/null @@ -1,3 +0,0 @@ -video_content: "" -video_modifiers: - - 'full' diff --git a/components/01-atoms/video/video.twig b/components/01-atoms/video/video.twig deleted file mode 100644 index 1c46b42..0000000 --- a/components/01-atoms/video/video.twig +++ /dev/null @@ -1,23 +0,0 @@ -{# -/** - * Available variables: - * - video_base_class - the base classname - * - video_modifiers - array of modifiers to add to the base classname - * by default, videos are expected to be 16x9, include the modifier "full" to support a 4x3 video - * - video_blockname - blockname prepended to the base classname - * - video_content - the content of the video (typically an iframe) - * the "|raw" filter is applied so that the iframe is rendered instead - * of simply passing the iframe as plain text. - * - * Available blocks: - * - video_content - used to replace the content of the video with something other than the typical iframe - * for example: to insert an html5 video component - */ -#} -{% set video_base_class = video_base_class|default('video') %} - -
    - {% block video_content %} - {{ video_content|raw }} - {% endblock %} -
    diff --git a/components/01-atoms/video/video.yml b/components/01-atoms/video/video.yml deleted file mode 100644 index bfd7474..0000000 --- a/components/01-atoms/video/video.yml +++ /dev/null @@ -1 +0,0 @@ -video_content: "" diff --git a/components/01-atoms/video/videos.stories.js b/components/01-atoms/video/videos.stories.js deleted file mode 100644 index 52c2a16..0000000 --- a/components/01-atoms/video/videos.stories.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -import video from './video.twig'; - -import videoData from './video.yml'; -import videoFullData from './video-full.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Atoms/Videos' }; - -export const wide = () => ( -
    -); -export const full = () => ( -
    -); diff --git a/components/02-molecules/card/_card.scss b/components/02-molecules/card/_card.scss deleted file mode 100644 index 640b996..0000000 --- a/components/02-molecules/card/_card.scss +++ /dev/null @@ -1,35 +0,0 @@ -.card__heading { - @include heading-large($font-size: 1.3rem); - @include no-bottom; - - &-link { - @include link; - } -} - -.card__subheading { - @include heading-medium($font-size: 1rem); - @include no-bottom; - - color: clr(text); -} - -.card__body { - margin: 0.7em 0 1em; -} - -.card__link { - @include link; -} - -.card__button { - @include button-base; - @include button-color-primary; - @include button-medium; -} - -/* Variations */ -.card--bg { - background-color: clr(highlight-high); - padding: 1em; -} diff --git a/components/02-molecules/card/card-bg.yml b/components/02-molecules/card/card-bg.yml deleted file mode 100644 index b31cda2..0000000 --- a/components/02-molecules/card/card-bg.yml +++ /dev/null @@ -1,2 +0,0 @@ -card__modifiers: - - 'bg' diff --git a/components/02-molecules/card/card.twig b/components/02-molecules/card/card.twig deleted file mode 100644 index 9c98ff7..0000000 --- a/components/02-molecules/card/card.twig +++ /dev/null @@ -1,115 +0,0 @@ -{# - # Available variables: - # - card__base_class - base classname of the wrapper. - # - card__modifiers - array of modifiers to add to the base classname of the wrapper. - # - card__blockname - blockname prepended to the base classname of the wrapper(s) and each component. - # - # - card__image__src - the actual image file location. - # - card__image__alt - (optional) the alt text for screen readers and when the image cannot load. - # - card__image__output_image_tag - whether to print the picture element or not. - # - # - card__content__base_class - base classname of the copy wrapper - defaults to 'heading'. - # - card__content__modifiers - array of modifiers to add to the base classname of the copy wrapper. - # - card__content__blockname - blockname prepended to the base classname of the copy wrapper. - # - # - card__heading - the content of the title component. - # - card__heading__link - (optional) the url the title should link to. Defaults to card__link__url. - # - card__heading__base_class - base classname of the title component. Defaults to "title". - # - card__heading__blockname - blockname to add to the base classname of the title - defaults to card__base_class. - # - card__heading__modifiers - array of modifiers to add to the base classname of the title. - # - card__heading__link_base_class - base class to add to the title link - defaults to 'title-link'. - # - # - card__subheading - the content of the subtitle component. - # - card__subheading__link - (optional) the url the subtitle should link to. - # - card__subheading__base_class - base classname of the subtitle component. Defaults to "subtitle". - # - card__subheading__modifiers - array of modifiers to add to the base classname of the subtitle. - # - # - card__body - the content of the body component. - # - card__body__base_class - base classname of the body component. Defaults to "body". - # - card__body__modifiers - array of modifiers to add to the base classname of the body. - # - # - card__link__text - the content of the link component. - # - card__link__url - the url the link should link to. - # - card__link__base_class - base classname of the link component. Defaults to "link". - # - card__link__blockname - override link blockname. Defaults to card__base_class. - # - card__link__attributes - array of attribute,value pairs for the link attribute. - # - card__link__modifiers - array of modifiers to add to the base classname of the link. - # - # - card__button__content - the content of the button component. - # - card__button__url - the url the button should link to. - # - card__button__base_class - base classname of the button component. Defaults to "button". - # - card__button__attributes - array of attribute,value pairs for the button attribute. - # - card__button__modifiers - array of modifiers to add to the base classname of the button. - #} -{% set card__base_class = 'card' %} - -
    - {# Image #} - {% block card__img %} - {% if card__image__src %} - {% include "@atoms/images/image/responsive-image.twig" with { - image_blockname: card__base_class, - output_image_tag: card__image__output_image_tag, - image_src: card__image__src, - image_alt: card__image__alt, - } %} - {% endif %} - {% endblock %} - {# Content #} -
    - {# Heading #} - {% if card__heading %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_base_class: card__heading__base_class|default('heading'), - heading_modifiers: card__heading__modifiers, - heading_blockname: card__base_class, - heading_level: 2, - heading: card__heading, - heading_url: card__heading__link|default(card__link__url), - heading_link_base_class: card__heading__link_base_class|default('heading-link'), - heading_link_blockname: card__base_class, - } %} - {% endif %} - {# Subheading #} - {% if card__subheading %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_base_class: card__subheading__base_class|default('subheading'), - heading_modifiers: card__subheading__modifiers, - heading_blockname: card__base_class, - heading_level: 3, - heading: card__subheading, - heading_url: card__subheading__link, - } %} - {% endif %} - {# Body #} - {% if card__body %} - {% include "@atoms/text/text/01-paragraph.twig" with { - paragraph_base_class: card__body__base_class|default('body'), - paragraph_modifiers: card__body__modifiers, - paragraph_blockname: card__base_class, - paragraph_content: card__body, - } %} - {% endif %} - {# Link #} - {% if card__link__url %} - {% include "@atoms/links/link/link.twig" with { - link_base_class: card__link__base_class|default('link'), - link_blockname: card__base_class, - link_attributes: card__link__attributes, - link_content: card__link__text, - link_url: card__link__url, - } %} - {% endif %} - {# Button #} - {% if card__button__url %} - {% include "@atoms/buttons/twig/button.twig" with { - button_base_class: card__button__base_class|default('button'), - button_modifiers: card__button__modifiers, - button_blockname: card__base_class, - button_attributes: card__button__attributes, - button_content: card__button__content, - button_url: card__button__url, - } %} - {% endif %} -
    -
    diff --git a/components/02-molecules/card/card.yml b/components/02-molecules/card/card.yml deleted file mode 100644 index 02a5467..0000000 --- a/components/02-molecules/card/card.yml +++ /dev/null @@ -1,8 +0,0 @@ -card__image__src: 'https://placeimg.com/1280/720/nature' -card__image__alt: "This is the card image's alt text" -card__image__output_image_tag: true -card__heading: 'This is the card heading' -card__subheading: 'Curabitur non nulla sit amet nisl tempus convallis quis ac lectus' -card__body: 'ellentesque in ipsum id orci porta dapibus. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a.' -card__link__text: 'Read more about this card' -card__link__url: '#' diff --git a/components/02-molecules/card/cards.stories.js b/components/02-molecules/card/cards.stories.js deleted file mode 100644 index f517eb6..0000000 --- a/components/02-molecules/card/cards.stories.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import card from './card.twig'; - -import cardData from './card.yml'; -import cardBgData from './card-bg.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Molecules/Cards' }; - -export const cardExample = () => ( -
    -); -export const cardWithBackground = () => ( -
    -); diff --git a/components/02-molecules/cta/_cta.scss b/components/02-molecules/cta/_cta.scss deleted file mode 100644 index 04fe925..0000000 --- a/components/02-molecules/cta/_cta.scss +++ /dev/null @@ -1,6 +0,0 @@ -.cta { - background-color: clr(background-section); - padding: $space-triple; - text-align: center; - text-transform: uppercase; -} diff --git a/components/02-molecules/cta/cta.twig b/components/02-molecules/cta/cta.twig deleted file mode 100644 index c14a5ba..0000000 --- a/components/02-molecules/cta/cta.twig +++ /dev/null @@ -1,7 +0,0 @@ -{% set cta__base_class = 'cta' %} -
    -

    {{ cta__heading }}

    - {% include "@atoms/buttons/twig/button.twig" with { - button_content: cta__button_text, - } %} -
    diff --git a/components/02-molecules/cta/cta.yml b/components/02-molecules/cta/cta.yml deleted file mode 100644 index 0a5d77c..0000000 --- a/components/02-molecules/cta/cta.yml +++ /dev/null @@ -1,2 +0,0 @@ -cta__heading: 'This is a reason to act' -cta__button_text: 'Click here' diff --git a/components/02-molecules/cta/ctas.stories.js b/components/02-molecules/cta/ctas.stories.js deleted file mode 100644 index 3f9fbd4..0000000 --- a/components/02-molecules/cta/ctas.stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import cta from './cta.twig'; - -import ctaData from './cta.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Molecules/CTA' }; - -export const ctaExample = () => ( -
    -); diff --git a/components/02-molecules/menus/_menu-item.twig b/components/02-molecules/menus/_menu-item.twig deleted file mode 100644 index 6a350dc..0000000 --- a/components/02-molecules/menus/_menu-item.twig +++ /dev/null @@ -1,37 +0,0 @@ -{% if not item_modifiers %} - {% set item_modifiers = [] %} -{% endif %} -{# Pass original item modifiers down to children #} -{% set original_item_modifiers = item_modifiers %} -{% if item.in_active_trail == TRUE %} - {% set item_modifiers = item_modifiers|merge(['active']) %} -{% endif %} -{% if menu_level > 0 %} - {% set item_modifiers = item_modifiers|merge(['sub', 'sub-' ~ menu_level]) %} -{% endif %} -{% if item.children %} - {% set item_modifiers = item_modifiers|merge(['with-sub']) %} -{% endif %} -{# below could maybe be done without a loop? #} -{% for modifier in item.modifiers %} - {% set item_modifiers = item_modifiers|merge([modifier]) %} -{% endfor %} - -{% set list_item_label = item_label %} -{% set li_base_class = item_base_class|default(menu_class ~ '__item') %} -{% set li_modifiers = item_modifiers %} -{% set li_blockname = item_blockname %} - -{% extends "@atoms/lists/_list-item.twig" %} - {% block list_item_content %} - {% include "@atoms/links/link/link.twig" with { - link_content: item.title, - link_url: item.url, - link_base_class: item_base_class|default(menu_class ~ '__link'), - link_modifiers: item_modifiers, - } %} - {% if item.children %} - - {{ menus.menu_links(item.children, attributes, menu_level + 1, menu_class, menu_modifiers, menu_blockname, item_base_class, original_item_modifiers, item_blockname, directory) }} - {% endif %} - {% endblock %} diff --git a/components/02-molecules/menus/_menu-list.twig b/components/02-molecules/menus/_menu-list.twig deleted file mode 100644 index 359845a..0000000 --- a/components/02-molecules/menus/_menu-list.twig +++ /dev/null @@ -1,15 +0,0 @@ -{% set ul_base_class = menu_class %} -{% set ul_modifiers = menu_modifiers %} -{% set ul_blockname = menu_blockname %} - -{# List #} -{% extends "@atoms/lists/ul.twig" %} - {% block list_content %} - {% for item in items %} - {% include "@molecules/menus/_menu-item.twig" with { - li_base_class: item_base_class, - li_modifiers: item_modifiers, - li_blockname: item_blockname, - } %} - {% endfor %} - {% endblock %} diff --git a/components/02-molecules/menus/_menu.twig b/components/02-molecules/menus/_menu.twig deleted file mode 100644 index e7d2bc4..0000000 --- a/components/02-molecules/menus/_menu.twig +++ /dev/null @@ -1,46 +0,0 @@ -{# -/** - * @file - * Theme override to display a menu. - * - * Available variables: - * - menu_name: The machine name of the menu. - * - items: A nested list of menu items. Each menu item contains: - * - attributes: HTML attributes for the menu item. - * - below: The menu item child items. - * - title: The menu link title. - * - url: The menu link url - * - localized_options: Menu link localized options. - * - is_expanded: TRUE if the link has visible children within the current - * menu tree. - * - is_collapsed: TRUE if the link has children within the current menu tree - * that are not currently visible. - * - in_active_trail: TRUE if the link is in the active trail. - */ -#} - -{# - We call a macro which calls itself to render the full tree. - @see http://twig.sensiolabs.org/doc/tags/macro.html -#} - -{% macro menu_links(items, attributes, menu_level, menu_class, menu_modifiers, menu_blockname, item_base_class, item_modifiers, item_blockname, directory) %} - {% import _self as menus %} - {% if items %} - - {# Set classes #} - {% set menu_class = menu_class|default('menu') %} - {% if not menu_modifiers %} - {% set menu_modifiers = [] %} - {% endif %} - {% if menu_level > 0 %} - {% set menu_modifiers = menu_modifiers|merge(['sub', 'sub-' ~ menu_level]) %} - {% endif %} - - {% include "@molecules/menus/_menu-list.twig" %} - {% endif %} -{% endmacro %} - -{% import _self as menus %} - -{{ menus.menu_links(items, attributes, 0, menu_class, menu_modifiers, menu_blockname, item_base_class, item_modifiers, item_blockname, directory) }} diff --git a/components/02-molecules/menus/breadcrumbs/_breadcrumbs.scss b/components/02-molecules/menus/breadcrumbs/_breadcrumbs.scss deleted file mode 100644 index 97a710e..0000000 --- a/components/02-molecules/menus/breadcrumbs/_breadcrumbs.scss +++ /dev/null @@ -1,41 +0,0 @@ -$color-breadcrumb: clr(accent); -$color-breadcrumb-hover: clr(accent-high); -$color-breadcrumb-active: clr(text); - -.breadcrumb { - @include list-reset; -} - -.breadcrumb__item, -.breadcrumb__link, -.breadcrumb__link:link, -.breadcrumb__link:visited, -.breadcrumb__link:focus { - color: $color-breadcrumb; - text-decoration: none; -} - -.breadcrumb__link:hover { - color: $color-breadcrumb-hover; -} - -.breadcrumb__item { - display: inline-block; - font-size: 0.75rem; - - &::after { - content: '/'; - } - - &:last-child { - color: $color-breadcrumb-active; - - &::after { - content: ''; - } - } -} - -.breadcrumb_link:active { - color: $color-breadcrumb-active; -} diff --git a/components/02-molecules/menus/breadcrumbs/breadcrumbs.twig b/components/02-molecules/menus/breadcrumbs/breadcrumbs.twig deleted file mode 100644 index 05a3ce6..0000000 --- a/components/02-molecules/menus/breadcrumbs/breadcrumbs.twig +++ /dev/null @@ -1,27 +0,0 @@ -{# -/** - * @file - * Theme override for a breadcrumb trail. - * - * Available variables: - * - breadcrumb: Breadcrumb trail items. - */ -#} -{% set breadcrumb__base_class = 'breadcrumb' %} - -{% if breadcrumb %} - -{% endif %} diff --git a/components/02-molecules/menus/breadcrumbs/breadcrumbs.yml b/components/02-molecules/menus/breadcrumbs/breadcrumbs.yml deleted file mode 100644 index 05875dc..0000000 --- a/components/02-molecules/menus/breadcrumbs/breadcrumbs.yml +++ /dev/null @@ -1,6 +0,0 @@ -breadcrumb: - - url: '#' - text: 'Home' - - url: '#' - text: 'Parent Page' - - text: 'Current Page' diff --git a/components/02-molecules/menus/inline/_inline-menu.scss b/components/02-molecules/menus/inline/_inline-menu.scss deleted file mode 100644 index 79e0a96..0000000 --- a/components/02-molecules/menus/inline/_inline-menu.scss +++ /dev/null @@ -1,26 +0,0 @@ -.inline-menu { - @include list-reset; -} - -.inline-menu__item { - margin: 0 1em 0.5em 0; - - @include large { - display: inline; - margin-bottom: 1em; - } - - &:last-child { - margin-right: 0; - } -} - -.inline-menu__link { - @include link; - - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 1.5px; - text-decoration: none; - text-transform: uppercase; -} diff --git a/components/02-molecules/menus/inline/inline-menu.twig b/components/02-molecules/menus/inline/inline-menu.twig deleted file mode 100644 index d39cb66..0000000 --- a/components/02-molecules/menus/inline/inline-menu.twig +++ /dev/null @@ -1,4 +0,0 @@ -{% include "@molecules/menus/_menu.twig" with { - menu_class: 'inline-menu', - items: inline_menu_items, -} %} diff --git a/components/02-molecules/menus/inline/inline-menu.yml b/components/02-molecules/menus/inline/inline-menu.yml deleted file mode 100644 index 45382e7..0000000 --- a/components/02-molecules/menus/inline/inline-menu.yml +++ /dev/null @@ -1,7 +0,0 @@ -inline_menu_items: - - title: 'Test' - url: '#' - - title: 'Number 2' - url: '#' - - title: 'Item Number 3' - url: '#' diff --git a/components/02-molecules/menus/main-menu/_00-main-menu.scss b/components/02-molecules/menus/main-menu/_00-main-menu.scss deleted file mode 100644 index a380585..0000000 --- a/components/02-molecules/menus/main-menu/_00-main-menu.scss +++ /dev/null @@ -1,93 +0,0 @@ -/* Menu media */ -$main-menu-medium: $medium; - -/* Nav */ -.main-nav { - display: none; - - @include medium { - display: block; - } - - &--open { - background-color: clr(background); - display: block; - left: 0; - overflow-y: scroll; - position: absolute; - top: 134px; - right: 0; - width: 100%; - } -} - -/* UL */ -.main-menu { - @include list-reset; - - border-bottom: 1px solid; - position: relative; - z-index: 10; - - @include medium { - border-bottom: none; - } -} - -/* - * Sub Navigation - */ - -/* UL (Nested) */ -.main-menu--sub { - border-bottom: none; - height: 0; - overflow: hidden; - background-color: clr(muted); - width: 100%; - - @include medium { - background-color: clr(highlight-high); - display: none; - height: auto; - left: 0; - overflow: visible; - padding: $space 0; - position: absolute; - top: 55px; - width: 315px; - z-index: 1; - font-size: 0.9rem; - opacity: 0.6; - } - - &:hover { - @include medium { - opacity: 1; - background-color: clr(accent-high); - transition: all 0.2s; - - .main-menu__link--sub { - color: clr(highlight); - - &.active, - &:active, - &:hover { - color: clr(background); - background-color: clr(accent-high); - } - } - } - } - - /* See main-menu.js */ - &-open { - height: auto; - } -} - -.main-menu--sub-2 { - @include medium { - display: none; /* Never shown on large screens */ - } -} diff --git a/components/02-molecules/menus/main-menu/_01-main-menu-item.scss b/components/02-molecules/menus/main-menu/_01-main-menu-item.scss deleted file mode 100644 index 8921d6f..0000000 --- a/components/02-molecules/menus/main-menu/_01-main-menu-item.scss +++ /dev/null @@ -1,85 +0,0 @@ -/* LI */ -.main-menu__item { - border-top: 1px solid; - display: flex; - flex-wrap: wrap; - position: relative; - - @include medium { - border-top: none; - display: inline-block; - - /* Only top level */ - &:not(.main-menu__item--sub):hover { - background-color: clr(accent-high); - transition: all 0.2s; - - & > .main-menu--sub { - display: block; - } - } - } - - &:hover { - .main-menu__link::after { - color: clr(text); - } - } -} - -/* Expand Button */ -.expand-sub { - background-color: clr(accent-high); - cursor: pointer; - display: block; - width: 20%; - color: $white; - position: relative; - - @include medium { - display: none; - } - - /* Down triangle */ - &::after { - border: 15px solid; - border-bottom-color: transparent; - border-left: 12px solid transparent; - border-right: 12px solid transparent; - content: ''; - display: block; - margin-right: -12px; - margin-top: -6px; - position: absolute; - right: 50%; - top: 50%; - width: 0; - } - - /* See main-menu.js */ - &--open { - background-color: clr(accent-high); - color: clr(muted); - - &::after { - border-top-color: transparent; - border-bottom: 15px solid; - margin-top: -20px; - } - } -} - -/* - * Sub Navigation - */ - -/* LI (Nested) */ -.main-menu__item--sub { - @include medium { - display: block; - - &:not(:first-child) { - border-top: 1px solid clr(highlight); - } - } -} diff --git a/components/02-molecules/menus/main-menu/_02-main-menu-link.scss b/components/02-molecules/menus/main-menu/_02-main-menu-link.scss deleted file mode 100644 index 03c5512..0000000 --- a/components/02-molecules/menus/main-menu/_02-main-menu-link.scss +++ /dev/null @@ -1,74 +0,0 @@ -/* A */ -.main-menu__link { - color: clr(accent); - display: block; - font-size: 1.2rem; - line-height: 1.5; - padding: $space $space-triple $space $space-double; - text-decoration: none; - font-weight: bold; - - &--with-sub { - width: 80%; - } - - @include medium { - border-bottom: none; - display: inline-block; - font-size: 1.1rem; - padding: $space; - text-transform: none; - position: relative; - width: auto; - - &.active, - &:active, - &:hover { - color: clr(highlight-high); - } - - &::after { - color: $white; - display: inline-block; - content: '>'; - margin-left: 0.5rem; - } - - &--sub::after { - display: none; - } - - &:hover { - color: clr(background); - - &::after { - color: clr(highlight); - } - } - } -} - -/* - * Sub Navigation Links - */ - -/* A (Nested) */ -.main-menu__link--sub { - color: clr(accent); - padding-left: $space-triple; - font-weight: normal; - - @include medium { - color: clr(highlight); - display: block; - padding: $space-one-third $space; - } -} - -.main-menu--sub-2 { - background-color: clr(highlight-high); -} - -.main-menu__link--sub-2 { - padding-left: $space-quadruple; -} diff --git a/components/02-molecules/menus/main-menu/_03-main-menu-toggle.scss b/components/02-molecules/menus/main-menu/_03-main-menu-toggle.scss deleted file mode 100644 index f870cc0..0000000 --- a/components/02-molecules/menus/main-menu/_03-main-menu-toggle.scss +++ /dev/null @@ -1,61 +0,0 @@ -/* Toggle */ -.toggle-expand { - @include link; - - @include medium { - display: none; - } - - display: inline-block; - padding: $space; - text-decoration: none; - width: 100%; - - /* See main-menu.js */ - &--open { - background-color: clr(accent); - } -} - -.toggle-expand__text { - display: block; - font-size: 0.9rem; - font-weight: bold; - - .toggle-expand--open & { - color: clr(background); - } -} - -/* SVG icon */ -.toggle-expand__icon { - height: 3rem; - width: 3rem; - margin: 0 auto; -} - -.toggle-expand__open { - display: inline-block; - text-align: center; - - .toggle-expand--open & { - display: none; - } -} - -.toggle-expand__close { - display: none; - text-align: center; - - &::before { - color: clr(background); - content: 'X'; - display: block; - font-size: 2.5rem; - line-height: 2; - } - - .toggle-expand--open & { - display: inline-block; - } -} diff --git a/components/02-molecules/menus/main-menu/main-menu.js b/components/02-molecules/menus/main-menu/main-menu.js deleted file mode 100644 index 069f9a0..0000000 --- a/components/02-molecules/menus/main-menu/main-menu.js +++ /dev/null @@ -1,27 +0,0 @@ -Attach.behaviors.mainMenu = { - attach(context) { - const toggleExpand = context.getElementById('toggle-expand'); - const menu = context.getElementById('main-nav'); - if (menu) { - const expandMenu = menu.getElementsByClassName('expand-sub'); - - // Mobile Menu Show/Hide. - toggleExpand.addEventListener('click', (e) => { - toggleExpand.classList.toggle('toggle-expand--open'); - menu.classList.toggle('main-nav--open'); - e.preventDefault(); - }); - - // Expose mobile sub menu on click. - for (let i = 0; i < expandMenu.length; i += 1) { - expandMenu[i].addEventListener('click', (e) => { - const menuItem = e.currentTarget; - const subMenu = menuItem.nextElementSibling; - - menuItem.classList.toggle('expand-sub--open'); - subMenu.classList.toggle('main-menu--sub-open'); - }); - } - } - }, -}; diff --git a/components/02-molecules/menus/main-menu/main-menu.twig b/components/02-molecules/menus/main-menu/main-menu.twig deleted file mode 100644 index fb9c53a..0000000 --- a/components/02-molecules/menus/main-menu/main-menu.twig +++ /dev/null @@ -1,25 +0,0 @@ -{# {{ attach_library('emulsify/main-menu') }} #} - -{% if menu %} - -{% endif %} diff --git a/components/02-molecules/menus/main-menu/main-menu.yml b/components/02-molecules/menus/main-menu/main-menu.yml deleted file mode 100644 index d4e3938..0000000 --- a/components/02-molecules/menus/main-menu/main-menu.yml +++ /dev/null @@ -1,38 +0,0 @@ -menu: - get_items: - - title: 'Menu Item 1' - url: '#' - children: - - title: 'Menu Item 1 Sub 1' - url: '#' - - title: 'Menu Item 1 Sub 2' - url: '#' - children: - - title: 'Menu Item 1 Sub Sub 1' - url: '#' - - title: 'Menu Item 1 Sub Sub 2' - url: '#' - - title: 'Menu Item 1 Sub 3' - url: '#' - - title: 'Menu Item 1 Sub 4' - url: '#' - - title: 'Menu Item 2' - url: '#' - children: - - title: 'Menu Item 2 Sub 1' - url: '#' - - title: 'Menu Item 2 Sub 2' - url: '#' - - title: 'Menu Item 2 Sub 3' - url: '#' - - title: 'Menu Item 3' - url: '#' - children: - - title: 'Menu Item 3 Sub 1' - url: '#' - - title: 'Menu Item 3 Sub 2' - url: '#' - - title: 'Menu Item 3 Sub 3' - url: '#' - - title: 'Menu Item 3 Sub 4' - url: '#' diff --git a/components/02-molecules/menus/menus.stories.js b/components/02-molecules/menus/menus.stories.js deleted file mode 100644 index 33f8360..0000000 --- a/components/02-molecules/menus/menus.stories.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { useEffect } from '@storybook/client-api'; - -import breadcrumb from './breadcrumbs/breadcrumbs.twig'; -import inlineMenu from './inline/inline-menu.twig'; -import mainMenu from './main-menu/main-menu.twig'; -import socialMenu from './social/social-menu.twig'; - -import breadcrumbsData from './breadcrumbs/breadcrumbs.yml'; -import inlineMenuData from './inline/inline-menu.yml'; -import mainMenuData from './main-menu/main-menu.yml'; -import socialMenuData from './social/social-menu.yml'; - -import './main-menu/main-menu'; - -/** - * Storybook Definition. - */ -export default { title: 'Molecules/Menus' }; - -export const breadcrumbs = () => ( -
    -); -export const inline = () => ( -
    -); -export const main = () => { - useEffect(() => Attach.attachBehaviors(), []); - return
    ; -}; -export const social = () => ( -
    -); diff --git a/components/02-molecules/menus/social/_social-menu-item.twig b/components/02-molecules/menus/social/_social-menu-item.twig deleted file mode 100644 index d8b693e..0000000 --- a/components/02-molecules/menus/social/_social-menu-item.twig +++ /dev/null @@ -1,8 +0,0 @@ -{% set li_base_class = 'item' %} -{% set li_modifiers = item_modifiers %} -{% set li_blockname = menu_class %} - -{% extends "@atoms/lists/_list-item.twig" %} - {% block list_item_content %} - {% include "@molecules/menus/social/_social-menu-link.twig" %} - {% endblock %} diff --git a/components/02-molecules/menus/social/_social-menu-link.twig b/components/02-molecules/menus/social/_social-menu-link.twig deleted file mode 100644 index e2e73a4..0000000 --- a/components/02-molecules/menus/social/_social-menu-link.twig +++ /dev/null @@ -1,13 +0,0 @@ -{% set link_url = item.url %} -{% set link_base_class = 'link' %} -{% set link_modifiers = item_modifiers %} -{% set link_blockname = menu_class %} - -{% extends "@atoms/links/link/link.twig" %} - {% block link_content %} - {% include "@atoms/images/icons/_icon.twig" with { - icon_name: item.icon, - icon_blockname: menu_class, - } %} - {{ item.title }} - {% endblock %} diff --git a/components/02-molecules/menus/social/_social-menu.scss b/components/02-molecules/menus/social/_social-menu.scss deleted file mode 100644 index ada188c..0000000 --- a/components/02-molecules/menus/social/_social-menu.scss +++ /dev/null @@ -1,26 +0,0 @@ -.social-menu { - @include list-reset; - - &__item { - margin-bottom: $space-one-fourth; - } - - &__link { - display: flex; - flex-flow: row nowrap; - align-items: center; - text-decoration: none; - color: clr(text); - } - - &__icon { - height: 2rem; - width: 2rem; - margin-right: $space-one-half; - fill: currentColor; - } - - &__text { - font-weight: bold; - } -} diff --git a/components/02-molecules/menus/social/social-menu.twig b/components/02-molecules/menus/social/social-menu.twig deleted file mode 100644 index 1facdfa..0000000 --- a/components/02-molecules/menus/social/social-menu.twig +++ /dev/null @@ -1,13 +0,0 @@ -{# Set classes #} -{% set menu_class = 'social-menu' %} -{% set ul_base_class = menu_class %} -{% set ul_modifiers = menu_modifiers %} -{% set ul_blockname = menu_blockname %} - -{# List #} -{% extends "@atoms/lists/ul.twig" %} - {% block list_content %} - {% for item in social_menu_items %} - {% include "@molecules/menus/social/_social-menu-item.twig" %} - {% endfor %} - {% endblock %} diff --git a/components/02-molecules/menus/social/social-menu.yml b/components/02-molecules/menus/social/social-menu.yml deleted file mode 100644 index 1976564..0000000 --- a/components/02-molecules/menus/social/social-menu.yml +++ /dev/null @@ -1,10 +0,0 @@ -social_menu_items: - - title: 'Twitter' - url: '#' - icon: 'twitter' - - title: 'Facebook' - url: '#' - icon: 'facebook' - - title: 'Instagram' - url: '#' - icon: 'instagram' diff --git a/components/02-molecules/pager/_pager.scss b/components/02-molecules/pager/_pager.scss deleted file mode 100644 index 0acd361..0000000 --- a/components/02-molecules/pager/_pager.scss +++ /dev/null @@ -1,75 +0,0 @@ -.pager { - margin: 2em 0; -} - -.pager__items { - @include list-reset; - - text-align: center; -} - -.pager__item { - display: inline-block; - margin: 0 0.7em; - - @include large { - margin: 0 1em; - } -} - -.pager__link, -.pager__link:link, -.pager__link:visited { - color: clr(text); - font-weight: 600; - text-decoration: none; - - &.is-active, - &:hover { - color: clr(accent); - } -} - -.pager__link--current { - color: $black; -} - -.pager__link--next, -.pager__link--prev { - display: block; - - span { - display: none; - } - - &::before { - border: 8px solid clr(text); - border-bottom: 6px solid transparent; - border-right: 7px solid transparent; - border-top: 6px solid transparent; - content: ''; - display: block; - position: relative; - top: 1px; - } - - &:hover { - &::before { - border-left-color: clr(accent); - } - } -} - -.pager__link--prev { - &::before { - border-left-color: transparent; - border-right-color: clr(text); - } - - &:hover { - &::before { - border-left-color: transparent; - border-right-color: clr(accent); - } - } -} diff --git a/components/02-molecules/pager/pager-both.yml b/components/02-molecules/pager/pager-both.yml deleted file mode 100644 index e9781b5..0000000 --- a/components/02-molecules/pager/pager-both.yml +++ /dev/null @@ -1,16 +0,0 @@ -posts: - pagination: - prev: - - link: '#' - next: - - link: '#' - pages: - - link: '#' - title: "1" - - link: '#' - title: "2" - - current: 1 - title: "3" - - link: '#' - title: "4" - diff --git a/components/02-molecules/pager/pager-prev.yml b/components/02-molecules/pager/pager-prev.yml deleted file mode 100644 index d50e2bb..0000000 --- a/components/02-molecules/pager/pager-prev.yml +++ /dev/null @@ -1,13 +0,0 @@ -posts: - pagination: - prev: - - link: '#' - pages: - - link: '#' - title: "1" - - link: '#' - title: "2" - - link: '#' - title: "3" - - current: 1 - title: "4" diff --git a/components/02-molecules/pager/pager.stories.js b/components/02-molecules/pager/pager.stories.js deleted file mode 100644 index e8a2d0b..0000000 --- a/components/02-molecules/pager/pager.stories.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; - -import pager from './pager.twig'; - -import pagerData from './pager.yml'; -import pagerBothData from './pager-both.yml'; -import pagerPrevData from './pager-prev.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Molecules/Menus/Pager' }; - -export const pagerExample = () => ( - <> -

    Pager:

    -
    -

    Pager with next and previous links:

    -
    -

    Pager with previous link:

    -
    - -); diff --git a/components/02-molecules/pager/pager.twig b/components/02-molecules/pager/pager.twig deleted file mode 100644 index b84acab..0000000 --- a/components/02-molecules/pager/pager.twig +++ /dev/null @@ -1,71 +0,0 @@ -{% set pager__base_class = 'pager' %} - -{% if posts.pagination.pages is not empty %} - -{% endif %} diff --git a/components/02-molecules/pager/pager.yml b/components/02-molecules/pager/pager.yml deleted file mode 100644 index cb3a377..0000000 --- a/components/02-molecules/pager/pager.yml +++ /dev/null @@ -1,13 +0,0 @@ -posts: - pagination: - next: - - link: '#' - pages: - - current: 1 - title: "1" - - link: '#' - title: "2" - - link: '#' - title: "3" - - link: '#' - title: "4" diff --git a/components/02-molecules/status/_status.scss b/components/02-molecules/status/_status.scss deleted file mode 100644 index bc49532..0000000 --- a/components/02-molecules/status/_status.scss +++ /dev/null @@ -1,27 +0,0 @@ -.status__list { - @include list-reset; - - margin-bottom: $space; -} - -.status { - display: block; - margin-bottom: $space-one-half; - padding: $space-one-half; - text-align: center; - - &--warning { - color: $gray; - background-color: clr(warning); - } - - &--error { - color: $gray; - background-color: clr(error); - } - - &--status { - color: $gray; - background-color: clr(message); - } -} diff --git a/components/02-molecules/status/status.stories.js b/components/02-molecules/status/status.stories.js deleted file mode 100644 index 19d7323..0000000 --- a/components/02-molecules/status/status.stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -import status from './status.twig'; - -import statusData from './status.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Molecules/Status' }; - -export const statusExamples = () => ( -
    -); diff --git a/components/02-molecules/status/status.twig b/components/02-molecules/status/status.twig deleted file mode 100644 index 06e1821..0000000 --- a/components/02-molecules/status/status.twig +++ /dev/null @@ -1,42 +0,0 @@ -{# -/** - * @file - * Theme override for status messages. - * - * Displays status, error, and warning messages, grouped by type. - * - * An invisible heading identifies the messages for assistive technology. - * Sighted users see a colored box. See http://www.w3.org/TR/WCAG-TECHS/H69.html - * for info. - * - * Add an ARIA label to the contentinfo area so that assistive technology - * user agents will better describe this landmark. - * - * Available variables: - * - message_list: List of messages to be displayed, grouped by type. - * - status_headings: List of all status types. - * - display: (optional) May have a value of 'status' or 'error' when only - * displaying messages of that specific type. - * - attributes: HTML attributes for the element, including: - * - class: HTML classes. - */ -#} -{% set status__base_class = 'status' %} -{% for type, messages in message_list %} -
    - {% if type == 'error' %} -
    - {% endif %} - {% if type %} -

    {{ type }}

    - {% endif %} -
      - {% for message in messages %} -
    • {{ message }}
    • - {% endfor %} -
    - {% if type == 'error' %} -
    - {% endif %} -
    -{% endfor %} diff --git a/components/02-molecules/status/status.yml b/components/02-molecules/status/status.yml deleted file mode 100644 index b12e077..0000000 --- a/components/02-molecules/status/status.yml +++ /dev/null @@ -1,7 +0,0 @@ -message_list: - status: - message: 'This is a status message' - warning: - message: 'This is a warning message' - error: - message: 'This is an error message' diff --git a/components/02-molecules/tabs/_tab.scss b/components/02-molecules/tabs/_tab.scss deleted file mode 100644 index f040061..0000000 --- a/components/02-molecules/tabs/_tab.scss +++ /dev/null @@ -1,44 +0,0 @@ -.tabs__link, -.tabs__link--local-tasks { - background-color: clr(muted); - border: 1px solid clr(highlight-high); - border-bottom: none; - color: clr(text); - display: block; - font-size: 1.1rem; - font-weight: 600; - padding: 1em 2.4em; - text-align: center; - text-decoration: none; - transition: color 0.3s; - - @include large { - border-bottom: 1px solid clr(highlight-high); - border-left: none; - display: inline-block; - font-size: 1rem; - padding: 0.6em 1.7em; - position: relative; - top: 1px; - width: auto; - } - - &:hover { - background-color: clr(highlight-high); - color: clr(accent); - } - - &.is-active { - background-color: clr(text); - border: 1px solid clr(text); - color: clr(text-inverse); - - @include large { - background-color: clr(background); - border: 1px solid clr(highlight-high); - border-bottom: 1px solid clr(highlight-high); - border-left: none; - color: clr(text); - } - } -} diff --git a/components/02-molecules/tabs/_tab.twig b/components/02-molecules/tabs/_tab.twig deleted file mode 100644 index a7022b9..0000000 --- a/components/02-molecules/tabs/_tab.twig +++ /dev/null @@ -1,32 +0,0 @@ -{# -/** - * @file - * Theme override for a local task link. - * - * Available variables: - * - is_active: Whether the task item is an active tab. - * - link: A rendered link element. - */ -#} -{% - set link_classes = [ - is_active ? 'is-active' : '', - 'tabs__link--local-tasks' - ] -%} - -{# if Storybook atoms example, add ul wrapper #} -{% if example %} -
      -{% endif %} - - {# Detect if Wordpress #} - {% if attributes %} - {{ link(link['#title'], link['#url'], { 'class' : link_classes }) }} - {% else %} - {{ tab_text }} - {% endif %} - -{% if example %} -
    -{% endif %} diff --git a/components/02-molecules/tabs/_tabs.scss b/components/02-molecules/tabs/_tabs.scss deleted file mode 100644 index 3cfb284..0000000 --- a/components/02-molecules/tabs/_tabs.scss +++ /dev/null @@ -1,41 +0,0 @@ -/* Tab Navigation */ -.tabs__nav { - @include list-reset; - - border-bottom: 1px solid $gray-lightest; - - @include large { - border-left: 1px solid $gray-lightest; - display: flex; - } -} - -/* Tab Content (hidden only in full #tabs js version) */ -.tabs__tab { - display: none; - - &.is-active { - display: block; - } -} - -.tabs__content { - padding: 1.5rem; -} - -/* No-js fallback */ -.tabs.no-js { - .tabs__tab.is-active { - display: block; - } -} - -/* Wordpress Local Tasks variant */ -.tabs__nav--local-tasks { - margin: 1em 0 0.5em; - padding: 0; -} - -.tabs__link--local-tasks { - padding: 0.3em 1.5em; -} diff --git a/components/02-molecules/tabs/tabs.js b/components/02-molecules/tabs/tabs.js deleted file mode 100644 index 3dbf0e4..0000000 --- a/components/02-molecules/tabs/tabs.js +++ /dev/null @@ -1,56 +0,0 @@ -Attach.behaviors.tabs = { - attach(context) { - const el = context.querySelectorAll('.tabs'); - const tabNavigationLinks = context.querySelectorAll('.tabs__link'); - const tabContentContainers = context.querySelectorAll('.tabs__tab'); - let activeIndex = 0; - - /** - * goToTab - * @description Goes to a specific tab based on index. Returns nothing. - * @param {Number} index The index of the tab to go to - */ - function goToTab(index) { - if ( - index !== activeIndex && - index >= 0 && - index <= tabNavigationLinks.length - ) { - tabNavigationLinks[activeIndex].classList.remove('is-active'); - tabNavigationLinks[index].classList.add('is-active'); - tabContentContainers[activeIndex].classList.remove('is-active'); - tabContentContainers[index].classList.add('is-active'); - activeIndex = index; - } - } - - /** - * handleClick - * @description Handles click event listeners on each of the links in the - * tab navigation. Returns nothing. - * @param {HTMLElement} link The link to listen for events on - * @param {Number} index The index of that link - */ - function handleClick(link, index) { - link.addEventListener('click', (e) => { - e.preventDefault(); - goToTab(index); - }); - } - - /** - * init - * @description Initializes the component by removing the no-js class from - * the component, and attaching event listeners to each of the nav items. - * Returns nothing. - */ - for (let e = 0; e < el.length; e += 1) { - el[e].classList.remove('no-js'); - } - - for (let i = 0; i < tabNavigationLinks.length; i += 1) { - const link = tabNavigationLinks[i]; - handleClick(link, i); - } - }, -}; diff --git a/components/02-molecules/tabs/tabs.stories.js b/components/02-molecules/tabs/tabs.stories.js deleted file mode 100644 index 5f81776..0000000 --- a/components/02-molecules/tabs/tabs.stories.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { useEffect } from '@storybook/client-api'; - -import tabs from './tabs.twig'; - -import tabData from './tabs.yml'; - -import './tabs'; - -/** - * Storybook Definition. - */ -export default { title: 'Molecules/Tabs' }; - -export const JSTabs = () => { - useEffect(() => Attach.attachBehaviors(), []); - return
    ; -}; diff --git a/components/02-molecules/tabs/tabs.twig b/components/02-molecules/tabs/tabs.twig deleted file mode 100644 index 84bf748..0000000 --- a/components/02-molecules/tabs/tabs.twig +++ /dev/null @@ -1,45 +0,0 @@ -{# -/** - * @file - * Theme override to display primary and secondary local tasks. - * - * Available variables: - * - primary: HTML list items representing primary tasks. - * - secondary: HTML list items representing primary tasks. - * - * Each item in these variables (primary and secondary) can be individually - * themed in menu-local-task.html.twig. - */ -#} - -{# Wordpress Specific? #} -{% if primary %} -

    {{ 'Primary tabs'|t }}

    -
      {{ primary }}
    -{% endif %} -{% if secondary %} -

    {{ 'Secondary tabs'|t }}

    -
      {{ secondary }}
    -{% endif %} - -{# Component Library Specific (javascript version) #} -{% if not primary %} -{# {{ attach_library('emulsify/tabs') }} #} -
    -
      - {% for key, tab in tabs %} - {% include "@molecules/tabs/_tab.twig" with { - tab_link: "#tab-" ~ key, - tab_text: tab.tab_text, - } %} - {% endfor %} -
    - {% for key, tab in tabs %} -
    -
    -

    {{ tab.tab_content }}

    -
    -
    - {% endfor %} -
    -{% endif %} diff --git a/components/02-molecules/tabs/tabs.yml b/components/02-molecules/tabs/tabs.yml deleted file mode 100644 index 0cbfab7..0000000 --- a/components/02-molecules/tabs/tabs.yml +++ /dev/null @@ -1,7 +0,0 @@ -tabs: - - tab_text: 'Tab 1' - tab_content: 'Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.' - - tab_text: 'Tab 2' - tab_content: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.' - - tab_text: 'Tab 3' - tab_content: 'Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.' diff --git a/components/03-organisms/grid/_grid-examples.twig b/components/03-organisms/grid/_grid-examples.twig deleted file mode 100644 index 0c55839..0000000 --- a/components/03-organisms/grid/_grid-examples.twig +++ /dev/null @@ -1,23 +0,0 @@ -{% if item_type == 'card' %} - {% include "@molecules/card/card.twig" with { - card__extra_classes: ['grid__item'], - card__blockname: item.card__blockname, - card__modifiers: item.card__modifiers, - card__image__src: item.card__image__src, - card__image__alt: item.card__image__alt, - card__heading: item.card__heading, - card__subheading: item.card__subheading, - card__body: item.card__body, - card__link__url: item.card__link__url, - card__link__text: item.card__link__text, - card__image__output_image_tag: item.card__image__output_image_tag, - } %} -{% elseif item_type == 'cta' %} - {% include "@molecules/cta/cta.twig" with { - cta__extra_classes: ['grid__item'], - cta__heading: item.cta__heading, - cta__button_text: item.cta__button_text, - } %} -{% else %} -
    -{% endif %} diff --git a/components/03-organisms/grid/_grid-item.scss b/components/03-organisms/grid/_grid-item.scss deleted file mode 100644 index 47e6265..0000000 --- a/components/03-organisms/grid/_grid-item.scss +++ /dev/null @@ -1,23 +0,0 @@ -@mixin grid-item { - flex: 1 1 100%; - margin-bottom: $space-double; - - @include large { - margin: 0 $space-double 0 0; - flex-basis: 30%; - } - - &:last-child { - margin-right: 0; - } -} - -.grid__item { - @include grid-item; - - /* This is just for the component library example */ - &--example { - background-color: clr(highlight-high); - padding: $space-quadruple; - } -} diff --git a/components/03-organisms/grid/_grid.scss b/components/03-organisms/grid/_grid.scss deleted file mode 100644 index ac2e8e9..0000000 --- a/components/03-organisms/grid/_grid.scss +++ /dev/null @@ -1,12 +0,0 @@ -@mixin grid { - display: flex; - flex-wrap: wrap; -} - -.grid { - @include grid; - - &--card { - @include space-stack-page; - } -} diff --git a/components/03-organisms/grid/grid-cards.yml b/components/03-organisms/grid/grid-cards.yml deleted file mode 100644 index f33417c..0000000 --- a/components/03-organisms/grid/grid-cards.yml +++ /dev/null @@ -1,24 +0,0 @@ -grid_label: 'Card Grid' -grid_type: 'card' -item_type: 'card' -items: - - card__image__src: 'https://placeimg.com/480/300/people' - card__image__alt: 'People' - card__image__output_image_tag: true - card__heading: 'Super Awesome Card' - card__link__url: '#' - card__link__text: 'Click here' - card__subheading: 'Person of Interest' - card__body: 'Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Pellentesque in ipsum id orci porta dapibus. Nulla quis lorem ut libero malesuada feugiat. Nulla quis lorem ut libero malesuada feugiat.' - - card__image__src: 'https://placeimg.com/480/300/nature' - card__image__alt: 'Nature' - card__image__output_image_tag: true - card__heading: 'Card Number Two' - card__subheading: 'Some pretty sweet nature' - card__body: 'Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat. Vivamus magna justo.' - - card__image__src: 'https://placeimg.com/480/300/animals' - card__image__alt: 'Animals' - card__image__output_image_tag: true - card__heading: 'The Last Card Heading' - card__subheading: 'Awesome animals' - card__body: 'Tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat.. Nulla quis lorem ut libero malesuada feugiat.' diff --git a/components/03-organisms/grid/grid-ctas.yml b/components/03-organisms/grid/grid-ctas.yml deleted file mode 100644 index 8efaa47..0000000 --- a/components/03-organisms/grid/grid-ctas.yml +++ /dev/null @@ -1,9 +0,0 @@ -grid_label: 'CTA Grid' -item_type: 'cta' -items: - - cta__heading: 'This is an awesome CTA' - cta__button_text: 'Click me!' - - cta__heading: 'This is an even better CTA!!' - cta__button_text: 'No, click me!' - - cta__heading: 'This is a boring CTA' - cta__button_text: 'Click me, I guess...' diff --git a/components/03-organisms/grid/grid.twig b/components/03-organisms/grid/grid.twig deleted file mode 100644 index d2d5485..0000000 --- a/components/03-organisms/grid/grid.twig +++ /dev/null @@ -1,27 +0,0 @@ -{% set grid_base_class = grid_base_class|default('grid') %} - -{% if not grid_modifiers %} - {% set grid_modifiers = [] %} -{% endif %} - -{% if page_layout_modifier %} - {% set grid_modifiers = grid_modifiers|merge([page_layout_modifier]) %} -{% endif %} - -{% if grid_type %} - {% set grid_modifiers = grid_modifiers|merge([grid_type]) %} -{% endif %} - -{% if grid_label %} - {% include "@atoms/text/headings/_heading.twig" with { - "heading_level": 2, - "heading": grid_label, - } %} -{% endif %} -
    - {% block grid_content %} - {% for item in items %} - {% include "@organisms/grid/_grid-examples.twig" %} - {% endfor %} - {% endblock %} -
    diff --git a/components/03-organisms/grid/grid.yml b/components/03-organisms/grid/grid.yml deleted file mode 100644 index 276c531..0000000 --- a/components/03-organisms/grid/grid.yml +++ /dev/null @@ -1,6 +0,0 @@ -page_layout_modifier: 'contained' -grid_label: 'Default' -items: - - - - - - diff --git a/components/03-organisms/grid/grids.stories.js b/components/03-organisms/grid/grids.stories.js deleted file mode 100644 index 355b08c..0000000 --- a/components/03-organisms/grid/grids.stories.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -import grid from './grid.twig'; - -import gridData from './grid.yml'; -import gridCardData from './grid-cards.yml'; -import gridCtaData from './grid-ctas.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Organisms/Grids' }; - -export const defaultGrid = () => ( -
    -); -export const cardGrid = () => ( -
    -); -export const ctaGrid = () => ( -
    -); diff --git a/components/03-organisms/site/site-footer/_site-footer.scss b/components/03-organisms/site/site-footer/_site-footer.scss deleted file mode 100644 index 7a687ff..0000000 --- a/components/03-organisms/site/site-footer/_site-footer.scss +++ /dev/null @@ -1,33 +0,0 @@ -.footer { - background-color: clr(highlight-high); - padding: $space 0; - - &__inner { - @include wrapper; - - display: flex; - flex-flow: column nowrap; - - @include large { - flex-direction: row; - } - } -} - -.footer__social { - margin-bottom: $space; - - @include large { - flex: 0 1 30%; - margin-bottom: 0; - margin-right: $space; - } -} - -.footer__menu { - @include large { - flex: 1 1 100%; - margin-left: auto; - text-align: right; - } -} diff --git a/components/03-organisms/site/site-footer/site-footer.twig b/components/03-organisms/site/site-footer/site-footer.twig deleted file mode 100644 index 68bbc3e..0000000 --- a/components/03-organisms/site/site-footer/site-footer.twig +++ /dev/null @@ -1,17 +0,0 @@ -{% set footer__base_class = 'footer' %} - -
    - -
    - {% block footer__social %} - {% include "@molecules/menus/social/social-menu.twig" %} - {% endblock %} -
    -
    - {% block footer__menu %} - {% include "@molecules/menus/inline/inline-menu.twig" %} - {% endblock %} -
    -
    - -{{ function('wp_footer') }} diff --git a/components/03-organisms/site/site-header/_site-header-branding.twig b/components/03-organisms/site/site-header/_site-header-branding.twig deleted file mode 100644 index ba2c345..0000000 --- a/components/03-organisms/site/site-header/_site-header-branding.twig +++ /dev/null @@ -1,12 +0,0 @@ -{% set link_base_class = 'logo-link' %} -{% set link_url = logo_link__url|default('/') %} - -{% extends "@atoms/links/link/link.twig" %} - {% block link_content %} - {% include "@atoms/images/image/responsive-image.twig" with { - output_image_tag: true, - image_src: header__logo_src, - image_alt: 'Logo', - responsive_image_blockname: 'logo', - } %} - {% endblock %} diff --git a/components/03-organisms/site/site-header/_site-header.scss b/components/03-organisms/site/site-header/_site-header.scss deleted file mode 100644 index 3eac1ce..0000000 --- a/components/03-organisms/site/site-header/_site-header.scss +++ /dev/null @@ -1,22 +0,0 @@ -.header { - margin-bottom: 4em; - - &__inner { - @include wrapper; - - display: flex; - flex-flow: column nowrap; - } - - &__primary { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - padding: $space 0; - } - - &__branding { - margin-right: $space; - max-width: 300px; - } -} diff --git a/components/03-organisms/site/site-header/site-header.twig b/components/03-organisms/site/site-header/site-header.twig deleted file mode 100644 index 5ad6a93..0000000 --- a/components/03-organisms/site/site-header/site-header.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% set header__base_class = 'header' %} -{# If `theme` is defined, set the path relative for Wordpress. - # Otherwise, set the path relative to the Component Library. #} -{% set header__logo_src = theme ? theme.link ~ '/images/logo.png' : 'logo.png' %} - -
    -
    -
    -
    - {% block header__branding %} - {% include "@organisms/site/site-header/_site-header-branding.twig" %} - {% endblock %} -
    -
    - {% block header__menu %} - {% include "@molecules/menus/main-menu/main-menu.twig" %} - {% endblock %} -
    -
    -
    - {% block header__breadcrumbs %} - {% include "@molecules/menus/breadcrumbs/breadcrumbs.twig" %} - {% endblock %} -
    -
    -
    diff --git a/components/03-organisms/site/site.stories.js b/components/03-organisms/site/site.stories.js deleted file mode 100644 index c7b8d2f..0000000 --- a/components/03-organisms/site/site.stories.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { useEffect } from '@storybook/client-api'; - -import footerTwig from './site-footer/site-footer.twig'; -import siteHeader from './site-header/site-header.twig'; - -import footerSocial from '../../02-molecules/menus/social/social-menu.yml'; -import footerMenu from '../../02-molecules/menus/inline/inline-menu.yml'; -import breadcrumbData from '../../02-molecules/menus/breadcrumbs/breadcrumbs.yml'; -import mainMenuData from '../../02-molecules/menus/main-menu/main-menu.yml'; - -import '../../02-molecules/menus/main-menu/main-menu'; - -/** - * Storybook Definition. - */ -export default { title: 'Organisms/Site' }; - -export const footer = () => ( -
    -); -export const header = () => { - useEffect(() => Attach.attachBehaviors(), []); - return ( -
    - ); -}; diff --git a/components/04-templates/_default.scss b/components/04-templates/_default.scss deleted file mode 100644 index b4f7063..0000000 --- a/components/04-templates/_default.scss +++ /dev/null @@ -1,73 +0,0 @@ -/* - * General Layout -*/ - -$sidebar-width: 33%; -$gutter: $space; -$main-width: calc(100% - (#{$sidebar-width} + #{$gutter})); - -/* - * Layout Using Flexbox (IE11+). - * Remove this and uncomment Grid code below to use CSS Grid. -*/ - -.main { - @include wrapper; - - margin-bottom: 4em; - - @include large { - display: flex; - } -} - -.main-sidebar { - margin-bottom: $space-double; - - @include large { - order: 2; - width: $sidebar-width; - margin-bottom: 0; - } -} - -.main-content { - width: 100%; - - @include large { - &--with-sidebar { - margin-right: $gutter; - width: $main-width; - } - } -} - -/* - * Layout Using CSS Grid (NO IE11 support) - * Remove Flexbox code above and uncomment this section to use CSS Grid. -*/ - -/* .main { - @include wrapper; - - margin-bottom: 4em; - - &--with-sidebar { - @include large { - display: grid; - grid-gap: $gutter; - grid-template-areas: "content sidebar"; - grid-template-columns: $main-width #{$sidebar-width}; - } - } -} - -.main-sidebar { - grid-area: sidebar; - margin-bottom: $space-double; -} - -.main-content { - grid-area: content; - width: 100%; -} */ diff --git a/components/04-templates/_default.twig b/components/04-templates/_default.twig deleted file mode 100644 index 2eea4c8..0000000 --- a/components/04-templates/_default.twig +++ /dev/null @@ -1,44 +0,0 @@ -{% set layout_modifiers = [] %} - -{% set main_base_class = main_base_class|default('main') %} -{% set main_modifiers = [] %} - -{% set main_content_base_class = main_content_base_class|default('main-content') %} -{% set main_content_modifiers = [] %} - -{% if sidebar %} - {% set main_modifiers = main_modifiers|merge(['with-sidebar']) %} - {% set main_content_modifiers = main_content_modifiers|merge(['with-sidebar']) %} -{% endif %} - -{% block html_head_container %} - -{% include '@templates/_html-header.twig' %} - {% block head %} - {% endblock %} - -{% endblock %} - - - - {% block page_header %} - {% include "@organisms/site/site-header/site-header.twig" %} - {% endblock %} - -
    - - {% if sidebar %} - - {% endif %} -
    - {% block content %}{% endblock %} -
    -
    - - {% block page_footer %} - {% include "@organisms/site/site-footer/site-footer.twig" %} - {% endblock %} - - diff --git a/components/04-templates/_html-header.twig b/components/04-templates/_html-header.twig deleted file mode 100644 index ab62995..0000000 --- a/components/04-templates/_html-header.twig +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - {{ function('wp_head') }} diff --git a/components/04-templates/full-width.twig b/components/04-templates/full-width.twig deleted file mode 100644 index 729cb1f..0000000 --- a/components/04-templates/full-width.twig +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "@templates/_default.twig" %} - {% block content %} - {% include "@templates/placeholder/place-holder.twig" with { - "place_holder": "Primary Content", - } %} - {% endblock %} diff --git a/components/04-templates/layouts.stories.js b/components/04-templates/layouts.stories.js deleted file mode 100644 index a1a8568..0000000 --- a/components/04-templates/layouts.stories.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; - -import fullWidthTwig from './full-width.twig'; -import withSidebarTwig from './with-sidebar.twig'; - -import mainMenu from '../02-molecules/menus/main-menu/main-menu.yml'; -import socialMenu from '../02-molecules/menus/social/social-menu.yml'; -import footerMenu from '../02-molecules/menus/inline/inline-menu.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Templates/Layouts' }; - -export const fullWidth = () => ( -
    -); -export const withSidebar = () => ( -
    -); diff --git a/components/04-templates/placeholder/_place-holder.scss b/components/04-templates/placeholder/_place-holder.scss deleted file mode 100644 index b978a68..0000000 --- a/components/04-templates/placeholder/_place-holder.scss +++ /dev/null @@ -1,17 +0,0 @@ -$color-place-holder: $gray-light; - -.place-holder { - color: $color-place-holder; - border: 4px dashed $color-place-holder; - padding: 50px; - - &__content { - font-size: 3.2rem; - line-height: 1.4; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - text-align: center; - } -} diff --git a/components/04-templates/placeholder/place-holder.stories.js b/components/04-templates/placeholder/place-holder.stories.js deleted file mode 100644 index 7c52733..0000000 --- a/components/04-templates/placeholder/place-holder.stories.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import placeHolderTwig from './place-holder.twig'; - -/** - * Storybook Definition. - */ -export default { title: 'Templates/Place Holder' }; - -export const placeHolder = () => ( -
    -); diff --git a/components/04-templates/placeholder/place-holder.twig b/components/04-templates/placeholder/place-holder.twig deleted file mode 100644 index b1a9dc7..0000000 --- a/components/04-templates/placeholder/place-holder.twig +++ /dev/null @@ -1,7 +0,0 @@ -{% set place_holder__base_class = 'place-holder' %} - -
    -
    - {{ place_holder|default('Place holder') }} -
    -
    diff --git a/components/04-templates/with-sidebar.twig b/components/04-templates/with-sidebar.twig deleted file mode 100644 index e0cba07..0000000 --- a/components/04-templates/with-sidebar.twig +++ /dev/null @@ -1,13 +0,0 @@ -{% set sidebar = true %} - -{% extends "@templates/_default.twig" %} - {% block content %} - {% include "@templates/placeholder/place-holder.twig" with { - "place_holder": "Primary Content", - } %} - {% endblock %} - {% block page_sidebar %} - {% include "@templates/placeholder/place-holder.twig" with { - "place_holder": "Secondary Content", - } %} - {% endblock %} diff --git a/components/05-pages/content-types/article.twig b/components/05-pages/content-types/article.twig deleted file mode 100644 index f395a56..0000000 --- a/components/05-pages/content-types/article.twig +++ /dev/null @@ -1,55 +0,0 @@ -{% embed "@templates/with-sidebar.twig" %} - {% block content %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_level: '2', - heading: 'Page Intro', - } %} - {% include "@atoms/text/text/01-paragraph.twig" with { - paragraph_content: 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.', - } %} - {% include "@organisms/grid/grid.twig" with { - grid_label: 'Here\'s an awesome grid', - grid_type: 'card', - item_type: 'card', - items: { - 1: { - card__image__src: "https://placeimg.com/480/300/people", - card__image__alt: "People", - card__image__output_image_tag: true, - card__heading: "Super Awesome Card", - card__link__url: "#", - card__link__text: card__link__text, - card__subheading: "Person of Interest", - card__body: "Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Pellentesque in ipsum id orci porta dapibus. Nulla quis lorem ut libero malesuada feugiat. Nulla quis lorem ut libero malesuada feugiat." - }, - 2: { - card__image__src: "https://placeimg.com/480/300/nature", - card__image__alt: "Nature", - card__image__output_image_tag: true, - card__heading: "Card Number Two", - card__subheading: "Some pretty sweet nature", - card__body: "Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat. Vivamus magna justo." - }, - 3: { - card__image__src: "https://placeimg.com/480/300/animals", - card__image__alt: "Animals", - card__image__output_image_tag: true, - card__heading: "The Last Card Heading", - card__subheading: "Awesome animals", - card__body: "Tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat.. Nulla quis lorem ut libero malesuada feugiat." - } - } - } %} - {% include "@molecules/cta/cta.twig" with { - cta__heading: 'Here\'s a cool thing!', - cta__button_text: 'Click me!', - } %} - {% endblock %} - {% block page_sidebar %} - {% include "@molecules/cta/cta.twig" with { - cta__heading: 'We have a podcast', - cta__button_text: 'Subscribe now!', - } %} - - {% endblock %} -{% endembed %} diff --git a/components/05-pages/content-types/content-types.stories.js b/components/05-pages/content-types/content-types.stories.js deleted file mode 100644 index da1f3b6..0000000 --- a/components/05-pages/content-types/content-types.stories.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { useEffect } from '@storybook/client-api'; - -import '../../02-molecules/menus/main-menu/main-menu'; - -import articleTwig from './article.twig'; - -import mainMenuData from '../../02-molecules/menus/main-menu/main-menu.yml'; -import breadcrumbData from '../../02-molecules/menus/breadcrumbs/breadcrumbs.yml'; -import socialMenuData from '../../02-molecules/menus/social/social-menu.yml'; -import footerMenuData from '../../02-molecules/menus/inline/inline-menu.yml'; - -/** - * Storybook Definition. - */ -export default { title: 'Pages/Content Types' }; - -export const article = () => { - useEffect(() => Attach.attachBehaviors(), []); - return ( -
    - ); -}; diff --git a/components/05-pages/landing-pages/home.twig b/components/05-pages/landing-pages/home.twig deleted file mode 100644 index 1ee554b..0000000 --- a/components/05-pages/landing-pages/home.twig +++ /dev/null @@ -1,50 +0,0 @@ -{% set page_layout_modifier = 'full' %} - -{% embed "@templates/full-width.twig" %} - {% block content %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_level: '2', - heading: 'Page Intro', - } %} - {% include "@atoms/text/text/01-paragraph.twig" with { - paragraph_content: 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.', - } %} - {% include "@organisms/grid/grid.twig" with { - grid_label: 'Here\'s an awesome grid', - grid_type: 'card', - item_type: 'card', - items: { - 1: { - card__image__src: "https://placeimg.com/480/300/people", - card__image__alt: "People", - card__image__output_image_tag: true, - card__heading: "Super Awesome Card", - card__link__url: card_link_url, - card__link__text: card__link__text, - card__subheading: "Person of Interest", - card__body: "Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Pellentesque in ipsum id orci porta dapibus. Nulla quis lorem ut libero malesuada feugiat. Nulla quis lorem ut libero malesuada feugiat." - }, - 2: { - card__image__src: "https://placeimg.com/480/300/nature", - card__image__alt: "Nature", - card__image__output_image_tag: true, - card__heading: "Card Number Two", - card__subheading: "Some pretty sweet nature", - card__body: "Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat. Vivamus magna justo." - }, - 3: { - card__image__src: "https://placeimg.com/480/300/animals", - card__image__alt: "Animals", - card__image__output_image_tag: true, - card__heading: "The Last Card Heading", - card__subheading: "Awesome animals", - card__body: "Tempus convallis quis ac lectus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Donec rutrum congue leo eget malesuada. Proin eget tortor risus. Sed porttitor lectus nibh. Nulla quis lorem ut libero malesuada feugiat.. Nulla quis lorem ut libero malesuada feugiat." - } - } - } %} - {% include "@molecules/cta/cta.twig" with { - cta__heading: 'Here\'s a cool thing!', - cta__button_text: 'Click me!', - } %} - {% endblock %} -{% endembed %} diff --git a/components/05-pages/landing-pages/landing-pages.stories.js b/components/05-pages/landing-pages/landing-pages.stories.js deleted file mode 100644 index 15d98c5..0000000 --- a/components/05-pages/landing-pages/landing-pages.stories.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import '../../02-molecules/menus/main-menu/main-menu'; - -import home from './home.twig'; - -import mainMenuData from '../../02-molecules/menus/main-menu/main-menu.yml'; -import breadcrumbData from '../../02-molecules/menus/breadcrumbs/breadcrumbs.yml'; -import socialMenuData from '../../02-molecules/menus/social/social-menu.yml'; -import footerMenuData from '../../02-molecules/menus/inline/inline-menu.yml'; - -/** - * Storybook Definition. - */ -export default { - title: 'Pages/Landing Pages', - parameters: { - layout: 'fullscreen', - }, -}; - -export const homePage = () => ( -
    -); diff --git a/components/style.scss b/components/style.scss deleted file mode 100644 index 90a76c3..0000000 --- a/components/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -// Libraries -@import '~normalize.css/normalize'; -@import '~breakpoint-sass/stylesheets/breakpoint'; - -// Components -@import '00-base/**/*.scss'; -@import '01-atoms/**/*.scss'; -@import '02-molecules/**/*.scss'; -@import '03-organisms/**/*.scss'; -@import '04-templates/**/*.scss'; diff --git a/composer.json b/composer.json deleted file mode 100644 index f6450cc..0000000 --- a/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "emulsify-ds/emulsify-wordpress-theme", - "description": "Storybook development + Webpack Build + Wordpress theme", - "type": "wordpress-theme", - "homepage": "http://emulsify.info", - "license": "GPL-2.0-only", - "minimum-stability": "stable", - "authors": [ - { - "email": "randy@fourkitchens.com", - "name": "Dean Oest" - }, - { - "email": "evan@fourkitchens.com", - "name": "Evan Willhite" - }, - { - "email": "brian@fourkitchens.com", - "name": "Brian Lewis" - } - ], - "repositories": [ - { - "type": "composer", - "url": "https://wpackagist.org" - } - ], - "require": { - "timber/timber": "^1.15" - }, - "require-dev": { - "phpunit/phpunit": "5.7.16|6.*", - "hellonico/timber-dump-extension": "^1.0" - } -} diff --git a/footer.php b/footer.php deleted file mode 100644 index c00e749..0000000 --- a/footer.php +++ /dev/null @@ -1,21 +0,0 @@ - \ No newline at end of file diff --git a/images/icons/instagram.svg b/images/icons/instagram.svg deleted file mode 100644 index 0795a95..0000000 --- a/images/icons/instagram.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/images/icons/menu.svg b/images/icons/menu.svg deleted file mode 100755 index 7d083ab..0000000 --- a/images/icons/menu.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/images/icons/twitter.svg b/images/icons/twitter.svg deleted file mode 100755 index 59bab96..0000000 --- a/images/icons/twitter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/images/logo.png b/images/logo.png deleted file mode 100644 index 720816bab48c89026bb1879624e8bc13349f8a61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 335111 zcmV*GKxw~;P)pfG(W&BB5LlDRsAjV#w#+)!xuAEQ4uge zpauW0|M&k#goKEQKmZXyAVLw6Bmk%Z>vt5Q5C{}v3R9Ts`U#+b02GJ_MPz*$A^Xdr zxPBCiLJ<}vOUUbQK&1ct#}BOEgel$1Q~=%oKtx4A3Mo~|s>-S&$)p59p)lR`AAcRk z@S$Q5p+E$xB9i@26^lqpNnZM9j*f7@>fQV7&>UD4;43VM&oy z>oODTVrZ$#aQo{x{%R#QfCyP-is9oJ$Ln~B;2q~V-{(BDQp7|6LPP*igjMUKz;*qpisAls_&@*Q!;saV zl2Sk-vt~qOWwFR2t12^D1(FmJ1f;K$hyn&VhW+(AzFzh^)WE9qU*Ge8|Bw7WDUW*q zK>xq>7D~-kjqDliy6p(_50?>&3_5J>S z&-2WgQdEkvzl)SGMe1<*Z(rke3>(K8^Z)$6{?GUOq`=gV<8>UzaU5<2k;**J_c?PW zBm3@K*9fW*-S>7X=*Hu)udgxOOpwK2XTGCmNQP9EL62d7z5E}qF$PpigvFYXzrOSP z9cM&l36z3;BlMk6lmh8)|M>d)>lm*g5ShXE%wK1mky%WVRS+t84FAX1>uVf`8p-cC z-!r~v%&3SGimASa|F^H>>llY=lK*<2zs`6^22)T(iHM4t+Tq7Pj<3TVkj(hk`};l5 znUO_`s_Gc#Y7niIIV-COQi_rgn#f`L*D;P`j4=RY)H(BC@AHg`>dUEaVoKHOqLRfb zR4E072!*vC5($7+@dknZrP(N$;f1`hhK@8%i?(XJpW~#cLK^80ere|?~ zGwnLlbrt(3_v;a%T(^6cL`noRGb1Zw-9vIeJN<&L$6QdrF5cHy+kdMcYxeC&h!9!; zWmgISx?WnpOI1}hQ&TlF;1D_lYJjrqP?3)-L=j5g@72FvBCH}=Rb7Zh?h4=6Ky+Qw z{e7VVg<{wAe(L+Z^p)xdKl`iZRLB_SrlwGct*_T#&P2dAF`$-0bUX{Pps`4nIr`Br7wcBq))HWF#{gc^$31&gp&~>#<=$L=|I(}=gDGE|0O-Mngmb-j?9sm62|5nNW^h zy~gVp5Sx?lvyYDoih!p48v4(#@fyQSMKnv^v;O1P`#oo7pYt?@yL}z6|Mu5krkLk> z&iQMeGb^biSOTb3rKMN(GpvbL|tXf4= zvD;(pmZK<;Rj9A_`q41-YQwwAS<&k1gj)P1mY?Ran)gP z5W*^6S4M?GLP-%R6TOZN5fLg3t0t1n(y8L$M1@KgvJYwQV-J9+5Raolt_Bg$QpAd? znW<{#re?=+5Ku%Q8S5D1-96v`aUG?&TL=Js-MUR)A2x|BNR%)jYG$q~?v;~;H3A9m zvJ2cd=01X|BqFEEjMXfzd4cO=aTTysswg5-TBW*r?QXca-gp;3S3S%Qv!P}JGBPWR z@^iL&^dkH8yl#wSojJ8+jAKB3NRZjheK4{JFM(jW}O^4ZEhkp%!xtU4{qw0)&pYiLAu6z9yT@TyiZWT9s9mm)48bd{9WS&v) z$TKox-HxiFiedU1ehha5S+n9jXZO~XgovsR*Td}??ye$|ndhA6j40M_*Ay_7qyMC< zDkAee<2~mY-4(;o)3^$d#VqzyD!OYGWT+m)U&k@r1(H?oi1&;$W+b7mX2ac7AlWxo zRz>z*XYA*0H<_zLd(_x_jKBK>TsQMIPReHwyaq&(l1iyac&1wp_u)RwO;uI9YqOHv z9WyAl3s-ltt8P8o{9PePks>1{tTHuCGf~xp0@w|R@Bvu#``B-iyJ%hY0_(BHvxZ8a z*H}YpSFWoc*HTp!H5FP8Ema3p3Dj<1`%muf_`1SW_E=9LQAHMt%7q8;>i7GGy}sJj z6zes|?9O_%=Y6J;*#k_KRJw_oimj7#eTRF>kYp8wlBBFIe~;z&)sp>JdPH@1h*w$P zsj9OFrfXyYuLD)29t9b9jbtcaM=o!Wh=rS_}eljl1A7Yv0WBO?^$u7Iktn3WY(!cgmR z2cW7fcx%1C{(MpF3RC^Dcd2A!W~v0UazgM9HX7oN*P*Y&$8Z3V)gAMDGHZ8WQYLV-*U+!S4_62y=KHMgv(8yjLJ@Pk zhJC&4bqqJ9Ff-pF-*cZ76oHsxxV{d19jb;ZIU~Q%I3t3D3RJt}QIv>e&$0_%S|!01 zhx==c*EkGTG7qFhY z4J$w3hRKFw1tO~5P^GX+vN%y11ajjLRFo>T|F?d&j!zLZc#8*yAc`J1cdvFGAiR;v z{lB8j*&OHz)gleX*Ltif&uC|Js2pao>q?C}ARawPIXW`Kl3k2HG zib4Sc#_%%bF=pnksB`9w#+iB$QW0D)Xf^OkAw>l+h!j(#iV~UMa}s(e)bu!BYGZiB znT<4ttdWGfs5W+gU8|m@H!!q1>;5R&v%M+=l{IU!Y7BpUy@q+J6O3AI28AN}ff(cQ zTV$5Z!1p_F#}*~epNC78wX}(bwjnsGr@o!ZZFdTfKrsm^(W9n{xxXr zVe#&;`w1b*nn_QPp^)K{1SlEIBBSK2nG}R6hxsrYhCwYAhz>IjiDHt$TKyP6R#!u+ zBpDTH@#yMk7R)J!TUo{X&p;Yx{bN7FIeSN)N#~6@0XT8t) zea1O!hOpM1Y^r1E>+oZYVMg-%obNfmBWF})iNM^4+u?o;gG#by)jQ&>N>ZRb*rG%g zhuS~Jad<<{@#}rQ&-0ARTA=7*t_oI_L{V#_4c!L=7_MK(c)6Q-iM-GAj5udSW=Yx5 z;}~NcZZ5@nen&EjMUj4{lB|+@*1UeUT8YOxOm%a@kEWWTT8lD~?s8DJH&jAX zm5;67v#!_LlIf~rUB&z5Hui8gZ~Lu~$r4DT5n@Bl&D7)pLWmS4tLDteoOh3Yt(2~F zMF6OpnnO)Z-Sjoo42QW5KaOKYzTfkl(LnYdkaQo+>nweA^-@(LoKsEX2sgmiVJH+` zrMhZuT|ohQC2#>xtKlZC_)?BxX^7$PnKN=m=BoOjto@Y~*m#jcL^6Y{?tA11Vu3YW zUE2#GBJz|{DbR4(VNk$CgtUH_DsULBi;P-dfnHKZzwiyGp*Pi)GmBZ0#eD8dASdR@9S|C_HC>R zC7D$*=V?%PbMx^!+#5J$=8T9l>WqxclOL1*TF{F?7GqSofPTQOO14~qqsiVW4iUdPvK9K%KAJ>z}m z_q)yz>K@yK3Je=xFZ(*|btt6HIp1ge>pRY1mWn`yhx%V%$Jfivgu4aYAI;i0~7YDp4D)b6hsVi!2fxC9b-T=vfl4`M$D=p zs|3JdINXnytAeZ<^L+P1r;SmNkSu|y_S8|lvEqB-*ccf$Qg)BO%}8h!pGk6gS3!eO|~_I|LsG_v2dh0Vc`n z#?jME;c8`litT+z-;{X5G1pe1QJFm7!jf?eG@>TVNC3ss<`Hq>(`qK`J|~pAj*~9b z>aas~xGIoj1n1<8Zjd1SI}rQs{?{hDBvLZV4DLFlYO)qbKr%~&rqz%S_i{Hg71Cg` zg{c*?(9PKz-qy(##fYk;s~+Q^)$pO}A`Q^a_xqgZESC7iV7WH}GwYr8j(pFW(KkBQ9kjRc{hfLz@EYUmIEI_B zX2$o-_pEc)3|95tRNeF#IEEd=O|{5~{GRciIZKM5z*L9Z>+shx+(6;1?4@~h2O$In zq7Heve~odtt0E)Mg$pAvDr9IcR9vUXwFiy#hud+Ki zsm4@vH~fMJcDjH01~wLK9Q|Yx*-IR#Vo&l5$54eraU;Z!3w&LJiypYT$u8>Hzj}4x zQWyNS-y8NZ(-b4MVLJBSbUoWy(iEM@h#50;R^{DN*Pq(!QlnaFnj2UcZt4wRyQ=MC zSP|=(+nZHgM|ofJJuCK}AuDIZd!8a3-jMWe!>^lRfzW*!>LUJNuQMRxs#I|?OG48u z-7+Z@6eUwCnY)ARM{>214fhjd{a#K1?1cq-B#4j`1Vj~pjs1ERY<{9DdXiIxn)XYs zY_2o+CxIkh%>^}hM9_Mg;Yi)Q3a58 zC?UYQA(iOPOCoE|oDrMhAp6l<3j~4m*jkxt7(P_Zk3+3t@);3x&NFLfU%|VN=`Y}l zW7yZrzg~x{vlta;#(T#1%rn+ZinS_KcYO_ixxL&RAZ2C+<$Knzv(8!9kg?D)u<4&t zV$flD4gVUiVNN2lex3P#=bTIm``Q;x^>BL)|2o`V$%^Fr+<0?}sJh}X{B`Kp@L{%a z>zVH}-e=B@TPrY3kKxC#;VQy;p6`?A%*<}%*Pd6=gC(m_&3ahCBHVB^-^PZDHcjzg z?>RFCA}Ve=OkZZh)l5qyqRz>4GO`;`T~9xMqwakoCO3&xeoU2pU+jWQ?Cx88;eR!5 zm6BCj6~!pcE>i9^H9%pen~Skd1zaR(tn+yNpFeB;egBcze^*ugDhycQNLRe7RrubW*d{9`ZusFL>o@GS@t*Pn79GiJomUAG}+Ty8nA4?%Wgq&qy_cLM|#$X7(JnVW7>@Ubv=W?_=3>FeI4o z`5tC!aPg?Xy(s53n(QUOwJ7f@%$RRv4mE!rV~ni){{D`LnUis@zCQ~J9|Hh0DG8-2 z%+*YXsv8c4t13WZM!kc-PQK^5uRb1?3&Y-rcGu^=hNNZcIeAdrjO0Aus4{a16db^7 zjNvxSRoA^0>@|xKLc?N$3Y*(PrmR9O6%467Y8>`DUVX7=#u;_aIM0YEiV_!4)Yn`4 zivets=s&)W<1lw4>wU&KbDlM$k`PtZ8ji2m_&V(6a1{||@tyUa`Kwox6pEogXmsON z+-2XPhx^xPI=GYdJ-^S0cT}V#RYaP?;D*<*ui?X0n}9LrIdjg81p7}S-S9f}>t)Ab zA~GY-tcLM&bt$C+6Mfn7&#$lJFi7<_<~?&}X0K0SW~Pc_u_{^FrDD;TV2WY(<$k!k z4MM)-JkNNa%n%`m>p#A}y0@<4JKpD+XY|SK-R5rwI!Gh$&*Bm)$YNOFiE3~y)anP` zeG$~2LXcIz)Ys~sEOJI=o-^W{a|}Pma8qqOGBX$Y@T2a3lAaz1ghH%(1-h1!fVQy1 zgjF1-Symi9;6&D(jHu?*ubRA@KG{l)UM|ZbEcUsnUDj2hUD^8QdLf?u9LqZ;it4CB zbIP-_2+?~GU+mBYyVf7x-XGq3e=>DLGrX=lE`;avgrq=F*o$U!g?=zT0QJA?P zd1!A?`*d6F=MNI$#aeC-&CEi`T1>dPsz9JLQiZ)D00|JRg}6i9*5aOPAuMEx66#Q} zl9VSY_9>|0=0;sLldP^(fV7J0eV&a>n~xV1>g0E1W@M0VBBE6V(MH;<7|C>#+ZJ*w-*K2qjbAlfS<6oUQgCi>esI z8(tX>k&GGNzv6vXl(DC2lrm9kfGSFrklGKgLWb$<@V{QiFc-l&^IzZdea6fzp_&ZU zua|!fdzlI49p^dgJ#z+wwzzeCun45q){8e30*Furzctm*<{LK^k0kB|o-VP*;sI_T zGOL)8oKe&B7&hG9R7FMZrk9KCFJLvkbNz70Q&xEKS2icScPxr6!`KZrHsj#IO)NG_ zfD$iaE%vI_cD$Ce8^eY?m=m9<^L`8;ypT`ESS=nC)*yP3Pq13r^%t6`JyegO?y%iH z&8RbFR&{&5=G&@;ENESe%&k3obl6#D^b#+6iJQ8@ps%sGmymhRnOBF4eOlmROV?%} zE^2#L6*Dtc)lwHU-m6^MbCGPR(*6t-W~F7Cmw;%m6CPLcBHOIXOSD4n6!BvmFoSi^ zzqo)OfC_)>I*M7;%*eFj7zWd$3Xs-xH5j%3W{Ln(fGA)FcMQkAXaJg3%rkr6)T@KP z2d#IVzI&c#c_t#t6fZY*hN)6u2B-=f&>dy~D2ydM6E*Q$YJv|!qCA?iMrfkS${Zz_ zG2b)ZXUwSBE5qLEw7B7>S6vG$Ll!Ute7*-_xslw5wt3Zh}jaY4bzv~*RaN^0_U zx|RZ5K9PEUy!lH&@zB=r8h`d8b+y3jo$r4)gL$~UTo1$0rhB3aQSy#@&v?%`XJ+Lk zjQoUdA6y^Y<5SlzvbS-XC7GtUp?AUun@Zgg7aMBv@M$RE#!5ypO17pGReLx2P{lr4%f3tM zYC1AGMS!G0M9bBC+~UTa8lB!eMTHQ6nm|jC>v+w`imY54Tzq`@OGWcoemv(Z!E@z{ z{(@$4j56~v1_T+YfV2=x7n6TIjg-tfXTHyQ&WMQ2X0B{Dm|a|?!X6I$!jg=6&d4)n z#3J1UEomuZ$ z?}$i}UB4(Fid6x`4a4;1{^i5XSvaeHo%x=5&Z?>l_BZ2b9_*oRk{L7N``kJi3cCIu z_B!nA(8Ezu5p_nr&s_SSZpswH^%#B}J`Pi1%=(^r&N-v6)>0=xAQEJj$R6*P*mk(S zh98H!y9j2~IpaNB7a*oOT)&3?b@ZsL5tW(ejHVQ_`OKRc*0P0l`tw71PTmFOqqx;r=lhLzK9VHE&G@+nMo_n>sz1~IEZ=Vj_1Ub1;bheB-S7Gnz z8k-zpeOd#Gz0!;1nVf52UVKnq+tr82S8kdkwg|7E*$?jYqa1%|fi6Hq7ZkIkmS%cc znyhwr0X148q|M;3cfQX!vtk`Z4OyZ~8`g^QF^H%>+DoaB$kbVC<%XBZ(WI?08vPN$ zlt_L+XF}mR>g03Sz_mEds+xt}2Veun-Ey|v+!SVJ<}Im$YO&^wIb)t@RBRkVJ^zxr!q=@$K4d-mMZUznkQA<{q;IthqDzxw_6O!5&|#)s#KHi z-E=|vY-fa-d`F$dAhIvf55Vs?9`*+W#znm z{>L@^k?6y3KZ2-M=?FAuPJl!*etrLX&pET(I}=q&Su0^s6ktiE0f+l>jMwWhGm10~ zdhXKQZ0uv`ag5hthr=Og8bH<=^`7-UbH+MoTg_3I-c1e<3neRyQJ6xaG|6O$h?^e6{`z_iS0R(p3xV(NSq0PA>p1@Tg_k#$ z#S+P=+ykXh_awfsw8dAt!LiFNK^g>BeK3xjQ*n8`@M5^F)^00TaBtxDe&e2;Rb{W? z8I_zstnb~bnRe~?*th-GUSESWwyVS@RqO@5HhBY8nVIS9WykTVw1_!peNTzp&xCA~rHzX6 zW8&<8g*;BTNb;RkJtu*~e8KU1Jk!x%oJ&dsQ6rJXbH?}kJm>0Z$=*?2n3_Bkxm)b} z9De$x+LjsiKm6sABqT!!0g*fI1NYAO^tBY%DWdY?5zmP!Z8YU(T8@EXVK z(62*Xg_$$?ea1QSoE261pyF=Bhg#-ifU6v?e;t0f57o;0KI41lcf-s}-%}78>M;B3 z>v$c*lvU^V`+T4Ao)txr)eH}}*I{2n-9#iK^F8a=JI~2&N0kLt}qTPyzC9bs)tR%3=o|0~+l-6F?Toms0dySa0J z{FdwIKFkVLw{V=FY9LwWc2~C#DitUrDDWl`g8&pN!eJDpDO0sdYVoe_;)W+)vWfU4 z9*-|S(46(JJ)(*Qoeg=0N&0D|JANGak=&pl{NDbCPYMOFSQH5>k zh#7|Aa5Zx?B{_kUf()f58dSQJ<&SmkLROj@$Qh-YU9pM6qMq<+8TJ!f!}7D4Ma~eB zqv|z=x7KN+SOQ^}umaFcd=v9(4d}I-$jq612WPY-_j8N<+b?|l;<{!@N|r=OMw!{+ zLj-Um(4@I6X6DS88L=fPTLsGHx3eHWUi6cvu|ScPMQfUJ%o!<4>9!R0@OSBENL7aF z*WqKhn^7<$-)Fqv5fN+aYjD@s(S^)S0aj+t$aCg9az@R*O+Wgv9$8BTrsk%uc9lxVT8EEnX$ygQmSPu1b;$F_UE>vB>mV%kGe9 zzZTJE{tdU@0jP?Sl2u8RXi->RDQOB3YoCx|s)*`+?~DwoH1XH)zYhQFP&Z*oWX-4< znXxN5Ya@}D5grw#Ye3aaMQb&hKEdl7NxLL(#iu&DsrM9Jd!1{CRC4n#x_ z+y$EZ3A`n_KRDnYg_-ig_{t3L3&V8qqz(NOkhilZcsQ>)uId;*d77BR%>B2KHlF0vAldPEpN#m$uI8>fnpoSADY&|Dv>swpf5Mm2qyC?RB)ypv~SL{=!P zZ}s-cU7kOx@b>dDGljVVE~pfdrnxL261d9#?Rc z)1$#ZZlo--x=7@b3*25b%cS9c>ADO?vOvkqGtL>&>ai_RSarlqUqg?f?V+SV7H7tG zbBbK%VJg(EFKlzzak#ot$eHn;)$Miky{Z?6blV*QFjaRQj@Pi)FjK9nbGeaJBuk{N zG9hKa%k4G%HQW^yF(cZvsqGfpkY>1!p@*p}1k81oJApFBfq z8O9i+T^U_n)q6JsQ3xsKYINfE&i%m{mt9aNahR$Rc+H&v7NJ*@rXQ^ zdh4!Ii1+W#WSsBmGr#id7{@SEh!li94AGQmuDq_#oOW$XyHz?PXI9+O@}~#kCr$6q zq}A#kIaB7zUm_giuQAM&B2k=eVLNAJR0Tl*?PuEUEHg7^=G=JGc4HMt_WdE$hLA*| z$~IT-8^=~l!4j!$W-Dg;HC`{b!wrInJR`qnm(&!}T*oRv{yLfy4xvP0hWz@@_q^;x z+Qh2siwX$s8LyOjsJ+HGwrvFlrwq8tiWC{&svDeVIWLzPrWR^@)0 z`u0t359gu?p}Tm1iOS0`u6OZ`sQEAvVQ#6KP}~Jp;uKUl`>hC3r6LL`Rx-0CI>5`v zu<_Sl<2BqgTQQv}?-}3kIr9RLg%z>~qoXn^~gr2LnHF_`!2L|Jm)5=qE~L^^MnQvW=kSVd^JDJM0x$25sM3K~jP|BbIMnzYg^7 zwB*VB#gheuA7H}Gd)|Vwrq6$WpD{CnK8C8f4~k;uoHJRF2j_-+lS08QS6ZI5%ZweD z6dBcUm?~f_Y>m0r0-J3ylH~z@HC9PxWMs@a-E{Oy15ns8DC_Xa1xu;!(ssFt*J=r{ zM1(z6EqXg8tIjhcM8COMO3)U1;$f=T%e3C<$M(QmpsGDFMF<~j$Of@kkF_WsCRHUM z!~hku-cLsGn{TN!6^x@<(p5sK2On?A!g9qgKwxT{)_Fns>!sN0-bAtr5+Yu#r3FzH zx#o23uI6w(49%Y}$~*F$`Td@aBbSIsYI86WXkK^kY-XvvVWPZOJS$lC!3W{wX)N$O3Gd12^3}R5PKv0itR7zB!&OyX$1ssv zR{FBt2%q59(?*pa-23Z*Rlwbb+ZbaEAKn$Nl{EyYQrj{_fuX5NbruVi5?KkR%Ca22 z5i;HDjS|NV)#7P!gAbAhpN?j|DqbcKE4zm7reLYC2}(PIA<2^MnOn^XiJVz8mWyrG z7U+FC$l|gP`J*L@>={?2N+zk2Ds2GwbnM;?L{K5-)?UR5WagY_#F-h~Lt0x@UlzS< zH&~g3z*R2_=q=gZy$;Oa26sK&O?#-T_nF&9qXab5G5qEJ8g{r1S3okWhkr~*jV zwaVur=dVlCQ#O!-+`~MUlHANp`qQtiQN51vE`{Asm_tD%E)VC$;V~H^<{0K*2h10B zBgk2KMjvMPDz)XytBV#GWEkC`O`uSHWQahnZtXJp-<)RAl|RBob7d1r`*UeG{RXz_T`mpX4|9+|Fa+@-${{`@ZPvlEHZ2caTtrL}X^}EA-DEgTwF#-F z8YSM0kzfRFp(meD6r_jF2S4?)x-XVg^(SXacR2_VxS9fRSJSMF%A4eSS+PG>j1NSf z4|W_k7Q204*BGRiy~OIL?+>PusS48}&;p7)vl=8y7NrTSy(QZY2#=Enyp7kEhKdYt0UjJLuwCP=BU0{bc#!p2|KJ=WDMk)M8GCg74Wd-Y}?uXm|zp$GEY) zJ<03^$K4CnJ@p8y+oihKv)BF7R$=Onv1!u|LZq_J}4%gw<3&}HiRs{=6(~AM1?NfIQe~shtwggQG8etGM z*l6jeLfq6KJ@_;t+iZFR)RyR|h0ZJ31WkB^2@ZD(gw~FM4MZ&4(2@?&P^i1V+=r>5 zvT8;&bG6x2ZrYY6ufyD=B(vfj^L@s5)LdJS-Fj8w+G8xQy@o`!O_17qqtgx>TD@oK z9n*A+_^RV?mZbRBnco@R0D&r6H`z39Bz1lxib;dbGHf3trhb+n# zant7b>5|HeX|ODAD$g@a;bZt{T+WuIUUB(zkPDMmRTb6RY{-`SacgH~Yi4>LTz_gO z`sHo#v`3x$wQnbk0UW6%T9R`zW9}rO8^|b<8A9}7{IDvywCxY&DfncPP#|KYWj0N{ zJnT4znjT+69rEivXJmvx)yML!;-@#)-)LPS;%5JRy zu!@@J_xt^x^DGi(5L+N6wBfwgy;Rj?xKePaLS0eq)m>5o!T>XM*Vlo+UZ#pjnURq) zXRYivl$tb&^W}b+x6s9;Dk~ye#n@PW-RiFe$1S;oscY|ueDS55by#f-BbKH6ysV56 zi9!~O(a!E2!bNIU@0Yq4DUqZ^FH0z3yMU|pB(~dMR9Se zbEP_Vr?%_G&IQnA)sKgb226W-y6@PP0$Y_ei@#E47IGafvfR(`9Qal%zm9l*MjiZk z#Gj0Ui{|cY{p1)wR+YrlDg zDK3NW)xQ=iUx&Y5FYzI)jQO7Ny_|YiF(6Z<1ObT1xPYYvbUeb;ezHbbge}jX#$Z*2 zO4giJoD|rgDqx}}tAKSmT}~07W%GIRKHLCEn(Ej;$eQQ-Jm2K^A|~DI-@0GtgR6Nt z?M6x#XO?BU>oH(vuP@ce_j~q%%OCo!iwu3m9#VermzGN_KtWQAkR*^R^>g>cm&sz! z-J1+mmj%K)!at-m`*M>%RgbtlHaqGMo1sx8qVkNXCW3M^m1};vLYJgAv1j*=xEB6P zfwp}uAXNgW*viH;DUrD*IRy?M!)+WKLx(TV{yF3O9q*av&Kr7s$n98Yui<~a#xcxH zgq5s!@ISugecqz9OJBiteV`CE9ggE=FUMaBgG9zz=(Na$>7hfZLY2-MG(|g9S_^Mg zG4y3NuJy!XrC*lNy*}Yus+kc|x_!xj{>rj)O4Wq0V{kiET*0L+{#hcwti>H^J{L=( zf)SNbjB0|p4!5sk{NrmJ-c*-3GtL?BbDmL|=uuE{-$h&Wi(W2SyZ8-l@wlqk-s10l z`y!Y&dc7adWph=@s7)MN7Gc}l_KMls^$?>q73(HlHxr-ozaH(LF)4QjZwRQv! zUB`w;@v+VPgO~TMK$k10GAU=4&M5M5s%l9yCIN7}dK*O}B_hAic+d7WY>nDRAwS#* zFL#KH$`$KH&PZelsOXh>gAH)iUfsTDogF4zDZ-|{y$(Ozk7g|Z!ltjpJC^Ne6dV5S ztnD3$s7pv}a8+-U!QSUp6`9;?>0GP@LJ}3^?EJj;cfI^^sa4nqW4G7U9xwgzb!^P@ z;;%Cadx3k2m9=`)+l%Yo8M62Orb_ExSG&CiU^GK5n##7OmwjG9TLKHDNIXw{gB-4= zA~5c#;9l>%UZc-400qs~*;FnS7Z=Axx_l933sQ>zM=!^sY*lWA8E}Zsh&4b8HZP zr5bNvscrPe&f5d43H5jwv@TtWnIx5BgcQ;C%KJ*=X<5FvO!AW#xivg@7r1-OtpmdC zxOEl#teMFURas?h<(oZmiKjup<3rxIKP*5@Wte`whWilJNVYHDPA_lqqw=alz?Is2 zZ?&%XQ@wAgo@&h%>Sk)z%R}Cl!SV#iuBPjAS+cO$$NIcI<=0(Pkk34it584l3OfK$F=Pykp@(6JDA8P86)~upLdCSxw`;Gg9cm(o#YyTit+&zc=CtwREv+F0brEV< zvcfMLb8B)^g(OyP(Q;T?YvCQG)Qi;W7EZa)zYc7bQl*QCLun!y?#J+9)}cL-anAWY z=h<>3IyaPjQ=~TxYwfSKpe0-JhI`MjcnEs;fXgX+-Hy6Eol7D!3z6AxzgyO<<=B0t zfh`kcTzkVOfA6`4!(Fb4`>7I&i2v)?d#IY~VS0=si|1?-`WcaAk|y#+DywX~L3?!(71{4fih=Q+x+$iY3XLN`Ok4 z@5+oE5RnuYZDu#6a7E;>iMoBaZYn7*K!s(hU&r|CI1bgZg9w`BKMNhCk}LSVIo7yf zU~6%KsS3<+s2-wOdQSO9ol?0X*l=lD9!x~sOmln^G4AZ;KbhKMi3s<3bFtVj>C8hq zes>X+4?PrqL{k5`z2?tg4lFiEManm8M!mh<%-pRP*@6;Q%A{mwE#hJ`y<7mvEp4#< z0bQ#f5dl*0&Kkh*L!rv=V2IKTUtc=LFa-~-XPdAoVxfzvbnRD z$=wR-=1=?nr@>e9vFKQk<@ND0E1Stp2}wYZ1mqhRUwZp6!^~_9bzhV&g=1+z1Fm~lpT_*_-#ze~ns<t_uV7oqPqA4MD3>Rx>9)#ql`J);huws}j3#y8&gDpf{@6N znRG4pY@-7+46|Xvff@P>^PIiN*zSnS0D@1u)XvZAf#s5Px29Fvh-*f$CUXow`d)pF z2>sO~B02$jxG5Zzj7nxk&Wx%+iBgS=k9CnAx}YnmY>@75aBFj|K}bKq)^}D@)C78A zfh%UvmjAzUoZc986m&k=bRZSMeC>X+UDLfn=aRyy$XDE*jJ((8Bt7{n-2p_T^yQ` zmAS#a8^L%GWcV?_T{nQ-@~}O9{X7_clxRG>3qA(M-}jIo>ZaR``{r9=n`W*zrzJcC zNjalTwRvcYW&Kf=9cjAULaw0S2mf{(5#r9A5~)m9$HleNu6_M=9Ah|O0u`^6bIy0p zcbv21c_CE222xeHsSkf0{^dSQ1w{Pqk9T@Gjwq4cb4y;}} z$OTbpi=p8nqB2x3qaSXooSAL7R2}xb58;PVbq7sf4D5?xcqdR?Eyzvr_3!VQro-(z z(q5wxCr*7Aq{X-Gdm8UFe6w(1tn4@q?B?St*=3u&%3JP|ZEkCj%@FO6w^SygHvJ7We&ccr1zUP~j zTO(P03|%+2uq0y@qZP%qEG<82ArRNVO1HfqysrqB9M2s&!f_~Zz^Fg-5vnAkf%5nbD z5%{xef81SvGQ-(3WPh|Xn5d|7qf^{ydZExgoG^1%c3ZU0Mm}Gs{4`GK?Zi1lfWuuK zs7h4<&FpoUDyk}?zTa}roDn6Zw#*`j*~|TKKYSQaE3LOx=QVR}VEVLhGk0~f?yWn6 zx}B7Jk=}&Zw&smm|19=vZG99j+Tqiv?(ZxP@h}j*;z75dOm4jQ1CqLqt}W3aKR>zM z58M^JZU<7_jYt77EwhSJ&$769P@-Giyf_<_)U{oMstre{PG-(H-)Eg+k$tUpbo32U z%BSZiA2;@+D{9_hsaLsWs11cfhcvt`YA{vnBi`zq6;bzI?%~1&{>X~_5srQXr=Pal z50372PW~47xCC(Ku_rRZH8xza)IMPl0@B~&0LXvR*e z?TB2KP%mMr@ZnB%$9Qf3@ZNl5>34u9=5(DNmrPS?yK`+LuiA^{OEm=E#OkMoET7@? z{by9vRbInihmV2C%9!)as{0JTyq<7r%_;X_uH}v18ef;*-@Y%b6tb9SDvK0}8mf7<3xZ3Ng&1!yr49{dJ$nX9W zD|uB!RfoHZ{dJ7tE-b0a;0%68e8)U9qVmdx)tyG$mIGt>=-yhmoUmt}XPqHg1nP3Y zc*@CC#7xJqG5i>6hPAS(KA>-CfDuJ^*s{|-mqqe=zV^-Ut;5_Vp_D80poY;PWlB1!wX_UnhH+SVlUQKN@*yu{BNI8S&*?yGrAjZ^f7JY@}kyR<} zgRs?Sz3(8WuvFcxOfwOzYJ2|1JGyle2{1dayIlv|eXO0ckZUw9ByakBvtD)4!?~aE z?BJ?ecYggo!f!~XV(il}-< zi*{w!CYkc~{{jDa6bkU;j9h~LPl?4ntUg)z7gyh|Xok=6@d?vzO6Bfm7ix*y9;4%| zGl?^?!(q)ZhI=o%dqvnsnq5W;T2-0zoag)fJ!d9uNAhBMdOoRL5A5dV6Us&9cyQi> zlhH}hDzZG)&4$C(3~Fv)D#wsGYDT`d)ozP;KIyl4hcb#@LU1#zljD+kud}iWi$xks zTDJWzN5>T_zajj;@>t*r-}w8E{7?1# zx1iI_`fW>?>it4pA2WrY(RK0xUOjv4i=}h%hT6EYOzn7m8N_WsSXt++@5pzs^J#i- zwwOh>~W%O5$O#e0ATDt#_wN>f+koCc|I9flFGxB{_Ea$bo=UsKSDYqsUjQ&CW+Zah7t!`Z z!+}<>$()&6#j|t^`;mTF3f`*9%f5O6qj%p)_L1Ud-FFM+Oy)w?sZu)@m}VF-fM(1@ zGvAjI3(_{wizJI3j4pDWR?A$P%t(W{*>D^~Nn~zMpe4a6F@?~SZfLbmN6mGI)1&dy zH(eHw*vV7Zt$Yzp_S*Z&D*MTByGY;sT+m4~^K5i!*H+yy$;dkA*(c&Y!g0mee@b<_ z9V`+8D(DG{Qf!#1syj+pQW2=Uol*G2uz{bZ2mJ4;!2dt^!4Jb=xg4W9DDV*iTGvwj zwuOJt&E>a*w`*O~%-x7oM3}WT#R3G$WR`R!P1$m#b#pz2x!Y)2s%TqGosmsAn;BK? zI3#y7U!`_5okc*hNtsCrN>oKw>?nf8L?s>^+0WGj|JH2(zFqLAGmntpY(;;#ML%w4 zJZGGrojWgL{KrOQF=ct>|1RA8GG$uAT|-d3XrQT@ReOW2A$p}KE*`IIry^e@Uo5x~ z9btWCqJ8W_@06Km>#-BD_99VK%Zrm7)Ik|kS6JH`s=9UBVnQ$>*-ki*CweW>3Zk_n z3%GJvcVrhKpy~CilA=yl=~e@O%7Xcj3trTpdo9l-DLEL#X$RP_OKHAj?0Br~IE>BC)IyRNEZXqnI;P>HI2a$J5= zRDFtjmUj+djb3L(gs3_pYOY>u(0qtFmb9sx^3Dj*HqkcSKG398E)wD{f2ANr3#Gji zR2W2T#rs&cTsRkw@=8<#WsMhYZ7Ky(9kqHu%HGiieXspZk?Re}J{y z>u#&eR7}N;-CnmBr!A~oxQe#03~SI|@i9GGoM)cF$Q52k;i8ItxCYSm2yV?I?sq~AAYm8HMo#?x%oXGa3uV+2x^_5d%1|q&8np10$~ebP~q;%`D2je<>q>8pGc}; zxD7Lv^~sSH#WUN#xu4o42j(4Ib7jFBS9Yk7CB-sy7FR^MmAk2rF+d5RLUfaa@6eVX z3darR;@>sd_`ND(t0v`t03rF$TI-8vhsWK2!?m{o60Zj9NtpPdm;b{z=s#tGVo5}P zf4{4J&fUkV#={T$QXd&>ivrkAo0GQICnMwiK0CKDNrCz%jo3!Y`aI66jEKlNJ0Afn zUQ|SAq&?fKPyXW1)x*b%mw)gyyi$~S8M^Ok@f+q1`D7PA4X-};`#=As$CNK8D-r^BAh2;6# z_xoS|y;Tx|tePmZ9G1iEP>f#eTEkIpTJIBQFoGGR5VmtOddbka;wnhF#NA8fM^T7b z+0rrjP=l)CSOL+LMpiocGZwc)c?BD_Ntx<4Tb3%3Kp`_FOd|EgRbCFW@xp0ps_z*y zSe4CB*n;fsjefay^ZE7fUoMeIzR&EB-&9_&L)~zm^UMrZM1=&X@3f)gWyAIL^5d{k z$C-JaF=zJ1PhGncmTiT|6$U;8RwPmH870-b0Dm0=2%a^ogb$0sgiut>bbJjnYj3H_ zn9X5QoT4hR4I`V`PGYyrDkzEjv+FtYQ}uO&pF$IU%gb(21KPPtGn!8N~2{6m8E`+^lunEFW-*}q;@^E_3=UoVBN z7-=fN;Ur`>ya)kw_&96~?<5L`O0pti#(BVI!q$#jOE*%q;x6oJFt@5fpx8>q$&l?=>Y(^+z%MC zeXM3=Q_(x{!t~{?zB@@uM7xjkX5)QCSmOs}m6wpBJxU{%zUMBSn}6 zrxtLa7kO$b&=lCIi_6~h89vd~2EebZ!rgvEi3Uar6z)g+8Dx@M65DGCH@J!_2xK){ z-1RGF?1gV{Fv)FtTT8gI)8WW!Qh#3w6PQ@%I_84!ALVJU-h24Dn97QdJ=f(6r>K${ zd7g95jJ&dFw=Vd$K&polukFxqL2*q5$;>P{&@G`rlu5Qo^XcI9@D%^Udg;H0YWz>e zH2!nQWA}U?`rYkzdyTpED7T;182mSHN83fScG@r7EM;Vw=}Ow|f($5VYfZF*wS&yc zGx9xW#O!Uj4?o;rZo}GS2CW0hs*Id7V&>fTPkp+svbX{DKk+=C>!Ux=DIuQ^>z&8_ z?4|KX1nD=78&A*ppZ)2dY4W^mxgOf7A0j6{>48~w$GL$DnF`PJG|w*3y-C3E-V^lR zDW}y^k8F^u`A`?57f2*sQ}!bBV)5Y)qOM*c zMo}rKDmpvA^X1mw;fk2So!HZFSucjskB0+S1mJe~u97*czZ8bKJ5+ZBK~}bVP)U^) zN>XSxZ216W>!T2J9a@iNA4J+o&B!4FGOHx2Baq<3Au#hX2(98z_gKp2ZC=l9KV#e} zC{Lr@-7)IUr&cUK4r?g6u}0DWO)B#Y$vY41ikaXo2;A*R9}^t|WP0U~v@WRsX0?xw zskJ-)G5l+|`xXmtAgPLBD-W(Sz$G&BS{1d)%i6F$Ln%M4uYRLY{C73P|4Vj}*J_dS z(=!`S5$126#rWf~@X=>q6#Z*wSy{nQLFaR=r&8U3LrP5?QVCWmZ1~|mmgz9M37h>- z#B6xQ7Xhi=LVG4ERXvX3=C)e2^`qV5X60P;6ZR8*MZC!;=j9=UqM8aWw%W>cxOM59oXSs5wAbeIkkxA7YK-KHkQ>RER>@k7zFu4Idp zf+`7}d_ji$adc=l7dS=&XpdxO&W!K*p0Ol@q$G7uxmb(rOXIg#k0o3f)yf5`l9kBr zHU@wUjflymq+32C*bLN%N@ekSmYS`QQ1Z5-qE zI^5K%giJ;RXIpY6Gb`h^Z{50szE|>&``o7Y57s3gX@8$e{hzLe{Oo%lk=1;Jc>heP z{{Xt>PFf}Y-9q+Ci)y90eELwA}V8vq2Og*KQqlf!3#u&p?k8!yB ztJsn9=gcO!ld)d5UaIldiu82!#5uZ~*T=5EgAA8hVU^6R(hB8FiLxW@bsTPfjNwMN zG@C#tm7zK@ea(I=oMW$1RvXlBIVl;8AT$-Fn6|6IqE4HdiVqW4Da&FcXK*HFVy@wD zG1gY3++Ig}_GlXn zLd1tO=TwLcc9d^KMxQ6Gu##RkXA!~y>cfGapVW^{ExXRC%a!tjs>jb0`pRFw`lUY( zQr8gn!=dX(NE2?^6Q8r%8YQ9I@%O zz=WF}!`%gn6e)s47ATs93aM^2OgQv#9j@w1IV>1iy?w1mZq}1>vtkpZUGaI&=)I+Q z=T2+^)9O`F#XgMR(UkV>)4b`eH^-WL2etP*3NcevY2QrSJ7074!vIaI2|>kVW-U1F zvNIq)_Hms>q}pg^FJ5IvMf6d;1VGCm2JI%+_gTt{wQcxE@yP4C1bdn6G%!PNh*lUV zeyZJm>$872oBk&oK5XL4|I#0OTX@skDL*s;xV)wC^l874jAvZ+ZDH3Xz=@2WM^|o++wBk^N zqDD1)c#0CNn+4ID+2n1Jd%NI#w0CH7x53Q3)y9eiXl=N#q(pRYsl{D7Td3L)dYi)B zYRo4izO!8wKz6o>8IHpVWKcv*af}{&yi;(cJ-~TmF|r&aTlommvhTp9qWhC;-v5Lu z{BLO1{2#(a{s&&%wE+IZ%fa8i3GfqkV9z>?s&h^XvwDBL>0=_wrDkjKs%Ymok|;`c zO6VH(TkLqWbX6)jtHrWvzydF4p0m!l;c9NL-b*|zR}vc@L3DrQm*4h-;HPEE{f3{< z@+z$)>JOhG@CU{a_z9We^KN)%AU=$u`LhYUPL33yB(LZYg(o~|Cc|X7xWbnG1Y8ZS z=4t_(h{-5vZR2KpJ_({5zT}fu0YN=#7j|S)yCM_WZe#QmPZ=eI3^Q$qT);(9>_I>H zT=6|uzIfhzuBy2di>+l5RcX>lQu3@B^PKfM#@F~d%*Wxw+)TcwoK5|!+-3zG2r7$W z|484xLCk$%mq=9pI!}=tZo}n8W%IZLRUnlqnN^adRlyuuhq;fJAH#G;o|tVZQdiL8 zHAuB-7flpYq124Xl1SS;5ST$I8CirNB6+@Nc6wj4^w#R;3B#2XA7Sk>RA1(YTL;o& z36+-V7);GwfYNe-R)Tm>?jWsBIf5#-{&#D#)-MV>3p%sO-Y{>z0a=wHQcG>WyebP= zCUlumgunn>oS_aF>?%z}uqps6(K~A_F{Kw(j$ve(m2K~jDlT`9DG2<&mit?8u1k9U6KGu;kT$&%BNk^j;fOV z{C?^g@enW8BZlzOliZEdCjtD^1a~XvFMH!hSN*x;JjQUh;XaIqTibDPw`FTL%VLRinpbADf{0ruQI{{?3O`eC{CxA`peszO7@2d< zG34uzG2Bd>sp64%PvI2RyyETZ_UJ+FljuGh^irzzD`|OCM5(p7V%-v}`JPb}sEY~;6lC#z&N}08|N8p!F?a0_JMw zST46ItTU#}bE#r@p;PV9joc5es*Z60y0QY6Ex-f)g1X@ta=47q3DSBwj8$z$Y_<4o zvwa|LeP(qwF=bh>10ZMyHO+ZO<~PSy(P z7-|!D^O6YSBSdif4}3D7KE2p}KI)$%`h$A=(P00V+u0w%GJoi<@w$3UlI3kx*?s|#TwLooenC90PLBl|&&Ar9|s48bJ$9l<2DZisc ziu_0-yMe>Lzmp{y7W_(aS2sTnD@hTabGi9n`%^(dsR>oK-m`U;QZ*Hq@!^hrkyiMc zI#f%URWYj?9W0olV^Fc$YbntznKLQNIcqVD?#LBhHUg?>Pici7reE&Ivem)m1F&F@ zHUR=k0aFYYN>nB*Yh@YAl~{9wz2KHCk`mR)e|sO_)YbhkHL|krdTaDWL^2n7YY)G- zzNPnpD^vp2fe5#(nEdZ5lfMI`pD`K#Ynj+jry;JJ4G%}J=fMDeCerh7$!DK79FIo* zX-4!o3_7Hxu@DoWnoOB)gS|X5Z~Hw(k(Jei>@%*Q)HZmzJLEqN5>FrEpQ!;0Jy=KR za^AX#oA~e&zr!^iDHWuoT;YPZLF;9{!k;qyy5&e$W#>bjD8%E)Ziq!cE&Tq?wZFGv z__6H7MW{#tZk}MP&_wH zf{q~SIb1R`N{Vt6kHd$Xxj~U+JA}@NY%QYHWij?+%;3WdpZ72*Ny#FT3Yk`BYHns6 zCNI);p)3Zta%fJCGh}8vQ1kXteaOa&GNYp@f)pHTriXWmZm84@MqQQo=Ro~&Twh}y zrHCk;v&`~1hN-^BAS*fqVL6kCtT53I9E2ZbQe9OEgE}CcV7jo{&hLQQZhHkJy(+g` z-fe=q?&(^0N5k1ImApyV+t+mIo}lh_4E-8%42&+UJM_EOt<}-LQp>PL0C5E%W0)Fp zIVi56Fmh{ZaUol{+Z|!A5DzysxCvMWdVfu6^FOW>)%7@EH*e?vaWPd5W`yE$xZZ8p zLq`8Jt-I?y{>yL%|JRrCe_!7DpOpUp0vvu>b^SE&+l!8V>MuoT8;`1*r?{DEnW-h* z)oSfD)YR4YKCKf~*uvA9JZHUUL4gT?ov9bSAHNpSxA6~fYe90e=KkFCKK80xsD063 zKK}pHF6H8J?}+M~7WmYP{#I) z_d0C&xDwrTG2<5Ny}$2Lrblg&6p!H)oGJNmV-l#KxzsEAptY4zWhw;;GTQ%=MP^r8 zKpMds3+QOeD2IYl&3me>_E4CMD}Tj&Y*NeWr)p;s|K=O|2@T%~p}!fneV)DiPj-fn z+=buiU;iC!?1$kp9xDw8k#Q}Xft+m>!A|Qd z;jGH2nc<=C+I}OZP@|<}NknIKakG}5UV?d%L2%)kSFL(Zus?Vo3wI=ttpivQR{F>^ zz>}~BC@x33ram>$-j!fOEOn=Bbl;^n)>@BVzZ1wSxwJZ$_ZhDIn%_8<_$hFDoCmt@ z*lTQmatnLi15g;Oob#N~X@y58o;0Mi-bUSbUJv7{1_=CWK--z2*?y6kD@8Vit0FAA z8w?a`&q~Ev#WOOKqv$WVs(yXBJKhdG0) zimEl0(_~oZ^tqe8%wDcpc20e7wqmTbA=!*FsF=V=iEN5RM9J5&w6+EtmhU;=GdmQ6 zOY+!ZGrF8Fw?uU*L7Ez2P9TKuEI;EIKGb9!kttGyHmVP5zO+&CejMtHiQcJrQ>THT^S6z@^+;UI9^2tN%p zc?W*+wzH(L;lG+0S*~e@VLG-`_%R&bR-CQG*$LlEmE3Zdrdjs!gDh}`%r6=7MJ3mV zALe2RW;^D`e8=1DuVcK%INB!6j^REmzGr-&Q7GsWXuEke{&n~$xXP(=LMAfX-f>xR ztQVRjXY`!#s_}JrZzLCI7cnJc2}FO-cjJd8Aa6ckl|=IUOrffZx(-8mO-IKNQ6Opp zP_&3FO_8ipUzY5uR@8d9WwfWiT+KX6LDouu>jakl`CsZ2O84W?Zny!e+-VPsU|P#D z^%(Yb*s=Vvx5ThNt42q#r3O2xYxOe3FnNt`%PR8rkbK%rlhSBrHQDP97^?NLw2gq- zaP6UPIXbV|Q32_QJBNgcO>WOf;iZMReEOfkssBAxIs7J4{!g)l|Cta7DSt1E`H`G= zN45VOs>$CFl(+5Codowh3T~6|?mhNv)2(h|NJW(wnrwHN`EufHj@C*o-h2~$gxKD@XL>GpkMP(3H@~v^qM!z}!r6 z%XV;$Nd3&{$|UP^H%#5t!)8WH`?F#B+}(*b8MS^e4({+rNK$6j_c?{~bqqKEI)(_zx8&yCcOEFD3Yii? zL#ZsQD1{ceqMxN8bQORLm1y{)E}^+@%GQO>Vci^;l2T?#I zqay3fnvv&hrVaDqtqO2^bd-ph(@edyp&+L7Y_GKyi;1m&Vv$7{^x=oSaOiD{xhB=@ z*)R8Q8PZqA@O0g|wNKB-qCXP;?w$8QKyLTV|F!xU{Fg8j-c+{@%i(V&F%O^=I|u4P z5dGjc{>>rwB+4~iYzv`!dyfn5Z(>g;e-yw3BN3UAR%wU(aP8&w3R&-DnUi@*Um$a? zviCq^ZU`M8VJ{C$qmOu3uB6c)KSWl0t=As%=i>1}mR^TPZ-A~P+9TwV!oPEkT2||| zw@z0vthMCf#kJf;@WDY?3;K_c*r)Y}JhlcaA5{d|)|Cj;bWitVEK!7cAG<2GUet3f z+x7!h9Sw^ry-W`m-m^V@pG=}lM#yY}PJ3Q1P;+=^FndRKlI`K#h!cJ^5N@EkICR(; zZs)9XUM|nOwHKpwnaK5uS+C*4ZK&zcq5cc=Y{q#<*g#cvFV--i4bwU;tEaqm|7@=%;j+A9XPyZQ$jr>i3aOMPc6Wx)G8mB( zrZ7c$U+hlck{$pBqM~I$53DaWSEy9mZE$(PYIoKtx5l=gp&o84Qx#vwp=xG0XUtr| zK$Pq&)9poOMr!#mQ%8=>Q>AltpyuA8V=Zlw%#0art0|pH1Gvh|^{-=m9peoBp7F2m zSyGSsb1!}Q3g*w^H)|6sZ0^E9DPdyBmnP8ONU*@;~U) ze{e3PrIseO zHMO{suiKHPcXaEOtyUCV`BY6rCOrbIzKTl{?Ly1 z9!~UnWESrN`jD1xmBBjxQe>9I5;m-`gkGsXY#~+drPqqlU^Qp>vUkTvUT>#^^yM-D z+U1i)Mg)0gz1+vBqxS~e9KYDHYTW~<63UgG<_1$AZdu;#b$fK4E7_-51QJ+EX)$do zLQ!Vq$}1W6kHg(?K-F9aRPZi|r_}LYujs`I;&WiZQb&M#r>sb`>MMjPIcJ`Fy6f_t z@J6zI(2mS^XzQpVvI>=~47QL@RTLCCXA=SCb@*$zseT=YnTbw;Om?Z-P}r)Dl)9#$ z3;Rj{V#wn6nJlW=G3@nvsiLaRiWNLpt=LIQRj`zSb_pqKVUs|+n~4hMlx$}YQcI*t znq6Lkj{hiKq;P`E-mAbURP&!v53#`p@YEGU^)>vflN6~l>eu&t&pET#hA8pfcQo&L znQ#gumpS)YhkU)%-F$py`1kw$KIb`CsK8o;we4o1+p!HvV*9+@tf{0V5XHIHClshz zx#=)C#AGdWx?}k?0=ja~pELUHiG1&b?tS^6x~V@`VEmU=GMBBz2Skpa0*n2J_(9#n zrJ?2nmj`~HFu22ex^uj$@lQ9&#j9Kz>&IV)5RFhRF3znP3cC#qCUTzi29C zJEd&y({jgb;5;>gX0$2C;Maj+5Wpa?A9p#nKZ)*$caWOw{KVEf#okEm;Pb9G*wP#> zp>l!mteTOOSJkUS7F00?6gu(i`+HVO<1CNhG@lV}qL9PUDj74Jp*#2DmMN1+R!K4` zbM4KCHz1^YB-YcPsi8(WBd-4K+6eVVcY7Lsfp@->oYx_P`_pzdSm+UE`a~!vpMpcoZ z(!LIJ^Fxhbi#w(FmApAfgZ~*?`-I;*l`;$FIaceCeALx4?iE z8xB5VRvv*_x*QRfo7uyvq+4tuTM5-+jboTdR-$l?;I}^W(MQ&!>MvZmeGuBHe-+=3 zo!wPJd$}w({r*vApP2;ruhc%Re|W0gO3(`=*M{8g{mj})UMru4tlVBsCZd$l{tMNx zB{I-VseVF>i?qEK>z5LXS9IIIT|n;vpwHx3*nnj5sLEoSo0_(5qe|<9S#6PxcAZ!4 zz;YLLQ!l1jq`H$aorSSVTk|2zI-B^mUhf7o(oWzCr1=lgv|+?q{15aHUN z4^KONw$sx*o2t z6+wkQ zvQ{kZUu?FT>@Bw4@*25#E{`(K>*l<`YsxGTvh znF%wAl$jEVyjD!NK;ap<(>YP(T%>H4aB#S*;!qPYhN5cbCo4+$#4k#}kUocaB4N(*rZ=p;?ZNEHSRR0?Ryx4Smg)Lx~ zyyt|J>lkKVW0;~LKkpfvYD-FyshN?n_>R)3FHFlVnS(U{8(I${YvqDAMBwu-^{F<|bW@eCK zl2*$#S?#gA`CU$qKk2(SMe3eP{%d$M|K?AAfEWDFzZ&@S8DkgWZ-9=xy5 zoj?3=&sod8r-9z87VCIe18^G+i)js^n215dDZW?Xz0Q-!no*gxTpC~-ZQ8vEe>#mU zY0CCC5g}But0ci-(|+sB7;Z23WB6;Bnz@0QalU8H%t@g&mD$HocXfsO3JEe-x?@=U z!mo4AwdDhe!qxTeV$-SyP?F=z{^@SRl<+7^nM5$N9+t6}0o~IPel^>?`ri8#TvtGE zjuqxkFnT@`p8uJ+gQQfSt9RBdN*DU~2A^UQ#VL5AtqF^=JG zMresm))|_avYcmjvJx)FWXI>oohwyEs;Gz>?`DM1PGzhnSprt7r7TG@-<9t=XZ=b^ zfiJH1P9oZIrMRAj=E@g2Bk;{im)GI0uW`7V>3pB_Jv;BXePAS$iKPz08h{m6BC=5fjMcb|8e@UG+l|5L2v z1HbwME0uq0?s3gU58A{>miH6;yt>Gb1`FNZhb1HwKjGFna7{kW!gJTREO;;wX+Uj|YE+JDW6 z(pS~tI!w)Un5l|rsftR?iHyfNFC-hoC`M8eT8FCw!;FV7xM5aQ)+OP%_uK6(hMV$2 z2vDpU&8`CuIoyYjqj9X9u`J*r!sN0+VHTi~LaJ#F*A8SMB*__BB5ESg}F*x!gGloC;e)#)S;P+@8 z@b8e9mD_1rT2;+MlOH)N?Y*;c#=;Kexjczhh;;FcP{T2F7!Q+F$?8I^SY~H;|Guyb zz}R4hSzCcx!(fZzU0X`snvN_)bpi*k-`h@>k4yKF0`z$xFKgpF4|k6)tNMP>&=(P0 zu6tt#4wgcpWst+ta3SEk^%gZq*A_3jWoBk#_j=8g>rnA)lEKBGhgm_>zmMLQ4}$*38O^D2CQB_QDE)VY%8H6ymBpTQz*aP`v1{)h|0zz9~e( zizqX=;dQ8py}P}uS{%_u=3XAyPiF&Mi*$WFGxxw|0^G}m&Fo1sibpZs53|vROF#*s zqNo`%W07dC@R6cO*0~5ocnx>gVJ5G=IGr))#-8zG&|bO#ezL7*B!mvdfV+=@R~B)` zHJu1HU1T*cD>#hub;!PrY? zJ?q`EpKGnTg~dW46mlHH+5@UOU^rJ3PW<3|+$KS!{2A(fZs9hDs-g>Ge`Y8Cn;dO< zw01XLg-`F(kHh7Mk#lWs?(aY~e!kA%$b7gO3;ZX#A$%xiS}?ke@orWyKUx&IBUzp( z!g_Zl+u04>x_v3!^+h{fvk=c`f{#|d>4ZxX(;^eX4q?8jF%yQ#G>EmN$P- z;%&b4iR`R3?ZY&(_7POc%1l)xCAf^0YQW8`HPf`l>r)sRP^waOMLU_Q3#gh(S9R65 zVn)rp75G1#8rHp?yc=0kha2=YhKNLU^3`HVP~c})RGlfuwAb*L<2c}oe+&-O!}Wd2 z_c!NUK?PiT4isgkP?9pE?B(JrU&DvBIb?pn8JS$3y9J>j0gAU2P}@C3+K*XC;f(r^ zcC7o$eE8R)fM#Y@Y~`S}CVXY&7UnF}RFa2}!^ZG-J*YaRN=1d5wdsq?JibH<(BUft zjpUq>tXvF@E}uq%4fk7b^+N>1WGGBpjLup7PC29IoU7AqO4UUkEkZ=k?2Oc7;ICmr zAuJOy$LKt^S@J#4$YoBUn#6y6&-bjh_gbk8>)d2NUF~Ug^%2b>CA?4e>*3pzuY^@a#xw=rgQJDDNKO9tuiw=bKMq2`#a&zR-iB*U~;2-D_peLg3D&$ zvUcq0oe}vP7yJWv!KWOW_3X5t9(4SpcK%aN4gB7JFXFt2RHl$jh1u`{AX%9)x4-b( zA&YXIoz-rVvSoYQ73`W(GU7Wd$(I9PZcIOC+ZX^^s>^s;(GhX(hr8>c-DNc+E#_Gt ziVZ$iV?As2uIApNf=_JziB3Jn8~l*8Twp3cSur-mNQ2mpA+- z6xx+X*ru6sc!xwcg*r23rp(|B36U)9$nLATuB|S<-d_`Bv2a;H)H)QcagAo+y@vla zhN~WGHf)TqnfZ>G8Sfd87?#HvuOY2@Q>F4$3HuUv`=xxJ5-C~R2ZIEtP+1Zni82YR z?}*~pp~LWxAqU!SL^8L_5$ZbN7wM;iRNFNQ>b?IWD{IDN6(Ir>RiXB(Z#UygNR-rM zM4VcO+iUCg4lg$z=I@Db;GFpkLhlbS3*Q}jFzUbkbsWPEADumKI;GCJoJa&}Slho6 zrc^I)wnB8AGL{LA0?kN^0;&q(Sa@Qn>C1uRDLS4CAS>KJyosu&}s zY;IWqE-0d|Tud3t@ndSi?;=vj10$^`fDDl? z=*W8qu(g!vw`Tue+WziYw{1J`gWA(t>wUa)ovouI$@;b}E4D1#!m(<~v63?c;fVk77H4I9Hd-O(;lmdgKw)&8CeR=SgO` zL}T?(-pM=hLS<(OUERw?g*eM zGgD)56DA-+fSK_FOJWfYASM}61Th6eIl=4s@VXljt<0Kx*u~Ck&%wZ*svcuH0Bw_) zRitZsv_=tQ4s$0m3XYIVBj7a2o@cIuNt9LOXd2U}IYit!EQtkoun=~H(AM|O39@P7 zv;tnTfWEu-Qy7vuqiKHHd`^3xai;0X?L`K#Bbc(YFcO115M%-&O1=-XP|NM2fMd+? z%v{LmRCD&IJcw=3tw0RFy|Ujha$U*jb!n}{3a&W1YkGZ6IIgb!70|S7p1Jyo94Cp0 zh-!gov5B4QnBJ0EUfL?$iO5-Ao4Glgagv{l=Cr2c^ilG;XnL>X^H+Vg z;|ss=-dB9%g!|GZyE}XF$B@=#7}%=BjoU!$b%`4HX>d)urp27()~KvdR;_P zhL>tiQ5DX+7#@^J=9reNNzW2#%>rLFIf=&jf+qdZ9u#ro%i{a*aKHah=tD(*bJyHt zLfNP7%x)J(8n!a;soWGPqXNJ}1Q%9w8I4!MK@P96)iyA(!N2f|+^-I|INRDKMkE zWRnOq-GV{|MA3Q^mP3KeL&O}jEcKV+wA?GNHi)1gGN*GI0X(IniAwLyh7!dH^6*>! zJeGve0MbDO>YS-e4M2=Jqik8(k;gS_bUwdjyEauwj!%$$cw7#(mB?<{UGlbL3)KMYN#KsPIz{^BA>f z%ROGY&~9#hd~t6DBf0-k$X&SOzvA^@dYy9N=1GlP?gUYKffV9U3E>R##E99!QF+GplM<5(?Z!zR<+=8q`q(RxiAza8C=+RVE;vflEfXNgnWE|f0 zst^TehJSv(d-&sc2+P~?Nx6Of{`nlwSvF6;C@!ENI?dyp0Xkf}LPX@zru!tGMwFhV zgx_W3iHL|XCk3ghHqrhtoBX6P$Bas9yX-G0&k=!x*a$P?H2eDf{X9oyzO1p+WzLR$ z?^K;_=Fd2%g$j3RhbkZf(u_qrL}h&M{XB;!-UK90AhSwu4vH5{N)+EfPl1cfj+ z;U-N*0eCUdqAs8XT-oByEPa;N!EZ{BU{1z6t4LGst*b1q=0dmaS1YF%*UI@~-Vj1C0y@0u~GRuS~ zkP;$IRfM~U3P2Ro{Jbd4m1Gw|?g;07y?g`&-STWdqE!HVBa83@!2GVod^w4W8bPn= z>hXP~n7G}PYh~;nG~LDopWDr|3@aik(zqqgUp0=*+l!df&N)YU527l|l(kK+3K7Jc zDxLlNqIc#KI}<51y{Y$3N8`@an5DJO@q|Bzk15HqJSct?yut+leT{D7$b(nAx1; zJfH4P#FRdpqMK#-XI3&WkLU1V-c*hzy=zl#LM+@A&y&tE$MgiEECDJ&WbR+Tznke> zP;X7Z4{h?sD&y<-=W|SR3o4yYkzDEadfpZqo+^>&>FzyLMBcvoO3(4V&+q3L5xPJ) z*$p~HnA7Lejog_I<;R0X*~!^lSR%+gSBCKY3WF(i@Zn=TiL_}Kw5OeLGf*T3dqom~ z9SM_8Is$T_NYjWBX%}4LlR$ZhbBdpeyNxeg8*qZ0wC9Qi{6(mo&`I=Xsu^bffFZ!3_gn+f<76_jpz41xFCL zFq*ce+Efb=UwqGX;4W23q!!ZeQV-m4%Xt^Dh>FytIs3qjF-k+loIcIw%qDqzvC`30 zUC$o1WADfI)pzfYXk(1y1C<*@cgov~+AU|*Ir3#}PuZ7a%z$2?p!So}mj#xy^DTib)oaRm(2w*xN|`@4#I zl|%PkZrmDle7F?$-2E0_{xWM>Gw^Grc_WPk@WL7ay->*31S>pd!ortWYqf2)uw-T~ zfVdxhczDWd&uL?NHSmGk^gY$oYx@x!ZYmx$EIj56>TYS z@LOk)a}GaeY0`T;8VeNOoG4;E!*3!c6voayf}G9?B4up?ZX&_##2^lu6Jxr?Wn@UQ zNb+f+ zJ83z;@ZwRq4J}@X3)iT&n9rrL^u?#U#h2j2K(^G1Yb=;}b9aNA^9)fiff7O?1SN8w z=|yr%BCoG6^a7}FWmig0IeEIAO*AY;Z6eGWA6ik=l!mp8x9}H`=q(QYK68lg6FC#{ zp~C)xtoRx)7azVS-zdJsH>E7cmsxAqEw$Ifxo4~pyKRxl5{6V@*k9 zgF;(FA*ET+a?a>c`?|c<_@yYI2&Wm_BJ9T@+N3kNI~7{hiFD~?<5gCk^27kZ9^g=H z!)P!~vm9fijOejD#@H!27DP@t0&_xuo3dogD^itcmw0>cgCovqK>-K_9yw=I1d;g+ zvU6DGH4qUetjAao(#(N{?SyD zzPb|bDV%JlL~IkF&*=a?qzMbPVB(QVu1(vlGa%=c5+YzUWewM$pBs7OF4?-z527UMS%M2k;}R(5kNhQa2A^{hOZe>C%X{o3(E8r&naN(3@}|bE zVXcS|!Q9P!ip@z=Xd0%mRWj&ej$RIm-30{}nYm^sxS1Gl6!6>o_WJzYokVe?-cy7l z99aNnu0>u|;>fw#;*W75`Qx%g(hIdZ-!-@8-fI%fQgK#GHNpE2;rc#@yZe~7?05x8 zN{9R&sNEBgZ?x z@SQ7A>q>SJU!7|1OjwwdSUqI2PSew}ZS>S8dumd)UImLjl5@{wON7m7$wXCdIb>87 zk-%6<$?j&h6x*g~TJjam{}*5c*v=U?!(!)FQh4#Di4r#DGzcsa+#54<*Op?lh@siR zEq|H>`JA)z%7*%*A6*(k1P>MwZQ{?<-k;|*E5Ve*O5N|_!^2s)2@X|dQ4KlA*Y8hX zUhl#`CNmY6rKUd8dTpIzQarfbG(qmn)Rb66m|GA<2%|q_ zjOli|c_LTpgaLctIfjRao2neGiLg`p24*<1Xbq%u_za%YKy%o9PBV&Nz8E)aoPJXe z-HVj{yiRirC!aPSHJ`i*UGV98kWPHL*XHM8;l{CbR}^l&0)xU9&P!CeF+B=(Ih$lR4v z2?%E9#-d<$Gk>0+!xMZ=Sw%%K5yw(yR+i+IfxG1rQD&~4GWaFi`^(xow!Y%~pup*y z!2$RNJl?B8X7@jV%Zl`3oQX(&2mwyhoI&9D3=>A00(;}8P(~MYX@00V4fAu@7(QmH z9P3u6t>&z`<62HcWz({N5FQ>F!y`_&Ih7CL#&~FJEXwje=KE>W;vDBR8&g};qia|0 zO2YJL%uv9Y<9TM_EZqd6+s=e()l!j&G*ZRmArxYRq8><;g&-5FcxMk2MNG2@vgvc0 zs)$Hy?a|?8&(qG4c(A)G=oSD(1PXJ1KgV>p@S{m@%#5BcJTVb_o;K&2d)Gb6)8g~< z85Z)EQ)*Ji(Hn6P=v<+)Yei{S2U+u;tbxl1PBU*RVz59y^@l>=r$2|A&ow1t&h#Sp zISdp`CeW_(=)JL@bC`#)3Pt$z6z0X6^}{`!ESO-Jy|qU_n%d|0=NT3@BRqlaskJ)C zDMT&hh{_5m#b8dBK-G>V9ui^f^BB8JdPL60ev+7y_EjB zP%RB0tyH{9STldpWS}hiqN-@>Do};0sL0XGTFIc3B{gQu>6zvT5KAH)VlPm(fO8BB zJLkY)K3c=$AtIeSR7Gcol$)D9=Q+@u^rlB^O`uEyNRuX{jXs~_vp>z8n90fA7Z8`r zW$JNGa~n2^K51{NtZ3}+0hd4e-~J!OElqr@x)wFCMA87&u!fhRV~slZuq4&Ana#jV z32V%G;TmP$K7~pjdQGW*QQv3jL+L$LRhB9uR$`am*zarXQb6GHJ0}%Tl!2TQ#jSJu zkl!8*G*RZoL~vqo2DnrLfH@PZXat=z9c@<8A-mwkizoU51Y^aJme8WU+o?t?5#)+? zu7kFUy?7y0e%R*Yqi|4wCea%|dOLdSO_^cl=dfoA*yHjICa{C9XzV58DQgS&ru}HW zRfbKTC!?l@K8Byu?t7f+F`bg2Qs%{_qE3oXl4|Ed^R6FBb;y4uJS>wbMMPP3QS`3` zEN==aUif~vP4;gearO-x4_AFMvffsek{ho7Ufe=0@4pJ@hF`|@Q;Rpt;8u@HDnj_i zGU6L3#aASvP}xIsAI%YXKiz_OMU0x+couqbMebcEE>Jq=`(|*jFuxGky`<^T9rr?C zLSyd#o=8DtZDl#t?dNj8ythrEhe;U}1IgXHDh9qs-oLgusBfv?8HUmwIi zUQCbL$1mc+YI19bD-@fhz0^>b<6@gTmbIR^HuEAi^bU{kr6Cg{US8cisS>KC!W`is z(o&Fb*n{6U6*~fzv2KuDYcM5Sbx#uWBgr7(ggX(4i{YU8qqo+y<@CCirc{}vi@@zG zG&@9CM8Oh-GUtX%+E+wHGKwhOy(Mzynu|j?Phd)pgjN>x-exPn?s1lxp5nz?wrl*j za!?tY@{{eps`S?T*ldGu=71cbLkh8Na5(m%L3mf}UgAKD%=viCp~W1|yLO#WRZ{H(bBR z-Ll-qRj&soP?q}Jl;!HTZpR0J`)K8ZOa1kz-s{%a@SbZ=^TAgXHyKS46Rxo%Ze2o} zzE_~{?a^3g41qx@)Xu>9{8)Lzs8$=dI*4_Wui+rYE;_Q@;mfwNJLoojT1+qrbIaMKKkN0OhPdY~oiy82Y zBQ7*mBPHVKhN+Lcnua)zUW2rcXCpRa~_QfCM-Qp60r(3Z#V?y3M42CQiE+*d-uSsF2hrg_%cf{mUA2h_lX3`$aH!;5t-bZ*tv!zRI9hM5Q{X(u7-OkCBBJIs zw_(wx%-gaB1c9NVt+(TG9FL;^`K%OKRWVMuUZZ-&JM3h&53M{Xft(z&lj<}%o4lg580{+2_rW_RK z$hsp7psG;z6w^2Fbf1NF1}wZCV*i4xV&NikhENEotpL22bGwf5kEX`h`8Bt?8ehiA zT;{XAIloTn+t0c;0hd9!7TLFJV2^9oUAD_GcXJ;#qP8m&H^78kZ@2YX=+!LI?RZ-j zYiyId)mW03Z6^RivW`j}_}>1<-Ooo$;=93z$d=(I#*Mp3s?G>s z=lHq_JT6LlR&H8z%y^k}e-QQW0)rq4t;$)82!m_Nk`$%8ej~PGAEGOH6tX4lMB#JU z3xZ_r2st&wMI0lkc>`3qDQ6|ElDIKesnS`s;wsi%Q}EjgDXs|$7|u-0d5hIriFpNf z0D>&sxoL?Lj;4=8kETrrx-f~D(8sDD&OjdiO4sG7vKv3;+^sQAs!T$^_iB?KanYxp^vL+ z%Kokz+Imw0YnQe6<+mJ6+(d(`|5=aDx&T3hdK!dFs=5EUh%}xYS0zKP1a9vv&Ho zL1jgU2Cfl#{gfbN6GpW~OlG_?;8l}V0J2Akzq1N6in0{tP~9>S!_P6xBQ@vn1d7G5 zs1Exj@04q8R#moI`QZ!O^p zbWFC=I=}^)FRi-%=l-F*%v1?p=jd}2(n>MH#3_qe)Qf% zKu2TYqn-Bk`}vG?C>75*h|d{8!`$8;tv3;%tHh&&?eqINQZKVR?EA@3L`lDy$$Z%O z`rX|oKi*XJtsM#hPGF4U25c)2(kgV;oueJBnrIAy;;T<@r`p%=XP9m9W`6O*!t9)8 z)4jK*(m^(#&ta3BI2Fs?{p`Jedc29soafw|vyk0R?EKOjgl)x%0lc2=2%0v|IiBM* z_w$@#;TngwL-`O-XySRhIz_G(bUTWdh~pke+z z?Rk##Jl!LpyScA!)vpm5R-Z84?aW;d<<5hf$`oO|zh4F>CP6Bv^GE?r<2g~5gi4fI z@@$BZMs?a&`z6QqQqz^8i7UBq$(tz{p|p*pv^0QMlW?s7FKp^w!7gHM&`X}%#b3Qi zfL6SLUtxu}6t!!SLB6CNmt=u1<-Rq%+)5bz7`c8I|O(OI7e2!^uU5T{( z#6%?O2Z8MUOrArvuZ9075IBOyObas79@`o{9ukP>xY)1j?TomobTUjXU%BaSr;Qn7 z&Zcs7X(9(}?|O(o$9#@)jyXN!PF6|~Axpto4E<>)=GwEM6JZt=2>BB}C)xS}NYokc zXToim8Y@U4Z#~}*JE!OPf5|$Tirp84T^*%$F^v4Q8PE4ew6`uQBK=TI(0ooCKI1w> zIMtkE;CnGU~)>Kq9-0eB$oEvFd zn-!*WlE|yfHHTtpuF9z+JU{`7KqMDGtrbS*an31h%CoihcpNIiZwIT)@7kEtKc6C2 zYteGP5i>NiAV{{J36N6r1bO&0dw-7SIWjFYd=ZA}8i3(4oH5-)0#o{|(t7%FU~`Uh z%xN{Zt8MUgC16K($91sKzUl9dxsNl_xaW!^(I??fG=tn}PJg=dpq$Xx5GqOAEW)hf z=bY!XVPvs1{UmG7k#Ro-Atz7pE4ihf&d)JYo1vQYB|M}vbwrrkXPeJC$Cy^w;CxDP zqiVtl!`+_mI_06gi9Q}ji~c;vImQ@sDbm-xMWjwek3<d2IrWQ| z{<6djf@Axu*OjqYUw35gE@e2cfXuoYlT=8=m2+4bs!%57=uAhG+4Y=p&T-DOLQ!jV z&IL~NOA%uc?|#~xHghQ?N0TPnM|}Nm@59ENHsj3Lc7lZ*#46H+g-Lia!fhCv2z#&_ zk>{+e9!s|v;lNB_uQ0TUFdCDeMx!>Gk=l;5XGT)@gFHOo5DQROD7Y&#QMdqfT1+cz zXA&M_TEH=g5M7nQO(lZdgeVf!ySSoUxdgbUVbLXS2|uS1g^Gfpil+LY0Em$V*3C^= zDk@77c2?Pk}2ynvS;7BnIdO)`!#lXKI`;YieB(YiTJx54Np;pmN2p;CHLVLv6FBc@e>_bOj_5&y>S#55Y|7Gv znS%*JAvDKKxnVgc@M0R;KFSCpTe8;trF(9oMbI?d2xPO2&)3v*E0ZXyfO(01-yj+k zTx$;k{B;&^nTcM6(nKT(MKI!J>-YLs%Oi>_XLzqqKhS0`AnFRjtJ-?u7^C7wR;PWb zbLt%RJJAdG2jk1&pSbnjH=`hSAhwaS`*0d&!@0X=P^|#W+&L9xE)1C%3t{BGJiUpB zEzM)%ubNGiK(~ZK+PcYS3 zTxOH5LS2~R2^uqI?Bulcu@KWB4>T5miX0hG%W)c}tvJ(+X83|IU*duM#m5K=;SPvG zn$jcjdN!ve>I91m#_n;R7VfL2fN+39m@?cdTtSG_m(q=bxQ0$vzo2V|4yUX4gYjW=&T*b|xkDaQUF|P0_xwR4DkS@YjX!Oxab|6QB^IWQjy`QdR*oddsqrn1Nv#1mSx-c-QhK z{Uj3`%e|jZb=7uQvww07^CCc}JR|gc^Y*gXom&0tG zA9ePBW2!+FJc!^`45nF=rn6t7)bJ}Oc5kQRLcn6j{T7_}Rx@lS*LI3tCil#Jm=^Fb zH?|b5xjC966saI8O@!paIhbOadEzo?hwRWQhsYyF63zk<3bfvussivxXOx>6In9}e z1ai62Ap&$}QntnsG68b9;TENU9cMnw*r1cUiu~EX`afyu-ViBqRVA_UPDRL2G1_lt zItY_n%mpe6Uvo>OMyghP?lZ!9Pw|nHU2?aILdeIkY=Aq@X+H$CLHe;c51IdyQI@$N zx$es=L83azEVTiuO=j?hEBVT3SyRv2{-%C>3^TJ}f^gZS)8eTDhP(MR4%Az#yvVO` zCo>$Rb%uy&#$AAH&U1|OJjXN(O8FB(6462M zZ=y$R1QXY!A@VP(p)4Kh<-NLVd1I}Ry*U32&TVlfsZO_o@>Xz)2sNQDB*I95eC!AC z8-sf{xUcFIVZ8dmdgEB_V~DbNKEWo4O!N+PD~OTUITpnIfzt{2z0J971wnwAYN zjFqm(055-KQ>LKj^f`U;3d(;S9u!7!mON(hqH|8&l&l%Q>e1R;Yj3@O>h105$05g| zy>A~uxruI)wX1@-tE3RuVmWP>soqxXKaqxar(2Q6FFlrum|f@ekH zeeoDwLzhcku+sR}78(0?h$~{leVUIMW7?e4-Lu=s%(=b&T%OTV-O(kvDfRQEyr!K_ zo3(&bkdg>WI#HGvW&%7agPw@Asj#l#h*WcC@8maX+P2`7|5`<~99}ftH@I}~66iA0 zUOa%bnu9gRoN1xUHC2RxGm%JC)BneAwIuoQw&tN{SL$aFg&>+F2`=CLOV_&~qto5W zS&GQvy)av1J8E`8JBn-j4ekl*C8Y5cXbP}!)BRzkYzz^+di9-5O1>0oQZj)28k8ZH z!_vr=u3cW`5}oH8ia;79Xrf-K}PDk^2jV5T@>(jnLT(_3s;uVH7!Stt}3S9(!^pj!ZSMol7hDs#MKQ* zOPsk4bGg(ALef(xf(cz)OH{%x2^e>Z$rAn6;d68l(Q|kOIK&py@HS}P0OVbJs$mW9B+^I=s@C<-{#H;>b=^Ys;3P*k{H{tC~vsd8(k!H30H>8}}lH_K; z8D_6-*!pcI(<8S=vBt$|yCk0$=F3IDj8O@}@R)PM^TSe63ufN>;d^#B5;>>b zm5JgUUMSp1BoZf0g2)&yT%g|-OptYXk`1!Ut5ETzA4q*@WI-ON$1h?%kdcW<8E0h` zYE{pw*Ap8hovhJx6fPm(?KR1`G$6w3ZoGh|%ACjQ%0+9T9TD1zouN8+Ds{>!M5CA)|rh? zxb|!nYE_?819827+p?4ebV;F8q6!rT#1kR4Xk!~6f@@E4*Jol2t@dFf3XzJcDUd>} zq2elZ;{wCgC%fe1rOl>mGE;_|8@s2f>1F@e+Svp(mh7GuzGgk%yExhq{qz?($hi&% zywGXLv$(I6J{BTmE@M3U!<TVpY_}EvL4thmKnY!xdkXfW^A=YV>%euwVHEs25_9rzJ`8D4N3y zDlA?$i3F(NXbjME#GG-D;=J2H7XmM`7~qoRqX}9L(Uem` zrkIGPn~yPr#*9yo-j!7)zbnl2oIdiADa9>7aHsRk;lzwX-nu4aPX(Wjrh@l#CR%QW zFOYwp2kzuPNABsWDJHmA#h1x2m?Lgue_BI(U&8!7g}QwoL6O;i5wJ-%-BepY4(WRnipU{YP-rU` zLtQ&^4~V|a%>3fbWX-Wa3<;kaDo4|<(nVDC+i!Su`}{oL&-0v_9J(5bdoT?VYOqP+ zevVSf9}!1y%6v45ZIjuDv#;)Jh9Q+|KVb!q;)|^&oX|*k4)=l8%o^70Y zajU*jBXaGx?+^X`fxGTna)*JQGsZZTXHP(NQxR?qVNsSQe#$(DjdRQyh3&`PQ_o8a z7!hPM!ls*>h4uD!G!cD!h?+geI7gfwHhp@$55oItfk!`73x+#v5^*+MHAAAc5&%3f zJ+GYZ@qbgYOI*AHQQP2pp@CgEoM`2*uG5Xb(VXFCeOdE+Qw9(Y z7!8`{zK_Qo5tjTjR(pZc-4Z8yA~Wl!lTROhs`=5itExy>?W(;s7Dk@!?&~Yl@bHmn zn-)+F99ilth(q>FTlvlzDA<(y z270a9Gs5$0ys#rLiQOyjF+ONOucgzgnvho5`->zG^ud`^2)M?!^31`^($KLO$vaDK z{V_epGO?7fAUuL!?EtG3$v~?shRc^CZhcd8s1^we$1raVy?NJY#FBahNK4AkIG>Zw zc}@=s%l;h2TNWa5;&X=iG>fCzr=vBMqosm)5;$T`ce8kg$9!6oPe*Gk%-n&>ys#0T zSuTZB%uxVEn8k1xn7+>%mU*<^y0A2nd@W%ZbKW?DFQDl3u|*h#G-YA#Tx$GGb@oyl zvIou)Vokx{Ov_;nRlNB`82|029X{O%s9H_@{@-R1BwugO{a|s zA_om7P*Y_vQ)?wl2Lmvgm{%0*-XUjoDk}&hO=g@Es}V-Nw8C{`)?=H_)++{khb}E$ zojYd&?)0p)p{*%PS9h~{jv4t+Wg?^=^RjjAu5gV*ca!d3BKU2|y>OIw3)$^m+kvmK zBhhbPDeeg0`vmUpbG~q^_#!xau|e+MxFy_;{k7aIi64;ou{UWo+ONnkxFf2$M`QM; z*#?1ZmhF83_P&VfzG)n{*e;R};1_|L+|d-h@q;Bu)6+R9a_@baHv@4?U5JHQnC5iO z{4O;WRe+V?PXwK2tG|BHA;=u}INoXpa5@T{LTE2L* zK73kH-K^u|Cm7!kbqx@62m|cv3-e{S-U*!>;K2-%qLQ*1O%XFPLn#ydW`nsJxQ}1u zy$Zi(ZtGF4Fk$xz-xQjxu#=k1aA6jnf*9iG#7tJg_6jUr#*TS&iV+?$^CLNmC5B`u zklC4hZi~;S1x>IJq%pOI!z2)M#te@urMP|xvlwvUDJk+0Zlc~|oZ@U_E5Wy11WcQ_ zwt|9V?P%7XKEla|d(0s24plHqS1^R2K#bf4QoP!#!aZjFml>d=p(}Tx=n8hQ^Q4*k z4Ujud^Ei!V65-MFA^=*2XWV9x92Z{|;M96XA?T{Q&=f6D1ciWzp+sy( z$v#@{KTe$BoHltGMQ)oqXw%!%C1XHLhcH7HcIH5}3N85ncgh2Pu%kMout zFd80kZluy$lkyllC2#OHddQmT=HA|L!?iA2I(1^kPM)clR6q_ARJdXnh178L?s-j%8LM!2oNM?eNOJM zAm#Zv|MnVRC!6@+K{i#HFAK*TI)O?sEsTWd{30R)Aao#T9-&-e5E ze4fuSPxIknff+F8>1@u6TUXN}>|N~zBaaoTm2`{OvXx%$hnp~ZF#>`;*qzN`7IXR- z^BnUW=X0FrJcrHUbA(w~&}5jvgrf=d#u=KG(M3sn#}+I^TrSI-q4I_y`A|^AQe@Z2 zu|zE9R&It8+)0{v8;eS;2!T7#wn+As1W@l$M$`K=a!;{)ie8pmk%PEK)L|e~4q>ne zn9bZ)J$tbNV=WoUnY?8smpu~}Of_!L@kWE;;s7iO!U|vjBp?ZShZ>L6YvCv#kc!#p$X-=#Cmkq#4hSYu`@5Q8&rj6{1q#&dVCQB|Ulu{$+t9GEJon~C*9loW_L4r7WpaN3n z40U_!{q5+VdjHhhr`{eahwvftXzl37(fZN)(c015(e>!sdJ~nEia<@e#Ps?de{si(Jgm70$zr>(l&V;xP6#|#Ex|&_C87f^VE4vWRpR!B~`OOJ2RT%m)$>GR}NC zIqKWql-~{k^K-<=kt0*7l~+>)O6pO6JRV2ys>(z@=bZC7{eAfR>Cf_R*~pj-OXfmc zjo!nv+EkU~lK^BvDcq_PJ;_N(84T^{&1FMhME1+&MVgzbq_0E_ z4-m6Lgqd0+3I;!6RKWbJD@y-g#wrG!NPdLAU>s%gd$Ve1|F3er?7;=WCj?Gf=0ZBo?SWLxy;8Izu!a%HC8fL^u ziyLh(U|`9zX>$az=ZkAgUIRr4r|Lg%RczVW4qbwZ`mOXp>wD+04)Ku_T#FG6A)L(#!DehF^2!wNW zXaV;~%%uCJ_e~|r%U5!P{nEYGN-!OPj67nxMTCNZBF>{4zlfj=tp{EV)mdk*MXWCh z3Rdq#Y@*^=f{L0*;|i004lbiCtkw7Uf2WiF*6sJ zb%JDGw%^Yo?dB(xR{S0pYga&XCi|4 z05I2Rbzt&37Z_+qH!ms?2a^$bCM-2#f-=Cw4Pi_sRI6&YY4bS&kTeLR ziNs2BK4H^JF?|KdZ4*M=d^SNIbjE@*1Od<{t)+uAnCbbnNtAHID3610;1Gyf>Af=- z6U4+24jFS53qh{{hjcVX-TlJXd@6!YEN@=e3r<;4?b|MK9n&fdtxKY$A7uP9QF_NB6tkZ%fi*c5)8U=K@)b!Yst5@{)t8?4ahb4f4$y6gdN;bF7ah_F0D<=7fX!~5SZ%} zkK3$b-RO|pKh3(lLKK$>n?XGL=GL0@t}60&^wtz(;vC})nzkK7X>kaO zIVTaj$&<^{Xby7%1tThLmO1$klkDsB=@g{gSvAA2y9&kUX{S{`Sx1#Sk%!cDAv@F@ zJ_Lc>W*(JB;c~R(X{fOGhI5SPpl3$Gp#FUdGQ&Ai6o|*sd&>xrAU1B)m`}y$!RK@< zCDtWIq6$#-8}*2@gAk_d=(})}Otrm5Prbx$q#7)oEZB(~X|5yTg$W1{Y1am>yv{GQ zu=rh<_Zvn-H=5aN?!abM79sM&q;BU}Z0AAdtt8Q1RM1kE@H)n<#&Y!luj^OP_LWe% zhTUmQA7*3PFt_DjyrN<6_5KpOaY%>+ed14)oL>D5?rC0#N|nV2KO$E00h?s;Z}tkSyD$JR&etEs{Q zY;B=M+`|giwN&1#rAggSQ66LdT4ir~RW=5@NSv;h59YrgUS(-saX9S3aLazYVJjcI5Es<1}j~z4OtyDJ#>#BpRLK z$#{a_pR1d#NoVADH-qkP`S=y#i7~+ zHQTQ)i~QCJDs(!tZPGWFPVA+~OPRCpp+|&7ik@%5VVlC3a@T1#&BmC+VkY}=$xv3L z(z+f?A+aTOM9xkz+}(VRIY~d^I7kmx5e-K02%a;jzAejcpDBrgKv;yEgMvMHF>KS% zmd|aRbEd;@SUisCO;wm1Q#3}rKW)s6_A>`tv-e z;o2hI-PskNRy+#M?3UU?j%j10cxg2yOR}@|X|JxA7k2BVX89&h4c$~b^!lXUve-bc zFp2oGNDs$#!I$93Dm2r^v|5)Zr-qSP04t}44#h!20S}!K!+cB|W@CEQAS+||rDq7b z>1giXj>ImL5&87#<2k&m^sWz**1(9yM2be3!cU?1L1V^Fidq5n6qH2yr<;XQZ>@}lR=r%)LAIVn^X8STDkLeLu0iC zxHMwxJ6{j0fo3>G)27VKJ;^~FAU~(gm1J?90mD5kXnLTx5{d{oSQueoU!b?xhvhxt zp6(qei%?fNx*Scrurf&rK|OAr@0v1Y62KBgb82hEiALCpeqc2n8BvhOE(QG~|8c{p z#JyF;$NvgmVk>=3MP5_0xiWl<<|d3L#2+JNh$0NHJ>~^lqYYRtf&n3dL_&>KMa^ff zy)rR3mHw-oUQBIV1L$RAOJK`6S4K!UH)Y^xk`2O{^D5uzz*MS%3`nLBCv%^Mq5>~3 z2>6)50OF!e~vwZVsGbWlO#Bsv};n z(Arj-t+!!!O|ReW^*{Xn&8Iu;bg$R;GGu{O)=OR6roF*@38P#6QrxVPIlaD5e8aII z&()0Iwe~w;FAF#oQgN-YC8UHX17}sB5^6NWC&MdWBNpHAg9r3u1nZUJN3ayuQ&_kK zM@WQ_CfEefWdaN21o~Rl;dXy5H^B7pVMH#H(^Y9i-ZbxmG2P7P1i67&8C{bMy>>9k zTfU7Gb5MQG5cVk~$_ym#(8GP2-)B#8N6s&DOW@VHaxH};$Rga`!Xg|_y@~d&$^^|A zv+ziETWcz-ycQ@nSPN{&ij z#|m6cBL!TTdy_}kM{g<$AW^O7IM4H(HZ3V%<#abtd?O!Nq0=oG@K!Vh;fp}jk2waf>f09q$%-;AR6s2&HqR%ywJX%{KB6fM4=ygUmAVes z*`B3yW5Ohp(AjJ2((L>A_!z=akms+ z`T?OJVhOeY3C?G$@$6WKl=q<{T7bkcXes2`&r} z8s{Q94VN?Obj$a;+Fr#GIcNpx=0sW* zkH$yWLs^A{oEPl=0`3&_H`e@pdva4wSf83dXBio_H8}%wh=A_6P6;0gBJ#0AuEiOAfzHna(I~e zv^gh@8RsbbBGHw#dr1Si2HLf|i!d8!P`J3MdnDZXQ4$gN;S`h#?yPK1Hx22BHhoiu zn?KJplkJ=+@!nB#uB38=$9zABImYCpRakM5GSAQFj8TOHLlQc<{rgqSGDS^F?3{yz zm{dfL{vhJ_&%@^opI|8-v{n3yQ@UQ4k=pr6$hmfSH#XLNGg0vZB)ouS6`xyhOvY>6 z)(em8I^-kh1$TVA!!zX7!j~~IlaJhxrR;6jeoIft;G}$*JZyxs*H%}`%p|Is*S#p3 zk_W}Z^C1~yR0wHl7bQo9rB6-kOU7C+v+>^DyT_=00n=qVx5iBb5-n3|mD-q}pXZov zzE1Lz18mKb=ZyNKU>*?^omI3q0UIEU@$_M4Gw3-&BmzlJ)T2?-g51q-854m$lT^Q=X{296Fpj2$U~w`B=tCPI2IY8CW{4C zh>9Z)UId2WbsCvU-K*JxjG$>$e3Re`fTUt)yl<%#Cgei+18*B4fY3k)$4paYPZ8goH@0sxzK0qIBN=uX%V?wSv1O> z=(L2pMllxcZzY>B_Mdb1A`lVn+|6SKhWVT}<~YwW7sud*@VXB5rC}>Jh?|}6hcFGMalW4?g%gB;%rF>6K@4e0EDfS0aE&Ap<`Qm$R*52Fn8)*UpYGCCn`l=i z5?}@<&4>`c9ehAQ3adQ^eGa!4{a_J(duyWj{IoGV4689r%(mO6J*J2-YYQN@NP<2L zX+N-EUnbLWr`qBs;=_juIXl(9T-M&MnrEZGCAIk06XD4hr8Jbyr)^Is|X9I z2_GuqtH3T-2@!?fQfIDQVoy+65Yg!#O_V$sP*G+gW))4g>-vm*TZ*=(5D~$YeI{^8AdoZ61x*hR^ro!|vvlnsp%A7y z#x!!f7^{)0n?UBkYZnLspst$|f>9v$SNTO;XQK)qT^~)Fuw0zByGQyqU5g;xfPJ`Y znXa6?{Cu$b7SQkIk0lWRK8#UsuLG`r^IGpy+!3g?*j$FVLWhPYblu|$#xCf>g)?A! z&D7=;F8+OoSP%DiKN`u+5pAc8}%4*?7A+MNz)cWUt@9E~AAeseR+V1cbv=@|F zuQR`|DrkC8u${4Du4w_8qXf(pUIbEHPvN4>Z*asKAmPKGcT!XG9E+(`v#b52-NV&m zrDo#FNT97Q-Acgy34nsxP2dsa93JeFb@qaB+$c4bg;)X9<$Oo^x&svK5c4M9obnJB zfEf*dNQh5^atOzqn9ESV_FY(yvR$EbxK`}Tu}3>C#3Fixw^Tt$W0D{N1sQ4x9dYSg zH=4c5oN4hqEeJ#4q&O?V?(2 zw~4$a0C6T-lh6FVna7wW%qn<50?yNikr~A;ylu&f@RC!vG{o*6vx3!~aA9$;p7WYa@0$&x3Co zR#3nyv-xD^0Cj0i67N7}bGaF7o8}&=QDEYL2ovCvP@@r}rklKnO8Gk;2?h;`2nGwQ zDvlMrJ|Dwuv3nA~7gARn7%yxv5E#zpYBDXR zN2?${DuV>z5@IG2Dl`ql>g@d{{O=ZYA}&VLItx01VoT?`mj8s zaxR|HTFI|-q7mVdu4Z9XK8PX&TIQ(G$&x&1SY?+5Z=Vf7 zoNDRkImQ^eaJJezbB5N$ob#+8k67cK2(utWrs$CY&Rh{~D%xApghfRf9m(!bAr#;u z!VscH9R5DXG%vTAY>9gbn8R;TK(umtXt?{F7BSrp*Zc^(vaod3cNxzy&S~>Tpt-VJ_;mpLE>=TKY%aCm7l{FVns z;bv~reTGkbe**FMA7kShOJ;$0g+XBd<7)^>HC0*}eOXis6$4?8+>0$*Y7U=j_IP#J z_v-$Yqy~cFie^k3yg-F}lcw5Q(jwB3D=OTz4fAu(bNZ-go^1)i&Jp<#YvaVz@jji% zgpnl6Ty00Bb~lJ4hi*YJ&f$}yQB&#Kn(~^v_P{oN{j{i zT@$jJSOv1<&L-r{Sy*H4!d*pGnbBCZACKPLW4fQi-k;-~84>8O`9nN2^My!D>(vrrH^v*0<-#2v-9gWoBSh;*Zz7XCDwZ@slr|F`fUjOUyl{v0!i zL^4+^n#9o%#4%P0bIbml7VpD}O+-3FL0#l%J;-L_AelC~h$(mONr$S`RSle;**4lA)fB$?w z&vE8+Q1`D`fTp{RNoNz*)>bSBtLmH>BQrid%l*jJA;47Tet^tvmI)RNU?+>r8X%LhiY#L^a|z>MPvO#KlJv)pZbry=^y^iGJ_7A^z{_6MMfAzcXzxv(x|M=H_`*)u6eVjyjN3`CV zK+}c?hHY3#Y<7E!`dUL*D629A6bmy!B&7pFLUN;=gh34H4FYK*)CfUlVT(DlzCpg~ zx#Z0`cmSc&x-uP|X>O&myN8d2z`O=K#H$qk8pB<~;+s%8(ATH$F4YTMQBrvk+ccbK25D82Zd2|m ztki`xIEc+@RPwaVxgalbucY`DFzi_Q82MYAM*e=zVRPuw<KuA?Z|aQ1zovQ zo_XxPQ&F>y&f9WMbGN&BZ9jF%nglu683BU2rG1TB&!l8fWjEp~6wsOk)T0q-jB*Fl zH5T82XZz9Ifm+#oS9oH$xi(T&;l|8u4fz;cqwXCR6%KM^GjiwN(8FdS`EX8F^Xjzb+780Q{l%nsP1lT@lV(+@)n#VIm0%fC<<#d*msYV$%q+@K z39(3;8Q|$&aF2T|O5En~YnBTS3=5E@W;9a+j^-W|i4nhAYlM^A%)=@TTWAWVppllD zqQ}tUv!@Sz(itI`Kt@?pR|#)6J;G;XuXxe0U({^iOdFt4cb94xk=}cRGhmrddmXK* z1mK`pIe}|0RD0vPFlFx=Qn|dynE8+wb~6bT87u3tbbfKm%-foumlfeIuP;6xE+w}R zBuFy)nnKD8e_glk5N3A@L1tR0C#`5B+hg0q!^0?ye5J*aaX7o1HxE%}kT6sl328XP zD2Umn(Ofv#Wi)sld(@2OBK+lc#EsIS#sHBBh(S$w{cJu@o0)udBlWHayDrzv=VK5k zx;7O(dNvGk4$Jtx=Lsf08gshN33=nD+;D}whdT`;p3$cZqcNJ`=;{Htk&#(%agLZX=5!}OI6&d$;a%nL3YkMX zeJh@}6&sTQ4!1F%lS7=lhzbsssZNezWR3-S--M#;!oG4TgY&0N4;vO9>nn?~6G|kRA$DHOR!P%zq zt>XCNIg9e2e=k`X_FFl=zUf95=)%BEmRb5UVWMafy~*WLwPM zb%bWRVZ?0(g$o3^TZy$MNpZ3f0T>f1+)T{XqOz8l6OIR!P%-6kG7U2`+BMq>1^+TT z`dSAC(ahy$c!cIRsR=~TAc02V0vm>DAg|0C0^F-KAPPPa9-Ua4bQKDoosfau=FbQM zXIx}sfnxRuq8MZM0t&&_=UuTL6|lo1hc<2-~@> z%w#H#B$U7e%wtZg`FX853rEhk*$$itP9yw1oIC_9B9Gn)@Q9d^2rqX_S+!G~V?<1l z9-Vs=W(^9EJy%7#q+Lu91hNEqKWCz`k0y&8H)S2 zTUE^(&zMI(4%m*#OE7|o#Wh|RL`_gj`I7?_76Gpq!^}JN3g)#W*;y&VRy=4?EZLks z+dPeAnhG~T6Yfn@m(Ydz(YuF*53}bv=JaV;{t|qMSy(6q-xg3OnvQpOb01yaj;6v) z)ERR0CjK>_&vVQ5;zrY7*|J3DahlT@K?F!sJz8f*kWrdG+@I$J3BbKcYl|M~D|2}d zBS9WK4d--WlFlsL!DfEGd(2rOD5+Hk0cgiTM9VoAD*`${j41DqLz})G$I%RfYMh$I-+;pZ2~Mv}?0X_a!;*gqXsLAt(O}3tcH_p zT>nxkp=Zp~yp6dmCE!FI_X|{%mp4dQpgFo8^E}5KW=6TrT*9KtyIorcyohFL`)+sU zKoF5zrhod)(Xi9OFKI6Y?)(3XB0_iMIniwcb>o$xJ3Zg@#OA^8FPe-TVr&NuF_lki67|nkNd|j{^Sq- z;!pk{5&eh%`EUK7|JJYnhrjYY5j<2u>>xj<=Y*IFUg-fJ9tjW0`DGKq@f=R4pVLd3 zk*A$%ip5fgZSHZzp{(r7JO!&4Ifvcxij+sup(!F%>#-Sj@1eg`B3sy>U}+;;B3q zmBw^59+u%AxkZW%n$9h54exFsPhK3IqzNS>Z@Rr5O$8uy(e|cKweNj?Cc-EoNVo43 zrpIR=)7{Yya94DZuijK;e(&@1oHN`p!Gi#37ZrJHL?xxO{I6-+bncXc^tEuLSXz0B z_R>38qUg1qii?wcEzXvjlNP>bWqjlc29p35>Y8FIBIFXn!WkaS+_h6@p9Ty|ZN~16 z;-c(bD~jDvRW8Be@z-U;tqH5@TA3%kVvtYD8Evj*jXccBjJk32P=!jC!o-XO{JT`$ zm9th=q^)9DDH#$)l$V|D@_I*d?J9&r7y?(0DbzfaLY0Lnh*^}y2^PN>X2=V(vi!h! zbs4|7BEAss?GrUa331Jmc*%%g@l#mC_G^T)R9m>ET1VU>N^V*1KunKmG>y&8$V{o_ zcZO4&Ey^a0bIvpQ?{U+IRaf_V5k#C3AZjWdq{;_$x&_6VfzH?LBAlEIJOdd(AxKF{ zvRr4;RjEdi87&q*-Dw(5qfl-T=+PudMoxzB!RC9-1`5en25A;Jr|Efu>2atqM}$<^ zVQm%5*yOx(YikqY^#CD*w+V3W(9Qhz!!jydU4%(W>!easbwdS_s5Sg*%( zI*~COft!f3JXBL3GtABGIfz9ebTn4bp^_Yvh}75D#5|)MpJOt!sy0SK{hMF{X5nPZ zjuy-uf|a+$$#C50f+QC(XHOz;hjvv3fx_Is^QM3KkN?P@{)r#>@vo}x`QABy`8PlR zKmYD;{f%GwtzY`J@BYU7*a3Ji$;p|Ax5@)sK|X0JWi~|grWxO2j^|lPNnOxX7#WM` zpX;Q-$0*rL!+Co^7mO*KebRKBbC^a~ZCYIQ%pVcP7}yPY-Uw!q50{#&h6)#9lfkdr zR`6Q+71J>%{Me)W+Rx*TqkJKUMBD;EFZxHFc@~ac1>MpHal>;g0MT|m0v}Ot^r1TY zi61zA@h89iXMXwz|KiX7$XADcc`tnTIsfKA{Q5ur2jBY}fA@F(uYdbDZ?M#rCvjaC z^eT)0k~nn_NWg2=U(-4_vv329$vm9!qn{f66aVqg{`3$2vp@ZV|LH&Y-e3P)zy9z3 z@^5!YcQ>X>%d-mz=Z=#1PIleLA`OG(V>whdv&U)*6GmKpHiM zDQ8Wb@I~_==IQLlV}^_=Y|JEuSXw`%4qpZ_PY`?Xw0NH5Jnft*kxZ`)KBR2p5~onIC#t#(%5ppZKvK_!B?&1Aq0; z{y`%8kN*B||KI-Bum7L^=5PGn-+V5OIA0N4bfdkk_uSl{pC{Z!4~Rk=oQ9bNnY%UP zE-H9y3=$`cNp9pfQrkUPI&S{dJr9WJW?|DN#t`dmK6-1Kegkk*6`9Sa`7j@rSbfjh zD7P_V8pF3CILoWt;xHgQn@aDJJ4%6EJETijZ9%V+*-ekV$p{w06;~48g?GIrJjI7( zl}oi%JQKQ{W*?%%K=fyR`aA#fAOF#R`R9M^cfS?BI`q%{)VKf4PksAe|HYsDd%yGi zU;q1m=l}Rizj~#*#G;(t^2WX)qwM;*FN&DU3KM*J;7WJS=5fyRN59qS_vw#6_6NT8 z$Ns>#{`61$_W$bt{wrPGjC@`O$C?ugD2b@U-Oey=pB}yOTQCv5=M*kHA|wY8%n=qo zr_pm5VsPMS`sgB(5!_X!eK34ye-glSKPTz1-dI(UAhw8=F&Byay)PPGWg8bgRXGlE zl}OQFcB(<}@GMZu2u(FC zxraqqB7V#!=By=ud^wVd8`gS<$GG|FG>1JwBGi;Cs94g}fv!Hyo|y+cvpc#G-cyFL zL@T$F1L@pwpXN5Ye>yspfl%Pn;}DVWeSXirY57BWI}VKKIV^&S>=E1rUGUYLirDv_ zW13B)C%`O0bf`3yH);fm_j82H&;M)x<|e}fne?ElnivdoH_P9OEmi%NVc}IP^s30+ z8Bg^?b}n7AYI1hlRD|oT_TXtWhM6USl1tJYWn0fo^o5|lA&HQ)1z+3vN<uJHAn2D9sy`z&0H{rLuha{^D*4dKVi7Oa8AFWzwmcVIc zAtdyT5L&%HQ_!5|PL-vZy8pf6U*Gp8EH%%9rRNYiAzncU*K<-|34?Q_kl$Osw=TE+ zEiRz={)bMPtW?wb0zaOqMZo%fMcHmjrN3+=ISuo>45@?xxNV~DBQM;jdw1``Z83lG zkK(1ge&Ns1YerF8aIci?)G`)>&sUDH(;7UirOpLH*m(qC0SgI-a0r~4QZp5cXi)Fw z?Nr!vWUX9`G|R?|lo1WJ)2;<7kgZ z?@a;n@Zo-%{gq$%@qhjgf9H2UN`CaK)Nl zVC;`6%yXXYmKU%4bf1>H6DN>Jj@iN^W*Rqw_Yfkzj8~rFM`Q=?O6K({1rQ?KR0t>^ zW^k>>E>6*1106fp?3)DF^wpvN&cF2Y|HD83Gk^3CeCzjAo_=-cU;Jl&lMe*9IT`zBkc)xj*46ruV=1XMg&KKmEQ{*hKWx zKY0A5Kl(%e`~T$EsPvoFD!=UIs{V5VaxWze5ZOw7Ep5z)vt^U6pG$I0p%euGsQfr#e>0{a$Hm1)R!!rK4 zvL5r1jtX`U-+!@GO~p{9uz))(km*R~bp=_caGWe7fBw=d4OD}1}e^$x6{n;$CEoM-+LuW=hvKa#T#`r zZW$q1EeLizZVp^T0Isy@Rp|j0&y=?nxm$$GFaCG`Z3c%qMTm%MYwZvgvy>)h%%_*} zxPZ<4vhkuDn1V_XSjnomfpn{|%VAfv#8ff1D?Mkx@6S0>kw=sRD#_@TyvQVX>mLa% zAirsgl2O6+g9NNjJWe~4mP^5s{+kskq@pVMz2*y3?c2ERRFJ9Xu;)3Sr=8P>h2>T^ zU^C9=7#qQQTSc$n##j!XBK59~0jQi7x&8D7XcKpOp2O_-3J*atkuK0aEos0;Ctek9 zk%pKfqzC%2KwUBz9htV@=Pa1aT{D#dX#qpWTETKDe@uiz+d6qU;@q6D@{P9R25 zxQz%~&vKGdJn6s85NXU()uGc%ZG8#Iu;WznGEdL0)2ai#YKjGyrQK$fK!dRv3 z%^Vy=2+aUXQLq%7HdX}LRzC7Fl&|lD%7!Tk5vIlSoO4!$_l}iW>yxGb$kJLB#AY#! z&Kc(!&k@hlpQnwHQ@>d6+m)QWFR9Iy!Ol@;y5L>gvPMdzh-`CZkE<5dCm&g$13d$*u}&ggv9JBZ9Y^s- zPy#m&z(KCeM{kF!COWmgn;2PMCtQh$%fQ2&_g}_~;o2?S=I*;Q;WJ(}=cC|*oxehFm=+bHK+%Gdb&oSp*WMaN$Hb=SJu0ZGHZr(Gjd8JZ% zgn>vzBj_Av{(CR4=??o^mX%xhVXjhA7h7I2)!tff?P#s5^d?=kD|eBm(pBoOrOw&p z!#5QbZYn93+7pgcGz#;|M13*K(f*?7HB^i)qpaV_tDqdrRh?9g$bR(v4$ps482d(P zv8Xa6A04M8SXlm}{8131$hjKlvzZT4DZLQHaa%*w7Gh1HA&gdPT`EhWAMR?kB`#To(b?a39Gu>R}Uo5Op zOC(%7g!K*Kt8Doa54OhhsfxBWEHS?s>XJvnl2F%r*l%{l~uu+yUqnG z8iO?b(`BT#MV7#nY`jy0~Zq*OTfCqr=q%jq{I&Z!;mw)nJqMk znk*X#C?`h=$isE!WRr|UaR}#spDF;+W4F^4VCK{23G&wT(c9b6kJh9}E@bY*=JT}A zr+4Q6@t^-0`90P;-+61l_*38h@BG4#|M4IF>QMRR-#VLUlf5g*<}(D;RgdEUVABd# z6J@|L-wT42k?Q7=tm=B{O+J7u;=To2SIpuY*p^1+{J-+Kg9%G!P3w5il;%tcW!~F? z3a}!8E+o`yx~QH50!NbO7`xZ5q}7`8>=Z0qo1N=|WESQsuP@E0Cx+#EX#!t}xURB< zK!5q?fBfJ1Z~pNv{7+b({^(bafA^Pvy$PBi5oxWKlj~H^W1Lx+m7>0f>*cz?l-Nk8 zn^gp00{y8U|LQOQi65nZ3V;0Iqy51jeEau)`L}$nCg=q%!JB=T$m_1M%c(1kHnuXj z+PNr|y)y-vMLV-BFS75D%_7R=LiA?i10Y}(Vdf%ak2%d>M*PgT7L|69o&T+wZX~Da zG%d`+>>8I|uLAIqKXUCYFVYO|0Ff#^_%2y~*%_^E%DsLuCy%fo3rZ=7MGm&}hRn53 z0KuuLV#*B?uVSM%O4ni)FL&1x6~)>=Czjr}lZ3#aDlm{>rW?a&L&=CTgU9`WTM z9yh)D)fv&XDyBrBf{C&=2%@B!MZ&pKvN-p)3zS({0$D{P$shS|{ToEowI8jkHVQh= zajucWTFPB+r)8u1_<3ZNQm>L6J4<8MtiQ1WU6)FFXPtcD zp4G;JNx0M_y~)w!IP_>z*A#97gScrGZ#1i`1h4RyBKh&ME#+=xBP?zLg)MyC zB~%WYgoOo70MK)s=9aH}!VhDGzGPvQ2(xStxO_QvaFp)8V6U>WFzScyEFQulI3$Eo zu(>J`x4`41B9sv@%SsBe>FW%Q>*LLY7>c-2YU8y(xo@ZGMGF5>C9;0;+aEdW1#br+ zUR*rbfz>w$R92uut9#zkq+3OMIW+m~p2x zW`SjiF`jw#W%ZWIrP40edT^(KDnW^u;6#pVzF%LIxp|PVCQdas49rN1_d54dJ|YVb zzGNn`0kWi9fri>?L{*pqlqj>1)0NnC-8!j$;N1n*g&4E$93qGZW!1W8B(vpOW~UKz4hbqtO*xk}#n3FBA%Oflpi7vrH{ew^ zNgWPB9vN_!yP!bUCA<~^OR;q;obHK?^w<8mpZTBu#h?9ss!jEO_e+2OYQ#UL`qst1 z86zNZ>=}cq#xkL$9-4V=H&{|pZ~+(`RD$N#7O`6Dv@tL{^95Ow}10}YtAyl z;dWi})+$OZ7IKZht$1{k8s3H{5pGVzt#x6^3c^;Xa9m2EYJ0CN8FvtxHesPiV}{>u zbGo|dtE-HQ8ERYRSNalKmG{y@ysG|cu)9R1jqaL}18O+^lFLH4$fEUg;{A=iDh97r(>2Nq(< zte~c$3|*I?2sg?I`(%Kud0UVN#}a{3iid#+DwPO$MG+(JMNh;l8QN*dc_LoKS2=U_ zz`yvjKm0%Y7ybwl{r3C(<=_1L%fI>gSAP5bSAP5b|My$(|DRv`?(d4_+e3r6f=Ck1 zmx#24l2-G;%F>|%?3Qu!CF3>zG&XOFDK1nt7N6=W*Ai=$52Qt?g$k*}RG#KmF(t}WeZsd*V{Qs zw78X|``Ar$>9&^sYbc%G4vXZK4bB~D10FE8G^W~@@xf5f8 zLz+U+d-H(LHKF_vMzD5^7q|jn$f4=`B)V-A)hY(@(ewh`%rUW1=u0;radTZ|C_~&a z10Ph{@qu$d_sL2@#W$oO%`uP}Q%Myi@y8lHN*2v5l70}cIANzuGo0qrxU$(jTB9aV zA%bvLMQoTpjodl>GLWvL(>I~QoO}e4i#WlRkxNNZo+@N<#x(M{uTyUTaf=ADI0=L< z(iDvm3U3k-lWGOty$&@f`Dc&U5&f;Xyz52fq3zuW5evhu?nopMU#HKlej_{crxp|KUIW`@%7U zz&KhvT7}ql6%TReB>B0+9X{cy!k}>+-W3WWWhMnE6?QMKt$5XQAinvT5ArlrMId}K z%-~Aqp%S^JFX8}?8xi3-A;}L_n7T3xnZpe3B|2MKp%pf4hJ|v6XE`WocF-xZ`CthuoadFn^yX zv3AzREtJS$5<8dlmgthq8G23t(TDX;O%P3Uu|F-Am^X)g3q=Qb(3mc;Qw;!P!9zIX z3!Lu$-RqOjGV!{?zlqcYikAjxG4OChD`qJH+!LU_ff5!kg>(^tF2!Q3n6YJY!AEM% z{>f|5$ZNMVm`%{uwn!AhP+}D*Vld5oT0CsE5({w|*vy3Dn3FrzoIJ=KaYSzAl)*tc z&SIwL8VlcTLm(<dN7((Qx>5o)Z^a&%0yoZ9W=ysUlB=>H&e(zr*%JtZyN}a_hcc;fs)w_`5@rE)j?g7msH0vsIT(!T^nUtqB2mEB zXqETq;;K$|-?5#o*URO!%vkxo{;R+EQ~8_v_CtT+Cw|};e&PrAlkGvj^sC?fPk!yY zzx1o${iR?1?tk_hU%$S6%iF$wp@N)9;-#gx)PhZd>q|j~WXx`&g)E;}sxiOREi{gIiMVMEz7o~A#VUdtEaXMzm zkF-Agr;!t^O)Rd90WRu?14nN0yp65Bn80BbND+r1~ z(__xrXJojhGLpL7hRJEqvGPaUgPd=*O{`^p;#uaz=lAIzZ$|?N;S%w!rV^lt_gM0< z1yjJ1g+zoKCdE1Y$&Mgt)P%fqkeJb&@qSv+5Q(F6QwFun{W`}i%U)@`an2CLp&>#n z(U^`d?(jeu1uQRY6?q)T+tIpmh6%XG^mxws`TabHndg9je*VWk{eG1-Kl4M6zxL;T z^5=f^?SJ$C_`ARP90by`zzGpG(bUSdW~ap%F-~%W6Go87Fgv3~Z_?Y*T5qjAhkbrO zN3q-U*`urXU(bKQug513sC6GCF3lVg#(c6xz^AP6l7-aV*=hp8!H1O7-Ak|aT^ZQ z4A+U+qYdNt_mhYpU5?Hc%?b8CA|e%w;g*A-s+xkFXiR@UPa=ODl3OkY2m9?ajc*7P z+R(aN?D>~TaCeO?UU%5Fj>lGD6{~&YZ90a6* z)*TEs#2{yOVN>PSW>;1b1yt!F5+TFlIqf{{9CLb9hjQ)P>&;p>EX?V7#&mlQ>dilO z=?Vrl6_KMgZ91Okd=8s4N*bEe_GF(XpVcWKL@=k%=X5ealNiP%-onCWAV!5nUW_ac zBYIAEKRrT4yS9`px`&(38K+ymBLX> z0h=aqR~*F!Z7t>!7yD`t`_-Dw!-J7mhmt z$R6LIC@EVj?g#^1m5lyNKmSud_SN@wSTpEP{`gma^2clb^gjHTe(k&em;diS{P+K} z-}t+~{l3-LCDBPkfaK~^Thpd088TTQ)-(}&x`t~en6-7z{foGMC6Vvd&Bpg+mfqqV zL$a;p%diiyiV&cIY0gOM8D;TU zMKbbqTcp!$8i|l9v~4LZ7@iW?Y2m?MuEctpy&SUa&b^B;REk=`?Npc%Rd{3PmKO62?_{De zr~)hQjGF&Hd;b=*ZFb)GVb^Z0`~7Ci5od-o+-XBfBt=S=TRVUnxs4OYPJrxS6gg-C zrw1*XwkS}bNDq3}o^^-ztVs`g&;Tt8q-kp)fC0C#)7nVfxUnV3btsz>wWB2Nhn$)3 zeeQK#*S`nXTK9cF-_SuDYd*>bA5j}pGxNRg^E~%j*XIBK{V&?5YY^>{Y#muA1h6L& zawL}tSA|v5lv+7og_Re)XmOmD1ebDx7D{^-g)Ufsl=4a?onP_Vuu0L}VHR!h&a@Nu zQQ&~p2hay&&adfq*A*9V?Vv#~ZT3I<=RW_3UYI`J7tmrBl`|K%8qGC-EH#~&u-9mR zZE6>og+OEqQ{6RG)@(8Z;8z~()33ciAM)$vu1z(~Jp2qFSxSCXdPu@Ocj4iwjZ~~F zHq)e}BrgaVN&{E2F$QrY-Nq2%W?facW|`!p@J`3n;QCeNT7=$qXC`YxLQN5dJvf2@ zS%Z+pH8`jmM>WIuCp|8zlEW8BXX$*UNK850MgBJx@)sTUDPWosMiOw!>g|zYl}|A6h7u+`of61C~$}alo2$j3{A=+ET*)zsV>-e55GHmP(03ncfqEB24f7=>xiT83ni(|((;3mm%4|d` zBw}XVsza>bQC5^gwLcX~3#MQKDbuEeC!2~q-o^+77$nI&oPmgdXF(86wY7+w`{O<+ zjzgHw2}faur}9KgCI{4iwyfO8AcQz-BkZ|H3Wl&qwd-ZAI@HliwZ581WReL>Q(~Kj zHel*VXQI#yJ*@wnGUrTUvSp6vqmDCC(?9?7pZNd%OnGQhG}HgiFa6|~zy9uj_?zGP zTfh6=U;Wy5fz%~8eCWN|l95l_L>%P7QQc{tbT`61h7F86-@{2Js&}Cx#65^8&T?&v z_P950My-OT2u)Z`HnH5yBX1*aBlZyvEXa18^BbWA?ct?$g5`jqULGKTFfOaP49Lix7!E^%ZYkSL?If-fVwRHfaxPE>=*W;N@_-suK^3a3w_rz5lo)^Tj7@CR^9ciXlZEesrYy zVr9k2g65u@m?@{3Msy+3in1w#`y{&{DVDn`YeGmeLssI9NT+$l9#lu-1xAqdKZ`!M zOrr%rg(v)ApT$%nuU}MV-d_m=r@WAf?pb&k8}fjVh?Odz3EmytuZ1Qx8JE_LMS<1DTSd*g^7yDIyUQpOYo!906O+=xixDnwy7&W>ez?x zo_^-*$z%)BC28T<=$x_P=Tz@#Q9#Iq6YWeYD_-=E33Rxm`rY_vXy6Uik$NBEGyL|d1FTMKEKJ4=P zLCDmo>pmVGj-zy8u$1tktkvv6j~*=X#LP?{pdg1wCg%kBDledQ9S9?w2kFiajhh;E z^Dz>2bxt09r9aowymTHrl9esE89g)zC3eabgdvGINQfkkll26E@>TVBm@jKRUmff6 zdh+7jc;SIe z^~q+;={as*Q??XNuox54qbAogoaa#DR0+Cd^uiZ;?Uf)<*Z6V(7R1Gb%LpPfxbl>7 zUgtv@0Z;}prlR1XwSr^QND7W*0%y=Dwr>r7RZP@ONr))bF=EcRPyWs_CQJqLk!ypC z(Tb1Z$J9>;<-#7O!~`v_VCNG|cq-+V*;MNAWhJfHG)a{R$>XP)<+bD@Cs0--{59B? znfRam+{b?WKWn4^xi??>+?y}`Yk%@nzw_OvfBDzG{(t)A-|gMDX1%LPL0qHwCnLfm ziy3?>#Uj@TjP?)MF;Ka(hDx()Df9E5vOZCH6vv+ab3jIGKQQxM?Ury zEFuinih7qR%$&R)Kogb-oqv$WLFHZ^ka=X!roCLgg!>gWkRV6N#5hn)Ispo!$xW5J zsz5n}b5O)|57pUNKKGg0#Z9|PWA0XVaRMdpaH^2w`;2Km95I=o0T5U@gjfyC)Xb9D z0~rO%TlpU+cYG!{c;?Ncj}TB7X)3)7N*Sf#PHZh>?4Nf+8%%jpL_Gg53j`e3xE9MFKzah{{FWsnzYPP zx-KU)iX@^OMZ>1WBLqcy1SqLDQx!9Lpz@@Kde%ozlL~%G3&-8!<_c?HR2M!Dl;t#r z$06FwC0M;O9HvFO&{-J+f-@gr_pi`*KmuTIK2ssLGGV8LU`Ez6tu#l}^s%X!a5GKl$P7N2d56rSilNB# zpnZf&s5MjV&8lkj`}^@&Jb-ZUe3|({h>dSSBZ_u^mrU~v|LU3LhOBH&Ii>KIjrduC z&p3sHwXxW)s|LcQDP83?6RmMr6*n4`h8O1HlQz%-l_;>`j!}N|Q=Lpc_?= z`td9YJ#*=n0M$dN{9W8Ti*RGflwCCx*V+|}^yUd}IR#sP^3a|<^e0 z0J2Q1BBV^pq#5jc-1c}JJ|blf=fz~&@RQE00-6E0QG?+`in1AZV{0W1U7>LYb!&Ek zDVnT762t*`Esq)COXYB;Dbn%jEYf*~M+_gseINVo`#$!4+{V~<--nMFBQtA9W?D>s ztEk?twprU+->h%eA6oBy8faCO5BI=w#u_m_U^AAYv<%0v6(pZLfxedhHP{e$<0 zGBSLOeE_FGSij$I`+mFK_Q&1p-t|}@&tpe)2GCspb!knHMAo_kR2X=754$RZ$23-n1vK5g#xZ)Q6&Wn&dK)HL?1yF|>-Ib-TeMJsiBe+}l1XIZY?2v_Sjn2l zdR`Xcagq8L`=`u~BqA(I#FL)KrnA%n#i*Q&k3GcYhq?>F^?ZUPio3pnu_K-~Q=0Ui$E=oG-lb z;{W@1zW3cn52R1|^vtcB?+0c0$cPI{vX;*9BnPq_Iz{tnE1+X$hr~JhdFH%1fe#a( zuySgT#1>qXeK^jjWk#)W^&u>ZN!GlecQEH!6LB-b-95$#_ZVX(MMNnKWJXhQmf?rM z6ergDKECgRbkzk5np^Sz#lQS>f9RuYBKq`eFZ|ihzxmQ8zww>#4|r)p`E<(30v5^4 zZn*U5A(1y*FN{ghhAa-joSPAXfo+|bk;ocKC7ne%T_AJEk&o7xMKd{)CzzqYOJ@a2 zmxzERY@X9gwR#v-Yx$R1Uq1I3?q2I?Urxcv4xSM*Q?gg{jdfxz$IQ}yW>V$s4$pnW z#)q3yS)*l|)*0mX%lJF6-aLhMw2f1FydV(VP$gBaS3TX}G zwY+b-LZFU|2zTVG3uFV+D~6nhr55k47}GIvfv3OflCyzQNiLrnDx8b8hFF{_luP(< zAVl(jX+w$>amL9DgEJz$N>sA&;bSa^Srx%Cqw1u5PGVZW{9Fm?#G)L!tD|8{fY;tm z0!&FKTXGSmbcyb(;6~60%%g{>5LdmmBu#SW^q3>58h8yva0Y@TJDDk)auEp!V=$bN zE2Lo#_auoyxkNap)ERVHiyb}gWqOK5RbKBY;m&nCafhs6u-C3cXJ2vZmHCu!lQ5|e zaSBCch+ygvi9r9syMXqOCM}8# z9MQ#8q^Y`=L6%Pty3Cn*CVXJ0xXIL3mc%8p4v57NKMamM$pFJs*@pz1mucwM+i+&B?{+uIV9dNCe#YuVX9(^wdaIS(BHjg}$V*z+&P4MNRow1E zdSVTh35=siIo3>9vp6p;$EgDHuyl4QVzTzQgQ>wL%YoUCx_ zOWq_iDw(xFngNc4D9qM?`l`=6=BTO4pXHk|-jg6{78ik$U zHB?yj=v2s1rD+c4Y)X~!qH5hH{dHPtXbDUvQec)M5!f@8dEc_Dib^wU<_RQ4B=+G< zWD?!938P84g4mM|ZwV5P*vCxfDv7lbZz@FGMO27|Jn|NP8@`Wv8sv%_0Puy6z5HPp zIzRQ=3%~u&BS6J*07-erRj_GN6NCuCgJR+ZnE+?Rh$yyhN^fFS%G8caodxb3d7_vu z_I}(UDetb~3+RTPVU5s{vT;q^0`8GIXL>mffAMJ`&RM}raHu3o^<>&>luKSAqkK(C zU+2aTs;!BxiZ252=RW_jw_g6R$Qy6H{O}k4#83SBpa10l`ak&lfBDN_KdhA>#I{U@ zF&FBVlBm=X9xR#~+!6um4Xudo)53%w|Hu>i@L%70?>2_FMy8xyM|iIHc=36aDu{?_V-xLa)ToHh5ji3(Vnq5(HYss*G83taP86s-*mSD#f^i=t?`cuF zK%g@zNFu3R(^C#VZ_!kd-mW@T30i+Zk$qkSl=V}QC}3DKv+(!WJy*&c8f(cKFg!|?ZiAnFKHZn#hHRm<~1=$ zG9^ade7{(AkHhH*+>wITiJh68fDzNYW|IEg&-~$o_rLV9m;TgG{MfJj&UYhbI_Lp+ zSU9{xxp)Fie&A$^`c79B;LbWsIhj@VH$++dPn4LYMzvVLAY6n<`cE960NAc^>tqHd~=Z~HDCj< zQ#N%KX=Y8VqeUWOXIWaU^yR5pOM&rFQ{I+-eN3bk&s{A@gOJWh$=u zO!1x+VL~C{s}{}*TgR-^=L0b9aa4@4JD%RfYqPeQbj(-k(~(Iz^dP6)DVee$rmAdK zp4Um0v1Z-0>W zk6Q@=N5KX{0DHvKeNQ4&YbIN7VA>s#;R*4)?Jk7PXfwgX#uD$}yapkl%7w>1f)K)q zkowF!nVL2!M>)KB93JjSEL80*K=aL)+fRPnXP<8Qql)2cDlx4%s(ZWisixsD)|7`LeqvY$6>zbs|ER<3vuu;kBxr)Re#}6 z{P>6ck-gOI-}oAV0K6qB z;mI7`+*q1eH|$LuoS+D1G6EC(=~b&7gGl%EWKnIZuu3|dDeepFNtbK*0`;$bX^Nz< zBvs({N)5koHke9^wLRYnT$NC3972L zZSp`gcc4B-#4UJuDf)n{joXUxB2bq7E8S_h_7=UNnazAqv1Y6$w-LPO7;CFdv}{Nh zm>Fme<};JPs!f!c%=86$V&?BZeazjbPK=j%_ms0rIp$~&k)4QiGgIX*LX<&~$zIzD zU57>{(jAB~(}Ei}VN&hgf&fnd7Fn!w0nZ>uW>(==JJ+V&WD~Zj2RMiGc_AbhwGfkI zghU45G4&ktmap=fIe@SNkg18NX%7OCN9++akc4icqP>v_5hqIyhX+>BDLWxNi!+tV z4@w0qZxz%KEX*E-AU*ghM{@hUe3@T9m$oNO|4@FUPrvrUr(b*FU;M(y{-fV{=P!Tdn_vCz(^6)ODkO;+ zd-|eM9~=Sz5%b`_mYp+IJIM*Vdyx=TxS4v`@QCx);uBda*QuI-$b*LG7=z8s z1f;u5%uNT~7Zd0Ge|TEJmlKm#ME&Xq=USav_(-O$bm#T8O}_hjFL>mUF?r724!Ql2 ztv;E}vtAdSn$)IZ3acpEa^=kAsTnit5dppsy)SdKrA=QOx=BDw0uZ1*5g@A!QD&h9 zRojccSFnGYDt;PcE=XJ3tf^{OCZS}*KyAvdsn0aWQCyC8Pux6N(ppLrQ$f?=TBdOT zU|q`Vm6JOEy#Txq7oO>`y>XIwQhhC)@-~JLOvFrklO!EMQRsAF8cCT{s;R7T88=l^ zD|d$@N5qKmEDT@yPR}pf=ihwkITSj-@$ILF%qf^_XHCNsvME)@6R|`Rk?(^%kGVxA zA(LnM3?UlG(fE{tv3K@ArF0 z@*O6nG1&v*iDa%-rr-mi5%(s8HPoo&A-bbz(l7nw>mPf0qv!JaV;_I@-~2EC!hiq& z`m100=6gTrid^=)Be(T{h+%p+E*|+tAX9wm)rXJ1(4NB&|IPRJVmI|UCl*)EnNh*V zvf>ziW@31#>K3Sk#ctY-W6niao(AXA>vDo5un#vDYrL;I9MVA`+y2H zmS)m}BWYyfpdYZTrVy9SCLJ2#q^!!LEG&yi#+M<>Map!hP#kW<72wOqmR63i)Vq=O zqT4Je?=`DALnDelfpIy-HGoBO-ESCYh0m z3YqFQlTuSD_Y!j61BA|B3FHZQGP~%Wu&I(%VIm)znZqfTJp*@!z$y^PGxy;t+)Y); zj6mFiy%^AWHAl<{KxDG#@HEY4qQ)H-ge3QXO_4}V2LUEh;ojuop>JKyxRs3R$ex{W z#8cBVzgReC*mdVkn_B7Dj%XsM^3o}BhT?6ZeGtcHu~c|br8kA8(jfcSc(f^b$u zxDVOEnYwxjIA0W#1SQiEUBVd>G2>EI^u1E4_ku7w43T z={{@UOC+V_N|F-^Z* zCQ*Ig*4-sqPkcn)9-RSGVNqieVq@;A*&8uG?jy0ta@V9~A%NU{C$tGG)C9(3RQXbt zXOs%Nn0AoTD|>TcHs#i|MQPRg}4&@mis1{Z}>6M}p-^ zzP-8~PxnK_MZQ;Ge3?m0B+Pb5G<_f(E>H>4RDo&*QA3qBvxjE6ZDaV;+t^2pm{F>0 zwl}$)OT>KKMo5NNz!`bKH`oK4u|B9MOo&DN=HcrMB35E}c;;~T5uPu|*34KsTT)-4 zP8A$7ae>Fs@i@jjv3uw~m>^wDWowct?vXJGyAuR7#i%8$Qz1fRj6H?Sv@&AW8jvHg z?}L%eI*Bq%Q`tH|bQ=SJnl`gdt*f*Z(5);+O3cX^$ld+sK4OLfPMOe*lmkm_Rud+|X`^S#HojfnJ$VL1xRNXikz!-QMoHaS$% zw=Fw)#0Vb|UM_81;jDr~XGwP)JUpwwxNCO#&OUY7LVs|GiwhZWM)b-VWmQn7Af0-^ zL+^3qOkQ0W>v>*^kt@GbnE(9BKzKf{mp1#i{?%Xj*Z-Yg`N}un#buLTh6I4p`LIeK zG@&A9@N`rJK#>@cTa!0leEIqO?ss#uGE2?*CV_z4@QB#Dz3}8oZ^A?-{LpR6tEbACcq%pKs*M<| zAmnft=58UXO(2%7b3mqIVnV#8O^zUxl%U;1mAk2!G}59+WV)3AcgaQ}6_?%Zq1)Cg z{bdT)D*!B)Bk4eJ%q!@?buxreQ@A8%aN#~mJ5N;k(xfT{z-~&W%q&3$$B2l)Z6C;N zGXg-E1=)mnrx=k@B{2#Qo0$}hiodOjqxf^pXhvy!l{E!)P8@N7KR|ZO$L1X$TnFM> zbpL}?%Lk(2jIPG1psGy= z_@U-8v*q|c+mhFOccL3&h_=#schiVNNw%)spFL@PvwTgJki+)csw z+%bdr!f%=FCKr zI->59`ln?gGKfV-kOV7_gy*7T6J8BbT4(lq^ii$ zuvcojh*jRb@??<;i!w?kSIbGKl;B5C5;S9GGU2WjS*_MuZ`QR<4%$@nQ6?j4WR5Jx zWlq)O@i`(^iJ3q9)`wnRf8|^6XZqrDDoX+Ee2?(Jgk&} zIBIhocx-fu?vfJjbs&5w!arbsnUcb{Uw!zww_kbwXY&92U;5&I```VQ-~Zm@5hZqk zwetNa9%q1BISnSy51t6f8!tYO`^6LAdvpRka`(~2Otl+!i3nVhF|W!N&ZLs48KVq2 zy=fpvpgixGaZyV+L5zfZ48jm*Y1*utxKk(x`=KelD<7Bv8pBOEniT&a&0K0iNXyM~ zc(eH`AHo&9dbiwIEBSI4>Gb6nC8B1xC%*46dh_8V12Ipk4*>#6$xKu8|T(-YqpW8=!;u#YVY$-BQR>C zFdP0-$$3&bjfkgQXE!&~)})h$N(2SMGe)>al%8?!Qg8(z9SmaJeVzFt*pp}K@w=0A zdQ#5=o@o*^Pcm!PCZY_TG8aSt)K9;7mB-=`Dy!Hx*GK2uo8*qKY1^NEbTP zmTSktTmfUD%B`#nqOP_2D|yJdoGnAGhu=mNbxK*As;dkg^Q4#p1S%yslh!_PWZcFO zrYCJ{qMKPJfH96$&pb@mYK3@(>@rb$#_loPYy01ghwqVjcwR0T#A9Dp8O*p32J^D- z35yvwvu0gYc;y6S=2CKHsfX)Con!Xe&>iudEX_>V>iRF=qeuFjBZk-7{)lUtTS=Ld z@u2V_3>9(beJt&xY2SL^T4WB7$J-bqql_=5(~yG&dS^ujI94>ehkGO5$W)A}vw)46 z=#~SSfvKM(CCW&7x_g4Cmq=8%)@p2!81qBrY3Aa&lPW#9Bxv^`60M;C|I95x%>h;s zH4_yQjumGn%tj!}5DMj(X|f<=HkLgT31*4p$AP-b3Qb}}+DuehYpN<>Wl~dOa+Sef_XkNfR5c-#6$ZDTg& z7oSLL`gn`So9qM2XU_+1Ad$l<62s|c*=;wSG=iXaM z#h>^e|K%?qLLHiJMrNTppsxJ>%M_ju4ok@;-+C^)(C@rI>b(r$ao>sX@bJ)0nnr{t zUv?66g=hBA6bL(~5-W*v`ASg^ zT);t6`9*!2b5ukKIYxRojX9C(#wOBL(3_A5<)H9_q*eN3)>Mg{SqK`@tQt;LAz@|} zg_Q5rLn0+IYEm*mD@lZN-h5v>p9}-KGxk1{3V9+)E{uhP9F0@j$5abM&KVN_;zR$J zzWDZsbng5Q{^`%sKMJpxy8fA;{IP%gSAL&_+tfeJ2R#G(WCtGMY7&|I2oY+^%+e|X zin&3yW|eTVhP}(Fw6YTbc!meVAi}L#5Di?yfkCG#02zthp^Dzjn43r#@1BuKalrgb zTyY56D1hBVMK;NDBA1^pih*(5w1+TsuKM^89!KPDAJm{?W^7g|*LXJe4fFvD!SW(5 zg>K=UOsxoMODc_+<#jF(MXgO~9wN*_Bs{s3HI!B1XTE1kgPTfJgFMo&?Q`QV3IQEF#tlrGXis zvxM(;YDpwvkC}c#B&)(EPynEapcQ9E*Vxs1Ya+rUJ=7p7YRV5yLy6c}q_~mg6rJKv zXyGMRo>Tx(p>CF$89pi=uwraDl)VWvs2Al_Ma^o*nA0tek>L^Uh(I7CVt9qt^JP+d zh=TCBH(nLy5Bov>t#{rRT`gl4hm~1`)Jl^WiQ5bT%5Eacof)vY^TZK>JY!=}ZD$5A z+*q2i3R%grg_xxY3$ZdAiz^>ky#!_IQn;RpPDklYAJ{_j-P3h9S-$qP=Z%18{z+KM zCYgq^3v~YCc_I9#yopO6@@Ibfqd%+z`4@luwLkgkAN!TBy)!{>n5$pV8On`gFL<&6 zu;h*t$@+;`pFEfHdas8y3Bz55H$X*N(Wx_$cxEhi9FiU}ocG8Ax;4$N>QW8)(HG%h zK>~=x$gs=~GM26yrZ<&1^(S~{rHwUNS3$GhM4L5F_px&E@B_Y$3zUGh(hAR5A$`1x{3$bY&QDVE_d%)*!fLl;}5Wy1&2{w5*i% zhXi@;VMD8gE5iskV`e3W_}mZV-DSq0mD(FKP=Fi}CXD-kKnJVcl#`)I!HG7~!{UazDc5M@ZwPoNgF zJu9^M#H2wuQ2#_EB+??;eRu^M&7m_L3exF%sE{~;i8qn1U5SffWnH`Fj=1@_joa`@ zuur%_#SOJ5o3-go(#&>qct(zqU{BfvCiP#`{bF z^JYz$62i1K1tTIVjv{fakz{(T0rwb8Zee1r_hj~4Ad@dssN^NPh#Wp&&6hxI>M+vRpJ|Z00-J99gn~FZ^`k)!=`^cw3BPuCn0xYLW zeqMZfJa$REN4$HxIrPc)f~jpH4089g7hhLPfoPawCkU~!HM3@onS%fxx0_E}^vZCU zg!n8MJpi66TjokMumuTT6-b^M9VE?LtO#2Qq)m!^W-=&>N0|v9al=lgo7T2rCSwLr zF{{33!T2K)D~NchCJ!YqO(Ves=>*B$g9KI;sOvX%2NuJ!?$GAPc z4G-;2AG)e&@1|Tw)BblS4lKwrSs4HrBe{Swv{!>5p!VmF4{&Syu^S9sGGt(o&eH3PShL$YR!gB8~FdTTMEP)uG`p65P zdM;GHZ$BL??yfrcaCbFp754FX3j|-#Aq&PleeWK58(v{Rrq&xg+$#`zWzrTipIDHR z3(?~dqGH0`w0BL;sSm!7pDiJM*1(1Nflyp2C)$yfuebJ)!k0nbIl;t*Aq}n^^@@#X zt+P;4L*(=UBLAjSzYPnro#G!wETSuc&2-fQGSyn3XXAg z+A{_G>EpiN$c$UF-kYdyx>>?J_Yq@w9J8hsg<3q-5lDDYI>kn|^{yfhO{ZK>Vt5&d z^YqjwVvlixBYC3Biv)gl9ES!*)~K9JG6ZSS zgpFpDjrCKv#X?3Za2;BzP@CL`C z4n#l@0vHLA2vD;WtzaCIX~5UoP^+*hSLj9rCm5tS-QZ{L%fbs^p-#u+$|pRXucU>; zSbhdFozD(aQJ??ipZ(a6YU_Ia^PhYDfA!aXory|=VZziZw3cWje1^RqD*<=x5+c$J zGSB!qPVi>P2_T^hWU*TRNO)kxL*H)@Oi3L9CtYvc(N*nM=1}?s5b#LJTg|t&3xw-I2fsXS$=~7 zArh2MI%ABPu|m(xW$*Up>+Rwil;lx^&rJ9dM9(Y$O_e6hTE0nWHP#@Tku>$Mj6m`w z1`E@f5d>#0cI0N(Y8GS4B5>je_m~k#$1_#At2p=4SvhG4Fo_0pHZJR-0U+{HWSwKB zq#*2^%%S4U+)YiTH}#A$D$eqR>#fKe2vbuvRS~VP324spmy>i7Q|D&{%BTuXc_BKh z022{HVU8$AdQFFj0U?H(nu#hCkw=cq-E)r^o+CVlM;_KFSu2ODCgCzbWBTG-uYTCi z?=AB8-@CD}KnOx$B!W?Whq82GFoh2f-)j>LbYnA7Rb^5ZCWzoR;CWDUOSxB*Eh&Zq zQwrzcsHEj)Gs{!>7`N0LBdH}g03VO>G-pXto{bo$MFGPRvwE!Omzbnh zyR{~jLi6MzJ=_!P4-(Z68ey*|^A|t;kst19{JA$?FcSisPzaguP_|{UPQU}1lzEUQ zF0Ky(6Mg!*)Yc^qIm}Z@9Ki10S=6i(-S%4?N84d6!MRhI1{>vOA}md<8}sJo-`c0xR5l_X1paQIFsRc*1a_|Md6M?x%1rHG?JE_MB%_yOnjco_sfyhD$_IK zCgPY0ameM6yB~qmg%ffX{&QM&_wm)i&Z24t1r(~n#-!6rLQPbaO=}rNKG#aLko2`I zs!du1ktaC_ER9%HS;dw$c8m!3!*M|*#R!~nsa`1^VAf_VEZw+7E`Zmp6G9| zkf%s!OO|Y^ro1&PsOub2RmH}92y|-ppUB;jU==C*qbT>*TC--RC%kY0)xjBZARtAk zFSyohUS5}c$lXgmC zdYmI{B++mo0`rKDt+6OS6v#{SHvD$mJv}ut6q()Bj5lMEZr0S|He&ZE@hDNyVgBMN ze!O|?8N$vy%EYw-FH~jXc$hjZu;2j4I!#M)U#peGrR7~c7Ew9F?~;0Ja6+>qTFMfU z>C)vohf|qV=;z;l>66a|3;K`5>regUNB`NbK|HWeY}3?q8djhQBIKBGd7XpZy%;z-^-MY5Y(leSAL$8^ET1rJw$Hb?M2Ld8wy zsw0{q@^ok{FVoe^JsMBp189W?`GHcF5 zoag^N6LUZWQg(iNb0kwE6M5*Z-VMMOsFZFQbghZ)-O&F zA~kNNy<6{X>t=dk)uNmg8Ci&=N&t+BIhy6Qh;pmMS!HFSMq5fiMs*=bQEg58*36`` z$7;HE^VlnC6%>Ie*oMr+(9>Cc7HODDeqw;W^wx)NlKai?ypI4e8!J@6l#!mnjFN;J zh(yr0eV5($JtBkMxHsyCX4bo^_BKRsavLRg!A0W21VIWDz^LjjGKP;z`<>C;a=fT1 zu^m8rIXqzt4WEaNt5P(9j^(1JW42YdLsn9Hp7wZ5;Qh1(S9`w-eGm<0t|0Dzx=`{f_*Y5e*NUC3`)0d?A&G-_p1@rdEER|G8T8!iV?`DoKW^^xZ^egE$J z`;o$3;fS|Ai2Q}tn^7~($b{rP$8hpQXRtI6Pt)*_LI!u!W)c7(bM;jlLMD(fQ6~Gq z4bEWQNCcZvCXXTW*f{3H7mZma?*mUMlDKy@D)Xfj+82av2e#20D>^GQ8yErp(tevoRqpF z+JZ=g*NukCH+snPi7W&4$n+Zuo7$Mim8#_I*2ow z$Q{UJ2NUQiJ1B{yo2Xc~8VjlKNDPmK`$2BRC6Q{PBq|y@#&C)Cnxhq*z> zsu@&RMNMHdLV601>i3h(L-CT}WDcgvwx>z!T0s|w9QcYNS(3pkI&JnuD>SN{N6Oq( z{*|A7>qk}f_4O});jRDhH^0XmgcO!$qDq}OAama}&BwE)$)w@94HqWmqLD(GSQMbq zS|-L=)2azbWP)b$7_P#ts|xjINZgi4jSi*n+|NlS@182LSvHdFiLx>fgEA$dB-WTzsywO(msSMV-Q=Av6L_S$6CpTBiMTaY>ty8s zta)kmyVW)%$Rn9{k?4&Zs|t73p_)b`=vdcPCcxHo>(+Xic0`yb0d<8m1B1lxoW$_$U8NkTc-5-bFJcnlnVT!P61+D?e!%=m~0a}^h zpr3s0#aACbbcVxke)}myc?k!zis5<(lZ2=u**3F^?;0NN6{tB)jg^Vq*@~y899{`^ zH5T#HOeBcti zSDu;_nWJ=Oi9~q1C-MV0Z7iYT9gX7>;0pTrkH7LmKaJmdvHvIEd{1WpBTpm+k*cbO zsH^fmC_JV!`usJ7h(7(=b1JX@{x{z{2AF^p##kkR6yer16T1_|Gw84SI&n_cQ@Ur# z-D(+P36C{2US__jZ%zu(Ekl^MZmKgC!HkR3cQ#QcD5K!nd~+uu>ny_EG&6lfTq=cU zT(_r~Xre0CARZtff{1g9!>{H5hR`M3U?svFyT`jP_=!0?Lsuq=W1aK$PcS1QpnkP$KjWQGrQ~G~%bt{b5YU9Bv5Gd8ZW6%*g_M$us&cvcg^Z=ye3rLnDpESo38<|j zYqccRa~`^}s_qO2>5G|JNO+2kaB}1;gZB(?qXV0~jM;M9qX8yZQ4k_x-v^R2HKT2< zo3>_+$RZ6i;xNU21R zIU}s7j$|-ULq1{^`Q)oFeCfFeBL5M3{lX_+`7@t={r~p&zfNEgC8;imdQ*@PE04JC zduEpCnxGN1?>mz#8#n7BOcGQVIb}-5ZQ3ZfrnXZNKioxg3u(*Ib142-2>9gb-_j z9``|rO*S*@y(RMvlNEFXp`Xv|vGAUqraWV`Imo)T1+Ti*N@vF`2xgRkf!5%2Nr)#c zq{vOkh`>mc{+LqhCxX|cKSg#EF=aDuDy?bjtpyK{+ieWE*P+CR9JRKv@Jx6*uvxs& z*_5?Qv%Xv2_PFhHq&X2upc!YrSUpIJ^gUgQm?1324OXcui2#8qNhXK}fX5gfw-C{8 zVohc140a43Ged}kteL&=U|TQ6uP~%A=e1lOF{U0d*8nDwQR$&P0jCL}$a!{16>C$~ zO%P!-YE8O|F*7(aANTP%#vVD!>9eAS`7oCs7vsEr7o_5(1kmT+cpm4>Z@n{=9*_Vz zC@Rpt0w-g45$Ua9TcX0sd=r0qt9LBj(<8T5ml;24wuy`;yX(zk_Za1QWT#^(drC*< zSR;~i&XB+q%wUd6P~syw>26L`RfS;H%Y*1kq<-fAOWwe(0z1;~w9CZ-_>*TAH8>nu;iQYpt1Yv5$QW z-`(9yE5cOQALPfMS9$&a`GfBrV!47dR%I6MY}%~>?6EuLN>a|1O>wN>XN7H&B{^=U zBdR+p!1;9Yi4@3~A_r5n2=W?<<0 zRxqB?C#}G%^-X0tsvA7Zl3J3(^0k+k&5l?vp8LXM`w8Nlv4As|Z8ET(JiHFHdH4*t zhu)%Yjn=HSzHQbwb`Rfu-^Y%GSWXA#nOKuS^=P?!H*DRSQt#r%@9&H~M(jYWlWQ&( zYVAr0CTD_@81q9nB8uTb36gR=96^q(Qev|34Cb24G!0==!e)}GIYkCVQ=J}}x*1LR z#ckW#3@lK=9PDHkb$1^z-I=sf2$#S{m?}Enu4l!eHp3k^_cF#?QDIk2(IvhLr=)Q$ zAv^*zii=H~Db<#IYaGe9eOAH{L=BNLktwWX<;SnZF0s>y@+)Q}2O?>7P*WA=uB@cS zvZ0i*LX3qk8&-uRs0S*Z$Jq{o0L0gox;&O+8O<1Xd824tKuW z%n2SIOld;GY>ib&0AuQ=k%$%`kLd(2??_Fr;F0@q5pLRAW3?ec3tYgb7Q3GF63=;< zQeW&|PeLG;BAiyR_~EgS#0a{1i134$iFSim48avvgFEQl!L$aD5zZJ6R%$9B@m+#v zaQFNRg%?=ziYJ|yI`B-G1zl5VYA|(;Xf8tC$$MY_ix)0Rr3kCw%v8?Q$c!k81u|<{ zh9NNJmYh~h&q~r_&QvCs@Prqd>LMdgN+d-FB?PqfqK9j5MC8>e`I*Fdw;{qDXwo3? z2o|xHCTb=;&aG$4p{~Kuu!g?oP;r^ft36q%$*T;^q_?BO-Sc!Fp?EBBIiTMN*Vi zMO_wd{@6t=tt#e|G9QN|S_^unoawua6LWURp7@#ft&l$@3_Nn{j&Ijps`}dg%#T2r zn`l#O+JrSSN5C_4^2;vH2GBFt=rbP~6Tkh+9|^^v2lf#hDI%h9(O|fuHBl2aZY7jc zHs|3v2HYu2A|>*o0NJHx5w7nMif{Qwp3w0gTAqLqs;U8j(gK zt{jcKUB*?DPxnRiF(7{??~~_v@3OVQB~O4a-?jzAn1O`4QaCd?mA!(yk|SrlAR?V0 z%w`mjBy4j=Biw{ZG_WUm*2#7<(`(y|L~+xEYhXqu(wmev#C<`mSnC5NPy{jI#?B03 zwWccCjocKGd-=%A`Vtq4@6-!&1nmyhiNI*)l+ftH@ zG3sph!0tKb&i1anp8H#HK#Vx&pqkGBj)`nx)TD|Q1^k>>APyvhij~X>uIS}wG6t@MzEdJ{NTh@g*VYVDN>nW; z_}m*We(|kWe(vp8zV!C1uf5QJR7U@U2iUKE;msHS?$_UANUP@=p)|uf<@i<~6A^PfzS`fGiN_ll zZmiANlv}VSG)YLB=?f3Gbuk4~kZ^)`s&RLIr{xXa4;P_C7Q5(8%K4`<&SYQ%7{I#t!=jOEQ6ilyz$l4{bB)0Nval zhkIu7oW>DfGPzH$HGg0}#l-xX*Z;_DHEZ))sGZ0GQ-CCB4!P_|~ht2U)j@~ZIS*x(%!WJL0wY=I}> zCcK$M>TRGfzH^4bRI$FoGVL+I)7#D@wwa1P7$M=xb=3ez3zmms0mPB?^yWmYPfS#| ztpW6O9}!@&qKhAj+iM%f&!nQ9wb=|Z;t5UQ@`qQH!wBp?XcAxel!wgIqqwUidgRfN z8Z#M@0P2yCqaxnmUUXPW=8+V`N5(#OGil1E{N#xd>F#;k^YJkUD9d<#DrOl(j(q9} zkcTv*t*MD9>5XF_6|=Ii(O?!yqJ2aXQe{)>$|mxpcLrj=Wr9KoN>OCCT!}kOEJ^a{ zNlZ$*8C$c?e2dhiGU_H$m`YIopw>&d!h*Te@O}8c#{|w+1~QI+O*1+Z78H>JX|!ZG z7bW~uc+i^N5ueBPT0LHj0oR6UN#YKQ9ST}^N|ftnVHH)Sk{cd3QMmvSgb|>@Bpaxy zsqof}IS95#jMV98@l4M!L%kU`f$H^A5tjm0NhameWrMED@*^jPae_zI&SX9h&%#dc zQfq5Ebz}(f*$`K8^Xb=~eDUM2eCe%MzWCP5&xZi|kH_nmKJ)so{{FlBh-13@z@o~= z0;6a~zz&Z*_O}(Um?CMK%-ofj*n|N|;{-)Ur9zb#WKErDI8?%;q68&U5}CP_2X{(1 z%32^A0%~l{B$6J*g{S2bhemGM96_8(yQgYAMAn+MH?&cbrxFP8h4XuyewcCV#0lCx zMdBgAOubnq_R^|dMq=j>nC{PIzMp(dr1OkY!t!|`=X8G#NzW_}%sh7jlc+*P!`MKL zCZ@rRm5W16Jg^5B!m}KZg)*%yd4ej7vUC$JF4QQ<(kgqW(vcGB2|*?YN`@`m%utr` zlKP!Ys{rHA9k(nO2 z?S9++whu2J)}cD(V{$n41`H#Xm{FQILM|9Vj zAkzsl?Gr^MDx?>i>elKj9E0;Gf9yHkcYp8O@2l|9Z65yB0`R&>B1K9aQIT{@t{}_$ zyHuSXIU+})FEPMCEeLjNv!d2UD3zt7592N0#0-#t6sdFg!(~w`iu# z3>~Akvd;{t`5w4_B-cK}2)YQgPkG5Pn4iLiyo7QbL(&pX(NYPDDVqvvhbE#bqF_z) z7|zU74tLPCBgloCQ#9F{N^dICn%0Cz7-I%3oO*|eMh{v6+$@?Zv~?q*%zZxAz%pwy z5{?8j4`*RzY0Q-yU@?}Y6Zf6RBoowfQW!(hWE#5EnmRcLQ3^v?Or@JVG&8YEyQ%c$ z$K7wkM~%au%`{+UPEM9&PUeKlG4=&%65vD`88^3l=-t#rWV20~&EwIr`?L@}ju?Sk zpf&1^3rVOdPeiwtnGpdGD$9a;J-}jS2=R|1o^O#&Q<{* zeZhY2tyjMI)~jE7>($S`{v&Dif0oyu`l;9d&AO?5;ZjwVIg#3-dgSca-YEb zx&a-@;X3N0wI(V|rZCC|xN{2mq_*NRYDOf+?qq7#I%JGT;*`?ok*o<|73M`es^F0 zpJv8#e)Du)En8z5eCLm`TrDUGVSR6xa{JtrvVasv;7#TfpwDBWbXFYTWO9c{h}0ZQ zd()=2sfnd%}G3sCp`rX7%5x>A|fCG+_=UwjVF>{s7;WTTZlS5!VeAeeKEgd9Eq4_9xxc8{hk zjrAsR^O{}dJE+<`wu?};l16z|2XKGK((6OA?r~M&?Gv%oOuF zKq)+#U@FP7d#bWo@5$qFN7)GZ4!5*q0Pu*LyD&8tVe7^o6s7woTtr#NK!lPgBKP6U zWMabF%_0fu%e;NcGido?2pN%&`%Yxt+Sb`UiQK7@GRr6=ck{h-XMUo7*Vji6k}4&@ z0ZuHiH;$-;=?z*c?&B8+(h-C~)#Q_@w$@wW=v2DGTG|L^jsQlir0!V|A}Bn3#2DFH zYu&6hk!~X%_i@{om0^0yC!8RX_bRlR$^g4SM8CAKvCY)wg7|B#B)z5&$n@bm!Z5<{*T0#-h?EhwyMVe@$xI;ePyiSU{?C*&m2i^~V`QqzWaKS5O#n$m zM4p=Ja&rgv-5J!kZpNxA8B=5rzPS4VLgb_Ynq)I=WmT;CK+s9(=W{p)DnAGI;i{s| zM5H&X$c8*zD=dfg@Y32Xb~2?0-3D0n!K63ptp>E1Sg>QnHvIyHV!|cG$yq! zMKM!^AO)EYBGPH-p94nBV8dWQYj@XK0rzn`_#<=jEm+5CT^s3+F({gunweQ6*_|?u z72CO6WzNJtLPdHfsl;;;Q8wk`Pgl^@KE~~F-}l`!L4rCmj~(E%@-r+<_gpJC5#wqi zAcUG&tBcfTPn4JwGk2{_cFfl!2p=G(KZ8WLWD*d*^jr{cUw!8(Q4r;frYMI)q$k`H z3o|@bH0eMCz|>4wS=g3T6tsJcgoh_(resnmHH9%(LrbhiYyp!rDMdEdbZ)|}XRk?j z!cV%Sd|YAICqw@59i8$IS@IK^TXdTKc0h&7wri@3^pd-e+kUHI*HwgsJ2dZ-&wLz!LS4X=U^ zQ-mKClR8Nqow%tsqpFtkh=Rpg8P%~CGh2~VO<0;y&m5z+lNWyZ5wcbBw}H}9Ya>Tm04I0 zdsyBbQJ_;9d?ZwoGr6W`c#5ZPjUF1AFg4A@7@moO%vHT!+hZJtNHB?X6H^h^L;!-a zc^|gm)k@A^Wp@yj@um23SV&8pYe_;PJe)C9RM^B+wKa24?l@wbh%dIB^*d%(as{^} zmJ-4w%Mg;*v^U#Sn<$GUlLv0YZ~M6Im40)EO5E`@4`2x=owHU}W~;?5m3Y4((5*K! z6BS}kFhdM1$Am|&>N6BhJaX0+rS)c>)wYnTG~?cMGgiTBoH)`W9h3`Z%tgGJsbL==l;p7iyl z0#6Jdiug}V_W;6NgIE$*wPq_Kw(w?@ip zx{M}QG4%Q(%&-`TDVmXrY-SM{g%GRjSnedzJeyM5J=L=%Or@)&>W#&*L@xJ+hP$hN zDj=~ti=bPo@IzOke0n>6&{f~%8N@w3UPur`rsZswAuP(GO`t^Ag!q~K;hDujXFt^X0iyGsLbC0l_onK z)Te7?Z7Wv#_Kd!mlKN%Z#w-n)=}XVW*Z5oCdKY6SMiFqB6-)^pV~icM^JFQf(C{@w zOKv)&8oG)xnUFc(DlR&GBt_6ZO`p<)o3bj4NLTKPY+75A$dxBo3n8LHXyGnR<9MDJ zcW3#ED>}<;OtQ?}o0qCfPk$s*7){fRR9S=qK0Ke^9&fjOudob}GpTCYEA#r*xT5`z zJDdM#OlN-i_ugSS;!)>mFn7Ik4KIUwn00-V0n#pS~=qH{_p2Kf^=cx%l^acR? zK-57{-B2~dfIBmHH52RFGT|IWji&W0(Hc%?VuV}JCelQ^N}Ak^Jvk?6=;)s3>;OOn zZi7T*Yt)3<6q&a{F69uIabs8<^=ZDOO7`W55VdX%L}T|Es8rdo7e4ihWZ*?f<>NnQ z5`Z(4m_&rMG<#wq6%`wS;T{o_YgH*EC||i8X2`>(5J@@6BX0Z68_N^wT~w@^NtX&8 ztmr0Q+>DA?AnZXrkojOH%v%#84J1Zp(QAdv8eR#D4uU%oRIFQTt&vCoIfD+C#j*N1 zrU(olCX8;X(yWc3!A{5Yn$AcFTA3ylHE7D2P@&4=Dlt=QZELnwk{yvpKJI?I`96Gj z%=per-YV9eLC^dd1TOrjs;QuojYU|MO|3S(rYa;1VougzJ}&R$OqfM*&hk1Ud78Ql zqruDtC^2a!O-OjwK$-9i_k*XnzN^V3v{HXSKu}_e8j+dkt%)j=Sbc3+MRt$+{Ub66 zksRQF2Fw8yQIu?-iB*L`+{*7i+zWP)c?{!;3P*b+bR8y_gM3Dt$t6Pk!`;rcZs=Pq%a-^u}Bod**J6Bp;g71j=(mqNExx)R2t*MWBX=9UiJ5FIG5|&Qp5+1<`qD*IsO3P82jMXC@?Oy#2Vt_pf!--Nk zd(nYRq2g0Q@CKV zJf%64I{|t|QFtmSk7&_n^Z$x7s>)sY3m^N?%j@qxj_<#}3o@n5ga9aN(m(56EK}3` z6-HFPvV@|UG%Zb}Y(lD}iha;tcUw>fN02LpkPA%HbQZPBY&M%WPjO{4F1=NPA~cZ~ z#NLs)OII__ob3=_=GV*Ec!_GdgjF+(nxI*#5HlyzBlg{I`@Xwp)|}=Hw!S1);i{v( zpPutsAN;UifBmc9zMu3nUD9-B$wMaxHz}%mB(k?(fBEI-GH?3zZ@;fZmCPDIuIP?w zJw!$%2+EQMCF$0x*c}1f<1>!4#yy6RtC_F~H|3_{DixbDZ4!c0RFX)d5wJ+ls5IB6 z(sU;Ko_*9ZcOCkG2sr0HTp%ji%vwk!ds3eMjdMYLTj?{kDQ+AGdxDlW9ds>z;hY7i z<28|~^ShaJQ)6L}NABUb;kQvBsPp|fuTxyI?H04BWMjg$%QD+G=}mjHrgDy_IIH7c zqvXR}i|gjG{v2gu6&5n+>}q2syH!_H7q-m$gcA~&SympDIcd{*P&lTdi87 z!#IXi6{)(9P##^Cx+#rVuf z?xpFCNL+=bEOo@zX(MZTOv_uYM2Pgfjc_ruZQEM2wpoQSxks56V#>}A_g=B^t1cCS z9&N`VQ%$0Ih)3o`EKO6_q-ldo2}d9zM{iHIht_lhCr9ifsk-`H=gt(kC3xS1nT*Ly zx30|a7&Q_({bzhcnRCuigQzI4GVusYF0u-)S%*~JMTyFSfeP6sDn4S~ZhqSN1`!R#4;^5iO)Q+sG<5!jzlQ zFuo1mNA3;}j*tj(A)#RQG9owQ!n{-@k_oABZw;%9tIK%Wh4PYZhtum`q7M<7du2Wf z>RE3f(>=nA?F_EOXlo*BLM#a62;WECb|3DU#G*4#`wq5-YtMO2c)CEZ<&Q#j{p;U) z?+?EJcn5r&I^aCVrPo@65y+cIK!W0cCxEH;&I1%E=_#=&EkZv5K{3W) z>%BKI^4svJ15s#3J!vY(UQh3e;P7@eUd~I{P57sxF=;I zh7$sqH6~v`hk?A^27{WKC^ywmbq@kW8N%rRir0&X7!e+!EXvvhbL)4e>#X3k!^3^@z-zsgcXy) zSzpgA`biSFnVL2cZLMG-*quj&S2lWL20*JBD%QH2F6n|jIuss|9J1EGf%0W-fu z{P{Ot`ofzpe&NlRf96Mn>gRsG|HWVZy;WfVOaXcvQHI%#wQ03aEt9eaxYMw~00k;6 z-I=jw$QDDd`6wl=-f@IufiVN+ggpAVOQuN|&9SUb=+h9;f8=YnNK3 z#;9C+Vj`6P--p-3A`CUF$PYFXhDp|HXdVNLFo&~ZkaFfV6EP#v0C`az@=|p(-%nj? zwhvG6fQoF5Or;q|=1v(Dm{l@gXj=>-kC3sncP3C#J|ye2Ri9M6)tHoWV5PTIo*!p$ zthxGyy2c<$aQ)UMi);jx?P++_?skqJW+wJMdG?|t16DWol(Bzrb)`b3K=5QBb3p3MhM&O81-?-_GzFsFwmT*x30|+K!*4nqt zZ+KF8f@1X!Q-^IzBrsGOF)h&n&L`qb8)RngCL)rg1d71&gB(9mu1w!^$dVw6JO30)g*mHuHJtL!c@_TX2L@D!q%IJ5=rK= zTQis<;mC;aq!GN{_bjFPI*(?S_zD(R;jB>*YOaYW zPk~&a%vs)eY5V*eFMj@w7eDvLi$C_H{ixD?{;zi)$A9zx_#YqC4Q65!BHBF*>|BSF zvTV(YyaVjJM*^&KZsdu_eK13tRTnFSO@%-sv_yI}b0i{CA%eRP;s@(83wOSOBhTuI zdFK5l!aZ)1!l}%{y7iWj3htxBnZj3}EFmR8gVHF{LMzdbM1v(Ky7fYS;G?)Z^UOgr z#W(Qu@z$F)HD(P+?j>MbbK)BP20+AGH$qOdp0KJh6NEE7Vr1m)_UL0glBim1Gsdy0 zt}S4(`>C>2s#>iLxvD6OsWeq)@`&9rg67+)c=&E3wW2?1hL38yaPg$Qw3<1V>t5dkMjQz=*yaLFPZ=0 zu$iIUt@SFeRX+b*@*02ryN{tprI3%<-EWSYqq5D2iBx#fnTQX)CM8QzQfPPgz)hnK z?WSAPrq(;RM!jKp?4Bda@>s4MjD!#D31Oa*@-h{PD)W6(GMReS70xzihnETVswyJG z$4C&V5=&*B`6%HbMA({kD+#5jFd;p7_js&D^0YW8V=<=k$djI*UbXZy#fdKvMI;WK zzz@5;et*Q@{N3-Fs3T`W>Is4qBCyoP?;H+J4>dS}eZ6_mE2l5H!IsZR$RBV^I-hQI5eG zK{djxkWWG$BcDl77giC?Iqj$^lMw&>TQB_&|MX{nRN($_5BLA-zx^LoCwgt?ketB; z$ezL>!fdQ+ow#uCk;NLF^=w4$l%mu^7J^cY1C|7u%1L-3i5TO7yqXyEN4KoP~h@{*s z#n@mFktnNjIRFz#&wY#{K_|_511`Z!Ck}6&&Iv9UiwVkIA#x28K2djvq`J!MCIqvR zjhiCl@Bp2E&p~$0GM->M)kMxf@l|2-;+!yuG6Pw|(0QhNz}GnGE(hqslD!)853!6e zn_5$4P$qmtcxfXgK;^i{Y#fR^enbS}BFLfpK$Js`&npg$n7N7c*0$byvnC=;pM5U) z;a~gK`-50n%V-*aXL!W?8W3|yuYxfhL*atn1 zxQ${;@*&J#TAgPKWnMb5dr>o=KGaI6{X4(^{V%@tLqlKxrC?{GnNe) zb0N3(bD89kyT{FyNtLx3_a=daXUuk&7g%}H023g}Av`h^*~GePQ+6T8vA8G|tQ=6c zQrZUc=0ij_?aJD%#RTkN=9=-nNSs$X(e#R)wNuS`S=OEw0c3b71|$tYGK7^&Q^ic8 z)qYaS+(a-_&4X2ih$AU-x(mt?LF@0OlU#I+y(hym0grT#W-2C4rOe`yxPW}hjjDwe zOkf{Fn76)F_<&EVCu)eY&Jam(Nf!aka!$mLT$Xdjzhu+1lBD6mOrD`qJQ*vRg(FAC zDCh+p$Z9@fzOObY+&#ZsOx0#hRc`wpnWn5nl(X%eDTO2A7I_;!#)zo@p8$)uw`4;# zX3s)-!57+(mDgz|Eay!LN~mb>T|}FbY-*CXL60L27x@ZeKlCXT`yxa&kE||%6S?o{ zc``|P2@ed9T|*?BwPxJAM*2R=7A@yU z1jiDME5G1zcVgbGshF9y$YTFfCP-)+zE5uE#S2mc?26Gr3-8Sywl0#H<2C`EIrZWy zbziI|Wq#EooS?0ln00CyF-DaZl1#gYM?UV;7F;J+8HCrytqy|8IV{#k#*|MK@B-zp zC0&=bXu8CvEIco`RrbJ8AzD9W)=@sUaJVx8&C8}`_Pl~e*gQoZ+!3lCr@5zDm7^n zB}pQ8P)_#(<4&`*G8Pbrw(v{?x$WeR+a9LUl$((mwT4Etr6eR#gR~r;!za3sKnbaI zYhKbsLNEniw4v7y=n~L)I0wq5TCQFQqbax9v9xAQn9I)Z?z_j+fqfniXGYBwZcE8c zj_MQ4O44co3%Ksmj(`Wahpnl1U0P zx^&0Ks6!IiJ%q+4-mJCOz{m_>kD?tP3Xheyc(w#n8c;Bb^th>-nJEjAWa{t{$yL|a zp8v*K<4&H*F{@UtedDAQ5+@jrqzH~cM7aAH4$SJBu8F3&#z*tSW;TePu z)G<*P)F3UEZk-n_b4g7JlK`kSQx#JdDO{g0S4`LW6VEXf=Kl5KW-aBu0Lg@isF6}{ z=t)i0gqx`moy*Trm)6!7Pt&20dx|qZ;>z_4>d3ov;6UU;buq=14I1 zF)m?DYo<#Sk;CIQScG+|TDdokFc1w7&!`HU01w)Si;zlF6=D*mu3B|$7Fi+|I%ytr zLzo^~E3%{~BPp+C&7u%Q&|um%Rns&@cx#fWnK8<20KNpAo?wtj+}v5YWr#|vzdr(V z6n9i-bf3d~vAuyn@X{2DbhBpK+mr~;vCKM)k8II3?nFj1Q3Nxqh?p@Gt5$=One`r4 ztso+7HFh|j&UrIr&emEpDFVgloD&3@!yOy}T6_e$o|^LY(Vl9E57d$eG`jG~b1~(a z0Vt8Fs)EVy^Hp&f_A>6l$9&RR7SQ}+<2h6w6(Eu&@Z_{s*1=-zBpp(TOeay{q12N*sDVsbSyY%vOvwa8 zbC*g}t1T?$an{ezB8l)U>JA^s>R6ZX`jXd@6HU2w=}lW}s^wS6F=7m_^72%(yiU$QTkaMTG@Ox-2L$MGGTUqDbw%S#8@&tpWfU zU=_(sUE*OoG)I+ikHNf0n36T#AOv;iS{M~PzD!3kkvLGN z5!$6S6QWkO$Qd8Yi7PSpNT0e`fSENxw>_9#rHk;^l_($gAg|Du6_Q0#7@SF>V=D7? zW`s*KYt0_EjhILJa32F@VW|Ock?H3njk8ECh6(}1N+=o{`IP=XU&LRRJJ%|7PybQi9=N_<}Vn~96$l5pti=s>88 zh^+ZCJkujR?my2fl$>x`Q(R;mtRzx01(~{eWx}l(y}=btCTuEAh-C7oPK$YY8|N6{ z5|PZfrZcjr_N{MQZ&qDo66L2}eI7%^FaN>2`|cS}y~UF!TWh9*O{5gp`yRt}Owp{^#)D`0985>aYDlGigm)Q)5vf?b?mAt0#?+5jlcBF4K9yC0L4q;X0rKRR5Y%${*4e?w;5=Q12yE0naOs!dl!z;Cm zFFp6+Y+K~8V`2f2kKD;ky*F!G!CEpK24WRq;v^(x#H!2di3JV=I5V573Kjq%njz%CUAx71{K{kAEK&+_6KPi9S5;xI9Gr*|aT_s4?zP9r8S2PzJ;#gTv z!%TsV$6%u3J$Y)tr~HEDl&yh@I7mj&ZMX<;wr-8!K-7sycfcl|KKsBJaWhw;-n5za z)&e|oUxS8qk1UvJ9f?W2dy04;QXwSh6q6;5!$VzwAXS;5lc`waBYmcX6X0Y@mQ*S* z6gN@RS~IRmM$WbV%v;U-1=*=R!)ducFEcFDzT5?&tpIQ{?oFC<8RT<5m3QN&=`&ih ziF#1L97_~G7n+!QhRJxw)Mrj7<};&ex@;AA4z$kpWUge37eYMIC5ZG$BpFufBP|uq zHTdC~V`P<%h0G|`h9<#C$V^4HjE-83>x>}F%w!rO+So^oG`O9hzR`4Of z6c=RgR2dfd#4~ba z?G_Ocy<6*Mrc4N>BuQ=D&FY!Sgl9&;v+T8VgqcKt^*6rtul~Z@KPq|uL9d(Vx8L8t z@!tOR@87=u?(M(wpZkZBs_&lv+yB>Z{yV??yCq=ShjWZ{ee$H6>5ETZF!T4`-*0n8 zj%8A=0VaoIry&fpF3QRUce4Ofz}KZ(<@4=v=M;TVZ8g;`fVvIK2t-suR6SIZiO2m$ zgqNN?n5a8>a#natu6B>5l$w$09=nn-L{ykfwJC>la?k-u#d5#Q5>ztr_;_QaX;-mk z%^M_=rxTD@jhUCSN{&aJSHr&}g&zs+g@?8^iI7Z50E#h%C} zo2@1(ij&Mqv=jwOEJ&0SKtLQoKoCQ5MD(q8PR$CW=H^ zvS?ePBsSS3yPHFg#qO%E>YD4;oxZd8dWZb+uC@0*UzKP!*|$UqI#sByzIBIl_St)_ zcfIfP{2m(kCPXMJpmWcZ+b7;qPm%6j-T(+kKw=_cJWOyXSOp0~-I}=>x+gaOWQiO) zinORPB0Hd^=-$?ls@SOk$6)Hz7kVO(&R8Ma$x#rHkR|0DNyp0GKgK>T`;@zQ67_;> zo==CCsG3nsB7(>Wldzvj>Svfh)=M5xIqhBf#TV z-<9(E)mvHqtNHEoG&r}@|7u2EGyCM3@is>NFEFhT(gFy*>bhKhtF!cl}s)a`8 zRbwCn=ffFY$r6!nqp1iW99#32MOzl`z4Re)A}qqS%2a#eL`3MKNt$o{K#QM#@#c?x z@PT)2VZXiTU-+3{yV)92W^E4j9Z``|L`tbbAgDzkSXAV=@JzNe)gFtLPnSRWt{m!q z<;9zb5@D)-em_&PNEZm(G}_nW(E<)>!F{$&@<) zWB@XUd0rS`A`#+R*n*mau5-48;!$TXbib!Z)3$I_c1D_}>WF$=I$9>MyDoagN1NS( zqM6R;hF%3XQd=<$#ReqYiMj&nK?j)Ld1~ zM#kT8cE<`r8Zl5ihrM@63~WI*BP@0R(7TBkm~pB!6%xv>J*8!NG-{EExAq8JRBw6j zW_77Qc;HSNlk#OJ--*&;o9pf$LOyh?HeA;?`LbB!nm~%5H3F0@Xgf-a0Fh?PM6W^&0H7D#XX3 z!F$7a-0c>U^bh7r6iCdrG*dMT@5#)#hAVZ*?v81>!=z;DeR#NrlNSu9D9Bo4EzZJh`Uo6RAd7$+1F0U#~lppQ$@2NjiQFgf+$Q=l>l^9*BIG)-N_AgrJVz15<8Qc zdm_~;Ln=reY-E^rZd#|5v>*s!BIF|Aj;cHK6XGgo+akL>S_BhR^NKEvQz2FIP9EE; zfz_!TyPUDrR%GsTYlu+_2#gt@yR2@?P#B7vkl z4M~{IGDjHGSe9V?{Zf8G*^1jT1S74xGre;wVj?L#RXS9x1rU?OVy&fkd!SIk5q

    Timber not activated. Make sure you activate the plugin in ' . esc_url( admin_url( 'plugins.php' ) ) . '

    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 z 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%0D_;FPj$revQ3o$rs&`q z8aZ=;UhVREsK`N#sNZX$&)O7(ZK4h5vaW;sV$JBWFB&5kPc}9+_QS#cX-7b5&!uOC+G4MdNLglEr`XSh~g%*vX%}H3N<2&L;h=t^r?ErDvMS3Mc;2?}&<{}D`YWM@$ZW}$oDv2I;d253^=Yf+ zCVN&at%PJZGXi@L3ct0vOYJ3NW)=)gOhzoygwzzZnKVQ7zdulvyiCNPB0C(TY*K-M z>X_Nzy~KRv0&aHoHr$EHpG2L5g2*yB*fBgJ3?`U^Uejr?GU2C4krY$B2D;t0Dck!E zl8jpdC-I**t`U`LG5fQXnZ{Jw1jY4UsenBZIR@$|>z-WCF!A{BMe;SRt~2rz0G=W< zHI(w;8!jf@0shrJIzIBB4kQ|*Qfw#Q*Rk;NSzo5vV(7v@!hri%nNWk<@%Gf3qy;EH7d-B=e9OK0 za&OrN%I}u{6au*f*m}C=0(3>ipH+f9uq(nplZq0GSLT#-sKoMegEF3dlkZ7|*~zAR zm&ye0E?a*r=Uu)Jb2j&FwDYYB^sQ=I+EY7}G20#K5v)+{ePPDU+LU&>G`dq_)bqs{ zoc7j{D9ObF2b22ruFpE#cU(_mzs^%<7vEl)3T+jNt2di0^CYqpID7X?pW`A{>zMwm zy|XIdEWS}`xgN^CFQ;!gI4^A$vfKGj2~mm9L~#lBae?w}Qr%KrUp$GKj1>(I#v9f} zLQ6+0;=7g z`543(UVzu~P;C$|5q-v@k@*T)%OL&{YjHbsPmY|G521jdXxDaU{tO@*s*<1S!K$qk zLuub%quWJoXJ|ge4_|W`vV7hpx6-PRpXIYOUzT`cHjYZb#4%7NWEU`HAS%(L3nuok z$lZ^{Vm@#fox_G z;v#Wrecj(!o&3b>jVS>*l32~>3;t#OWvZH(3#A%3{sv5T$me#G5&E@Hr=QAa@7Z-_ zZ9h#d4yzsS{Mhi!o}7yGQyShBeW}`NcpYA>tVK%iyI)L3pT(?%I)Tt>8Vf>3f2*Tg z!H3TWb#FOS>&iDL)fKQ<2vvcA`EX3;#A5NQhEx9+Limr}B_)*p6oWDK1WMW->A4Tv zV$V4*io0m0lUMmZH`chi6e7jIRbbV6rp+e;N5`Du;Z?$j8K#TY$-Rzn7>Ug2mo5ut zC4`;F?kdQh;oMwLRR;zib7$u-rfroog;ouahk}|8&WeMgGshXDnFzsOA<&i+` z{@nRM`bE>uIcAHBSi7B>^yT4SKsv#LE;tYD?YBny9bv=Gu@p{~jTRTzgvj(K&Nk^7 zZ)llud?%q-dAPVP^JN=^pY$}!&b#!*n*{kmeM4*7=8^3%GF%pP*F~S7u>7`OQ{Z+V z1!(Ia;CM>rx^ri~0grgMx|!E4tP2gDQbmiqt z=7206N{s?vKu{g@+ftgrfopy;E*l(K1Kf=02}o!+1%}SdQ+TRzpHc_p8=fj8@IfGg z8s2hqj>(ffc(xl>O(N-eo*+P<`y+6p;A;exh->dtr5CJVJuR%a3_gR8uhq>wt*jhlU?N7romC&Ye2f|j0>6i)Mt<_q?&aAjnwS7lv+(u>rED` zk$jdb?ipi3v(jEeq$T3lt>tFjwn8x^VkDLY-q%sS%zR$6^5V?J(hB z(&zn%JdL+#z}EJZj&%G7QDjgWIw+}IEsqrrFEZjrN9Sp041SX?&o^Yn%DEPAe>|&1 zVYTL==1>+^Helr%ax>(9nf>uqBdxV>8IHml*QY@V5TVQ1V3c=xUZN35q+;Q2+GoepJG;&Rv1a^RI z(shezu+jaqlI9oX7&_w!{*uKErZhXjbc>zEng}L^k){!7tw23@+u^L92<+ zHdm{jrag{$9~As~x$Tn70s`ZQVyucwS4f`a5PrkqR-Ayoj#TYqw*_KRK3nH+{>0^7 zRgP^>DuBT)+l{rg$gwSh>Z?n%1HJ?+g5WlGPpILqU-Y>@2;Ry6WNbN@w&%|7a6ngg z3K?c9>l6EV$bBDQRQQf!cf)G_6T+4ve4`@cR(YUP9)J-B<%T!$ohh<>u||zg4P89k{PD=zs9Y!2iMl7cds?rT?2k z?(J;oXN$F*@5E>NIoiqt;Wn{^LqCt@qaN+X!!+ITq$NUPKz8$?G%S_yKb@nBTq}<6 zs1(EpJiwfZMCyI)hm^mA(}%2S37pn(fA1#ty%4|3gmW94YjD+v_=|4m3!68$;3t6W39fD7En6 z<{sdm<}S&dny;?sD#qMDS1_1eMVN{H7(&Q)YilQPdxv!X{8#{0#dM)h7Y_Hth(v!_ ze}^6(Vs(J18m>C|-xf!xS3DRT_!$Nv0`@8e9?o;f>2lG zHwFy zN)Jc%xD>$!h-cjP-G@ou`a&9;5#gi)TR6EQ1*;FT407ba zB+Y_J;BWq^VGZ_ti|wzGuR!CPLm18!N|Tlz&ZZWdkjawbx^48Of^?66?%X8ncrr4BgG!h{JkDXcbabv-*KN|L1F>YnIcl|kv@q~LaB#Ygqmp(8c6X~}3cVEs zUyy0yW$unp9>z~=lY{AYj4-s8i0)4HBN1&pdr~XCf&nzrLqQ%#epwQEuFA@Ik&C9( zVo!8MvHs2f;YdUXv3ts?^~Mq7#%>7i1oXe{KwwhEOl zoOWtF7MM?$tt-%tx9wRmH`o$8X~ncE2aSSo_S0!CM*`u)QS^ zpJf`bFgJ1ETFO00akhC3?1;*RdB{F8pkp1ED$=_j%*Trghiv?7F!MXSjGwEG;?G&L z<;cZsXtP41RmfCn`ND?;)gWMy4!i%0a%cq!fgbJ#Gw2K1(4Rv6hfXKyE4F#RO{rIt z9ItkI2mOz_aTz6pRr!Dr1~d(6S@KAOk-Gn;{l3OMF3H8z1 zL7UG*3<<--;vcrC%>sV{U`>z9$$e(TIoCU<%VIZrGa28b;wnz+LX(m>jxx=RdgcrI z*-|?if}K{SP}9dZS)&mgO*TjB=5@7D4I0;unrA{qSAay#Ma&ZrF2q=E$pk$)aO3 zdRRtmmf>jYyYkoGd)pQKP+vc{ z{;Q9l`^@3R7%-4H_V(cBC%>M#m{e_44&vNAx)OZmFLDC}zVF%>tPDB3+JE$kbEUwH z05l-1YKxSR12%zM=7uZUT12vxX-ZuxSyXI{sKXhg+Z8}KQ*XH}HZV6aOq$R%%!I0% zN^v`l=%C)6JzZ?Lqmc=l7~1IdrzNRos+$k)*t~Dy*z$C!!N9`^n}#P%h_!tV0FZO( zbEzY5Q20+#`F{Q@C;r8kj@5MogpgtkZEO&6nw5yuG)d`F>3UD_sZm|NZy|!f)CQh3 zu?fVi2AZYx#rkBam=sX09Y5C>vwx37fgwZ;fno^E6sUF?ZOCXGW5P?ZQlzK}j9Jy= zxN=s#;+zo1s+G(^fa(=14?5(r&oW#m^=eZq2sLNVe{opY9&;W@6ync$0wTm$$ezJ{8W&OpPaR7d08jDt1Q)&{1af&fsp+i1hA)Mj^@sUYeo==GL?kK`yY zHGv~jVC33`b#jX(Kc_`X(Nav!02K&`!AHYw2$QPJCU=hJ;_RN1i>!~*1y>8mZz)~-S;KLfOQYKY3DJ%s=hTL5wfMJJZgYMc8kkMP# z>M10Z`|bCMsfrY4CdObaGAfIPJFb4ON=T7IAO=MbL(1+Xa11k=n&qO?KAFO#jm+F| zrJ54vl2wYSp&21FA|UEu3h*V0#u1yErYyOX<9+uJe|!0Jhfn|hyKZ>%p%;%Kd+_AN zpZoH$$Iqaw$?uNq(Ek5H(d-Jar0ZByIx47@Ej|iAP-$o1HfrINnM$AUFf1@FSp?^3W2_-PQG-s=OrP0rI%&Uo9 zpwUyW=GjMOHR)?#hh6G)p0yb?sryBO9uJA9s&`%!KM_ zNS-@mXD)Vs>tjzoes=Ybz46eVg$S>Z<4ea^e&L}r%t6&sDt#Toh=4!r)!{CY!=!22 z*hY#>2qvYJzAs%bIVpqy01kairIegL_w2d9_FMMn-+JRUJGa{U`N*l24?l7Cx1L&V zLRe@6p{Bk|rHJCeP0qeq=}Kn=X4P(731DNG$^7G|f<@XG-g4*#&u4z)iL>iTkwTqC zn%bc2CBhIOMr;~tLWtZTph`)7&XW3)`{Es20%T$`@0E`FFYjV_FH;pf<(hfYG?ON_ zk(eAdmpduFh|HC3zFeDeT}luMAx7FZX_^oSw3O1N)aP6TZr#6$<@NF9sj18Fq&}7m~VzZ+S$5uK6>qVwr-=|Uv7;s(W3=n}M`gpbtp$*Zu8CiXaQ*ObQP;YTWx1XV?GcN1yzApMGY~LfE^l0mO6bY0pCQuGRBA0E<_ck}cZZ>>^|^on5;hPxU}Ux!2rNNPU{-tu^`vFP zdYj<<{K18(>I#2RM7#gF3tv34^wygW{=jRld+SYmUzkwx^y;rXeCp#*oqOIzH-C%) zGcm}}RwQ~2;x=l=K`HmiZXGR=T)nwjmHy)#IJkvz*c5^@nqRTm`cs`%m5 z4c!nRgvj8^C`A0+k(Ez8ef|etz5n}Pb=5VSutQj$=HGbY+(#ZifBfPGf+7(!wGFoo zg}k?oxlaZ zD{)BYBirY#&#r|XvJM7z6wU>B)pK>p+J5QO3IM@=<&l&3AHDFtd#-!`J=ep_{`k=2 z=YI9;=M15UWXFeP$z@ddAR#hh4BW;ziA@tC0jg;!sn0nT&87DB{T#R$G(kbrC(o_@ zozEP8@2&gZee1q$k+JqjG(#kyg{8>gwQszje!wNr4(^zWS$M`YcB^4M#DN0dgyu^i4aOEeNKHU zSyYWSRo8c7rSm(PkwT!h;YlR7Jc($Z0U&|1X9T#WlD60li@1JCt->aW7Nk6h7Mh{Hr6G>k1l2uOjE3CWi>?>(1NwA4K^ zslN~#GBOVc9_GkyhzCFs6EPD7QB)&FlWE4 zUhE1Y`z_(HasV+l6UrtDj61ml2_v5iE8MWxTO(RKg& z%SS)(wd3D?+kx-Cb^kkVzC0NyUpTV#W8Pv|Os!TWb)P zfq@ZP2)r<97n;~Ip;;2^vrPNqTEDuP9@KtF6gVs_Oxi#JL~}~1bS}sf&u|Tc8bpwc z8DenC6}AC`y9ZI#w5a;BmTL+uszL>I)tQXWS_YT&G;l6780#`MGP-OofX;AEABWFd zt1HUfyI)Ylw`L6!qS<)G;|xZF&Co!?x+M9M=AHos!=sDSzxV0G|N5)P-+Al)ciyt^ zzUy90z|d-6=0E&U3gF!Hs9G}M(x=I+0-^c3+_&}K&#JnYQ_J1|`Pq*xzxTTD zz2jx76*;?}e(8}jUpsj*TS>*)WR?X*!GI0WOo=QqH<2ez(}dU%BcPh5lBQ{+mz*^! zl6zWAeWuJYgb)k?L1k&J`{l<^f8qGjyKdh9u3Pp6+AMI#1E*F${`BIPjx4F_q@5^` zQ2+puq9teNp5W$pucvtu%mCHcfYmD4AQwrwm}d=`+pEZc0Q7rrd%@Go4?l6{-1^kr z0p3%r3b7E{CbW^7fPn!)L`xFwv!&z~BvK>5h&4RI3~la%7xNOSEf4w=aAJTYdPv~dyxBZ%ZK^Bip)M`NtXvRbfZM)EN z8w^2Hfs~}{($|hJ|Jk?SvU4G8FB<{!N89{-2;UAOBezw4%7 zc<|(8p_w$X3HZkAcL2a6r!K5_eOEQ;u<-1+PB-uWCQ1 zmE+ionFT|_eB*F-}Gg{8S<=J5KlHkYD%`-^J z5&Q$-I1e3*JRD7wzE&||Kn}!_DKavl5~x8bT12Wed1zO=!<3MaD?_SwtchEufQXJ} z_A=J0UK5BYAX>$Vt0|x%4INEJ*Y~J`m}ZgBK6l|`PoBPY@4|cTIPji3uHO4%vrM~O zc>GRWr%tA51nANV@(R>KU{xc}hgX3jD1xe}qd#>Hus$~TwpTq(6y=?XZZg! z_h+%XZP|SvHo7_2x6iG<1x0 zfFO>5B=AEXVj#&w0L!ovD;5&jGAu!|r3h+hQZjpyB3W#TtiHKbw{Cm(x8@v89>$n! zt$l7OPzgL14-PIbZ=GiE?_1xTqxt`Te}J{<4*>XyPrv??pZ+R9Fyin1ruRKGex=j1 zF0b_VeA*{pd;IL1kFUPHXBtkN#CBXsd}kkbHmFB4caa|u5w9nt2@4Yvc$k^~zkcph zfBK*M-j}i0exqo|_+(PL^nASwGkN$_-RGPc@E*rINh@|RWhA&Q2YNu=TUu~~_}jn! z)-yNFZ_Xze&F|g(|f-EBd>hDW3#t*{mdJWfBV;8{juM8?F(;?R5_Q}27w4W z^Cr?+2x2ZBq1quS526sJ-sPdS&Wr)-KBng=TGh{nzYt5*S$gYRB|hq$cAq-brhx?l zNY-=&KujXo8#ci%)EJu}Vsg!6nh&)xZJL{AzloP8dYwY>;wJPQ-W+6uYjkYjlP)db z2}=}9EO=@+mz)I7ne=6(eG&XEvpQZUE0}kCeU)^a?)f&n`p*3?{rqSC(|_>k-}6oH z`{%yv8~)IDe)wCy9z)^V`#|u3Yg0k+!pP*wApf$yT&nB?SXLKtO(ve)8z2ATTmSCg z_?5r)YhV78zx$hi`0LzFz3d-<`!`vv7C^$JEvS8m3=LK5hEldVf{O^6}T z%y+YWj#6f$1C;H&4%L;<7FAX{F@Eu#@&EpX&;R%*zxv<(t`Gm-Z+g!Me~V6-&%Sa0 z_?O=P&96TG=Ii^X-`WH4c5C5oZWb8n1~5>r0`Y}Y0TG$GF>hVACc+2+YBp4J1(&YZ<8Z*`oX^X(HSC>x6DBtEF?63^#8QAw zg?U%mzBR}}D|IvTzTmQqK_!OYS0S8(? z++$7~(|+ZPZ~Z@h@-sj3gSFcIksti%Pk;XHS4V7(3H)FGzK{H;zwo)QyuI(kW+r*m z1~YPuwFzy)nda;6Q;RM&jJ#5ZkTBAuC=5-oH@YGo1iy6E^cR2lWAFci-|@j8`q&4)-bL+wS{DX@`%Y%2BPIqnWXh~Lupk5yX5umi zKp<2=F}R1iPtW_Hh5-;*`*QSSRgox!Ih7fRNJQLn8eR@-0(y{kYpoLkMiaJR_34S< z1@d5sKuA<01&~@*?NjI$&>J^yP1@FDOuO$oQi|<4?yJHcPanccNILOk2*H2>o-j8z zgrEETS5uUd`-tPo2UxhuQ`k>%5nC6r9nX$4g`!ziXBK8_obKbCyqjn0&{Wl*d+YhX z_jiBw-}z&|^+@SA9<_2QfFR3DLT0&bZEM0p9(J$wfu(8}5dka^ql(MP7(%#peswQ&lG`zB z>C<9ZygBTPZ||Rf`~H>Z&)K{XXzMaT*sr83WNp zaNGEy@r{DQ?~eEBk7JCfY0AgrRAp>TTWim@-k1SmPJ7&ApP&v_cr1q)`JrW^-rBaw zZR1ScL5x6FkA2KBbPSt0u?7T?0=|~18W6467_hA72MqKYuu^yJG7zUq1YbCFBjD+{ z;gE*Vk=_&+vo>7h*XKsChT)4duzG!{T+j9ABW2!mIamM#k$&+@Z~fw{Z~x!_{wIIv zV;}tQeDs4q^sx{8B-Z0_sjyJ`oQp(f4#MDWv} z{p!zs;f){u*oXhv_kH6J{5Hr8|Fy5Y^Vfd$)t~?3o49gy!XS(w2*CghL6;Xo(vmAI zNSw}rUFYLS3O0GYRK|cz41|FM!0D;*h`R<*P|NBCKmFGD^Z(!r0Qemrxc$x#J$&Z} zAHM5D4_|Mo{l&M&&wu`{pa1-u|KM|PeCG8xH=*}E+-}>7(ONkX5uSDpZ!~=qZM)h& z^dm1h1L&*wY1M`kK-R#~5u_z@l%L;Y2OTq^#_%tF@7KL&`>Vh7h0nfz$AoW|Tds9! zEuBWSx%H{K?{k=0kX5iA0N6oof|cZ#24uuags9DpBQb4VZjB!rHz5F@?sv1h*)X@l z4AG@K4FJo7O&A|;uyqtdv$)@FA3mLeTJ}A^^MfzZD)YHF9?Nmee#13+r|O_zw)KW=Z~P~0siNH@{>RJ z**DTh)r8FAfsg_I#qapgL*xJC=Rg0>9%`+2-V4Sm3|r@i-m>7CW~cZn;o^8rFp{t^g$T9AtxFSXv?A@# zRLA;y7P{fu9AFvupBj7O(puy+y)#n>GxU(V;otp@um1R_UjN{G`XBzz5B`B~egE(K zruY71F8+2i<{WpCyQ={ykOfeL*V}tAevnpk6}5PZKyx1k%)sPReJUoL;2K6%nV}K5U1UfYq4fNUwW=v>UY23j*dSlK>e`5@13}sjeXUNRB zvJ=hC9j~d7UIcJVK~>EX*1GxWYUjyD=A7ppUF(_}R($B%hY$c^6#rPVux3uRdxWk; z&oMRQhko=Eul=9@_9y=B-|9HeH}_Fe-t;ySF!R=UYrP3Z_|&=2u`joEvgFPv(};9l zZ(X)7%o2dpd`uft)eIt-NE-LXn{Ze=Zm902HqFPdF>Q|Il2UFtE&?ICuG6lM zQyr37YGNFFv;&QVPqh=L&IrH8&M{4^v5ejk=RhmsIz_=JiY7dD-bd>RPshP$j~Q2b z69&NF|KwNy{%^hp0YCg5ANs+Me&BmP@}BSh@GH0WwX?5xk8SG<0D|j!IVhbc|Kw6z z#|vlX$~;(y?6Pmt;!G|2IOc-%7hZk)mtK4R1K;+6Km4%|{NT6#Qz)``^Pm0Pn?LjU zH-Gl?Zyq-=EJQ$K%tMmRBAgI}>DDEMl!&PAYCfj!bM9kIyX=np|t=v#T0{r-^Z-4wtZ{>OQjqlmM`MtOIZt|Ya@5ykY#;|gxuuTL4G9)JB;Urh)H6AEEt?jpiWKmnoZ>ORcIw0*i- zTz0%iHWZysu(-&k*C(3tsSvs~zBTTRvF6rxpSn-Wm~ObPfR0NVfEl~c)<~G#W2)_Q z?$eja3jyM#DnmZ^=A99QaqGNwW`+!nzfarKBpv`j|K-QO{KvoV8~@Co_+5YcKl@oB z?988kYya|dlc1^b_O4?_4eucS!RKFRqAvW<<V?@lU`M&E=_ssB45@_V|Aw}xEHR_EiHNXk%Q=b-UF>NtHPj@25B}D+9 z8fa>5O|gpQqb5F5#SU#?R3RRCVz3W;!1;H^Dh*?W^}sblK@JdvyQ&V(oeC3Y;-7Q@ zxTV%F!V_~=?5e^nACGIC3722P<+4hgN~T7rPLUCWToP-ABqUY%A!RO(nU}%tOnpjB zZILgsM<>jW7B!a4!LDxufh$n`z=l7$|Kced;Gho|S?h|Zbwoe`B4$AefkR0a5KJsg zB>eY2`__+r`V9#CXTJHpKlE)M_=6w&(Es1lXaCQB@pD^$CFQ3MsE(lFBc{7RFh!k` z*^xNV;@cPMJOPEmtwHZFra#_eOt(y|D%;ng=m1kY10&L@RJ2`l2nAKhvyU4!;kLEy zQN~+uKbk3FEK?VqBfN;@_dVWy4&Aey+M3h{k1a|wnmI}<^u{g(@>INlxhJJ^PKyLd zARmc_amdpFE{F$2PGGey=SkI3D|-?+QG?8~7@2(JVqh{HO)Ym!$wv=|9Evj{{FPt+ z;)h@9f9el>>u;e@@{Qe)mJJSrg`YjNtxIDJ_xqUpoMX79nv4#Fg+u_jA_So|lH2Xp znyA@S+lP(ZczPg$Fh6_PZoRE!ue03{Jnh6t_6 z*5!8FgvlcABXg8gQ_UCwfnkvGSiNPGv2+qfbc^9J9jC@V?zt(9z#v-R)F4`KHRi}Ub2tulv-?{K7C zLIRK~2kSc8P68nyu0QBZzB|sdpZMfge)7|=F(DCt??+zw?vK3Zcf9xZ-mSf-w`ZL{ z{l@-RKK_-rN4)<%@5zszCGxwXadIOJhf!~NYhV$s&$LUzBOo#nxXB-of19-VSW$V}VTw_9h<3p>#T9${fLBV3J+Zom9= zby5+*r|$E!UwxjbqWJ=1JtJOV8>C>l4}bIR`_M@lC=dwgI6|Bj;y{oE=dxn0o`--K zu|b63v)fC@xqRj;@4WfeBM}RcNS(lWCL@LrK5QM40ThS+OW*r-tFHh0uYUQ9&;32m z9)vhmxtvFj`(5Wyn`Y{60Ult<#gCH+v2J(IA;<&rlT@Hlb^NlZ4c>U%*%{jNN^~MOHW{-2+ z$JD7|07ys#6#veD@k{@wf9?1Dum0%w{a=6d6F1pzas$LMQu~xR1O^}gBZA)Hw$ZcR zn5Yr_o4@~e{N-Qz;$QpaFJ!m~W%G5Br`S2_4{Wp2vt-bor_^Hpl{)=CFote4Bj~IaF5E?dA4H<$c zm?~qb?xs8=5h>FciI7kLnVA_fgbwhS+g;T_n0%F#Un3Ap2F_E2J0M_~>$Dns^bGlg z@Yd$0h@j!i69yNoL5<=pJSkm7v;lJFRA4aRLc&+4w-R0s#93ypr^PUL_(;^uAjK=C zXoLI^c)kLd*+;M_g}9PJ=;$4CR*m^8CCTx)v*KtMv$i&yDOgT*P3`5gL-jxRQ=j~= zKmFRD`u*Sbhkh$Of>TnQQIMJ$-8VdmQKy}>J_i1ygX+UuO*$7EZ$xU2qh#2ZV)b?p(h8abP@gl&H zfQ7p7t+mb^7{hEg9ct#Z5=end=^>q&fy{kZyH6XM`@ACXgs zPJu|7$-4E*hQt7KpVNlgv>4`Nx_KZ0US)I(=e!cQqMjyP*||W~|DFE?yqv$Q!2@1H z!6!BuymIZ%!vq7-InNr;D3q9^Eu@C(2Z+hRiz5lnq&i?G1`!9U) z?VtYao4@vz=j#-u!+m++At=$D0FG!3C5H>hR%Q(bte~%^mL<~KjxpiZ+HLDyvKjXk z%bYKoo8?0KbWr6NL8YGoCSc~~Ad;RRU~*b!hq{ftq;eT;5CPmG0hbR8wLm!eN?9r zRbw1&E+Q~7R6Vc2z?!rPQO^;Na1$mb51bZ5V;??-=lJF@ zRbybhTbzuQK3==)wILt^q*xzSLL{bDKP{)t0lCWXowFo7c+|&G4=+h$5FTKDDX-n# zLXZhob(}KT#IX@NA`uQE8Jdan3AGDQy@U`8Z6 zD`ReIM(%K+eCk2S!E{ckiG*_6ijAvmM~Jw*G)F7~9s~Tkd8;NngcS9~Ui63g?q5gS zOTr5)JlwlvI7xn}!HQ%KR_sRq#H(+8;?=kR7Ji9{B|oT*J2Md&T5rsVih)FknJgSQ zKnZyLvABGn1!Bu29bswZ#!E$qAfeQm^$0#DRIZR8Hb+d&1hEhcfVg-7xT`8an3~}z zm@NYlhN}_8dtSM1w|3w6`hfl|U=MUPv!4JY*ji=Oe7 z$C?n6`WZoy!W9hSK=5Ds!khowzw%4}>hJlM|M3re%Qya$((Zor6JPm{fA%vU|I%9l zX-E!czHNPz%wn0l?n7-Dd3t&w!Zpy5GEN>4oYA)?&t7?Gy$50@rSHRZMnn*CYjoS> z)~OQ+I1vZXG`LTD+-IsX+{=ZG1wVqu_|WOrNhmC=94EU?g$P;Cz9V>2R<u zS(Y!%9+5)%eOH|Y1_|Wi-qu(T)_J?M8EAl>*iKf`qAI`5;K4Mhu`!2zWD>c`y1c;J>T%k@AxOBM61UqzVi6%uRZ_u*Pj3C zSKj&3+v7T&s<^b2CLTPtY#>}|33#}E?Q*Jl1X#Y*h(T$TPvr|CGjF|Zt#xK01h)XI z7numLrrfzBIqKq-G6aX7jVaiG1(>4?Z7qZn8k|=EMwuU$CVt@okkA9hbn`hiM2IkQ z&)%3j9rC#((8v34FC7l});%!L+}%OlhR)P&cBaN8%)%(dw~qhA-}8~L*F^r8fAQ5X zzcU_MK71J^N=Mu%5fU*`V0ssxQZWy6HUcg#7S-4~DfNNGB+Oe~+0Yq;t3-aj`+Y>3 zTo5S0b3&LJgM6}?>DF6sEF9s!KiX8AJ}ulU92Jj|>F@mTvzJ+3-)|c~Y|@xKaG#!0 z))o##7z9WuU}B8d9_@ek*MIrn`Xk@-zyFuN_dojS&;872-`H26QmWMvu`%76JnOA7 z|IzRI(7*n>zUj~Y#Har1FMr7l)-Ur65$h~llUr{>93Io`anHnfw3RIl;e^ovJL7HR z-WoGGU|uU@M!_x}j<)|Sr7sq81kW3dkWzu%I+V^yDdz4f1Fz!}^o1Dk zbcP*QM?9YQ#3{^+mGbG~9&+&>o+)Ed8RrHyg-UkV66_KY10%-zm?-dHEsz9GzY8IS9+X}`TBWWS0W`K5ijs z!hwm)GWTd*S6uc17k@#0Trj)9BbFi{kdTDM8z7mPdItX>GS%R%3pJ`EU^({U9DYFw zU)Hx!)36f6Kn75OIwg2pkOw z8JMUuVnfw>LRP+Q9(+xZ5Zqk>Jj?4R4}8{+bdXj(%*|s2s(aSfmWuM4y*z}2lm_zFNEqCGY5_Mgx+OKV0ZyfmrcI>f17W_aCOUbouPTn~CcKFdl6wr*`$FTb zJx~r6Le2~`4uGkNN=-65txYZD2mZFo7eX}kX?EASXGV;9Jwfp zRVL2O4<6bjJf$819z~76I8WTN!oJ2XK0SUfJ{ZUmV+xVWjK5|8HA>7)oI^0WlUb;m zIk>Y0Rf(P6gE+z3tA2H;E9yE&uMqQ#5d5_DM1+SO#~h;N9xQ{pm}`ev7MWP0v_no> z@7pPN#Ik4};+M5p&1tQNt^UC(ME#h{*Ze7)8X`mnVi2aUJo=A*>dhbj)H{s)o-W_< z!R^~W*gy7x{^4isCbUUDS`-0~)1FUzXWH{&&xgG;?Va6TdpO09S$`8|V zq|-Xoa|g)BrIz84=EnimYwP(gGcRr30{Xpw{D} z6|EZ3A_$Beh+UmLa1-i6nJiq^?W#6##UbKhp)m&wx6r9T2+U;8Cg2gm$mKB&0(d4b zS9`sy2jbS-G%MHrdJ>FDiM9p^%mTmbBd`34@BR9+W`F(*Z~e_*dsV1nUuy5Ogw%Ni zmGF(+Jk%?>84iYVJq~dIAn-Ic0U`?VCi1ZH*08Z7hFT2u`>=haAup-33*c8SW=DXQ znb3_z$V1g^A2BuEMzI16YJ2-VAAaSfz7en8=WP>Vga}pNhuudqA>fIZ3xJt=?;8An z`LR#`{%?8j|MZ8y?bpBj!5{t2ul(pIUVHnlS#*lf*5#ugeD=fN{-OWgcYo*?zx4K> z{wx3RQ(t|aH_KsR%aKj48ASvF_Bm3)sOData>wL|6KssVNn=Jt^D;i})90*!mve+0 zPfnoY(9i2ZQ~R-|Q=X@#4^_FQUvP$CZ&>lswlt zSDXyu=5Pl7(#4%~{QYj=;edz+b{Ry&rI0=g>LmlglSOrc;o?ktKNBgFVvBJ^q?8gM zY8pyhvgS(aUP~@S2XJ>Y8*YT5E#u8Io0yhE2Nb~q>Vs|98LB^dLGf^H)i9%mp%;^7 z#wrsW?a=8(Nf}d_6UpJmr8G$pno` z7A)bhL6mM-Rmg<92HK^cbj7(KBqi&F2_D%wR&zoT*IR`+4@Gp z**4x<6XGsDrtc+dOB%1IIr`T5VQXCk5!L)YeV@9U>JpsRPOP#g53+E;lBc@6-EERG zno#eu$<|ePsP1!j51>NCAW`d`5#6;$Kns#nok&f|)GETDK3NXyF-&_PV1*5=i5dbD z1t1cywf%Y`BJiy-|JJX6-W#T{`lE99(Q$Etr!9znlNuY1;pr)W*41-=4r3@Dw?R4(g?wXz0t$1 z_0|Aj4t>1uW2jjs%!4p)n`}L4Ih8rAX1m(+``Cx6M+6-gQGUvV=%KfV&P?FB3LJJH zJ~efx6*355NYNT?tvyH=qNzS~-iHpeNF5=R53DzSXqzy5_)vX5OKx2n$K1(~H>NK9 zu=U0gfMe?O$NQLSaS>lcFku(Dwf4}v5IMj;^*&~+=h^%cYf z65cpG9Ka%`g}P5mVf3mG;wa&BhG7uL1g>=1rws;=6r5t?lwom677dHvr0$q8Ko}R+ zQwjr*)#WnMoCzH3hLhJbK`@W<&N|ML9! zR{}Be6f_6R?USjhshJ+rm=z4PI!A&-*?HcZah&&u)v^rE%#E-kFddO<$4yngi;0n# z1$w32mt3dZ3ycH?=8?ijhcV_ph9($;2@!L4dF9t$2Ve`hm~$owqcDmtM8c9rf+X#^ zd03zaMfFDkVL4a_fZR|aR_UPAW*O|Hw|wGad&%YX>-YKW8F!bV{&*iFgH{9P1k+G< z4@Zvf{>Q%i>jO1T^S|^9pPoL5kkfEO1Z&F#0TkGg8uQj5RU$)mAG?`3VoocuI1&h8 zkT|3qLL(rN5N;y3)}D0|fe4)CkF>{Ovv?JOWtF1<%9S8O2xGeagwWk~onyEfn_~^+ z@*hYL;O4*c8(yO8;49C^wzVz@5yR%=?)SauQmKN>(>PrvTa$;rH5N1fu}{4AH-72! zKk(7_|Fhru;UD>dk3zr~-nyqw_4j`B`#$}~{l`A})&Jw)`t{#@?XCMz&81Gzp}cAg zw8_pGjX7t;XFyBmU{W26JZgS zV?eMr6kc(IVPSiOS5>f@{6jh&hp*)Hmt4gk@4`VnQ62&SK`pGab2jX0ia#!q#6t=UQmh;?A`K@O!=R_7qoqr%wC z%#9gYD3Alm;OOt&3}%2PPkNQt?m6xAOX&Ooon0AlsRSpYs;%p%zawOzurbr1Ms7Pc81TF-P>C>;@zw+@fB@@E~++mnMA9EkN?)>AE zSZ#L}SdA$;&B7R9YPPEmjZ}f;CIrK-1ss?2T6$|pG^g&*m03fs2 zP48pwV@~x<{ahfNK!TRSjNY0cf=#o1*u7v$=lg@`h%O9UYquti7y{BbKh;xxSkq|6 zO6L*5z^S@V-KVN~IrKBY4P(L1T4M%;p?V*4pQh?dm+FMc8b*R#X{f_8f!O_(uJHi`VureCBH9C^ZMKnoxA2I_@wjy zR?>O)OEoem*F+lrxZvxxi$g$SVnhaH0X3I22B4Xv`|*IB$-IGq=|4PJc~9e$4gx~H z?Xm3JPe&=o$MA(C+Tk)$IfWFf#IG8(HbkpouZ8ED83SG1qQ}6A%1fW$IfOg9yL-Zq zYHPT_r3zYi$DL_U;ffttqC3nCZ@UV2&l^W0tujaumO=F;8aip1pq%bLPvf| zDTWD91iSF97JBJLfuZp@e4mwM3>eFQoM*e|GP?xCU4+rxre!KEco{1R0Z#WB1VVi8 zH@rk&?TzlO&SIawSCTE_IE$=<-hxzU(I9h{WtmO zhaNuv&i-ucgfudaVAxb&#fuRjF*TM=U=v{$2OO4&6;(4V9c-F3s7c#;Z;g?{BMWP- zBuk4kmt$?ptHMk(W*zA)LOFq@^Z9qTL~*1h4a&6bYqEMwcrh+%V#neQ@u~6z-X-$B zu<{Tw7=sCzn8}%$$pw*5tXW*n)^$XtiXHIE){dtH?29rv03Zy)?MV@Zm!iM4eOc*!f~+;awQI^J}lW zDUP!=61?FKT8rRp@2XYe)`SI_5vVevsFK|CX7;41Ap$UApp@Dq=cnK&dfUM~<*;jK zl#7R?z2r0w9f1+AQvTEdx`DeRxMKt%BSdFs0znpw113-dQ3xVRSxBt4d8*4jBGe<< ze3@|N>eCGF-J}aQW&z6R86iSIX3}sE19ztAf}MlF%>2GU2* z$!CTVAm|Y4{&;`hTH9_9y)*L0lkfZ8%_gXs-5&!3!Rb(yAKm9sLazpb2sydu7%ArV zL)jWx1Q0E8zSmcX8Ing?gU2HMF+~@8WoujGhCmUHY#z_|ag>{@1grHV1ZId45jUoX zttHssU8n8)Ogu9I2n1qit|$(P5(93HU&$(%V5&df=a^xR0h~VWt>LZ1O+vUn^BOqJ zAMdsgRb5(_qRE7Z64LF~pWXV_h0rsAeV?(9Y~HH-4KT!D_Ac_!T9<}_(`;AWrSOB2_GN)pp#ll$NPA_b#?r6m1i*kWcT?Z9&n`9a0$IXYNe4i9b(Chk zKfPM@7t_<2n*%}Z0Pl`uPkmZk=0c~$WpUk(%5{xMju~*7y%CX+iC1|`WriR=@|*CI zqO_(liymzb;cGA(ri`@phOMXb6#>m7TebV}k?x_Y=V5`>Ud((rw2t{?#FJ%1-J=$f z!xl6rv_Y^V2$Qtdnsj7g%0xj}1TDx7P%f$iY0;2k>GE?e;vQgg8X) z7GRC6FQwo$eCBd8q{N6w5C{%s!ckS6M42l(6|JhpKy*|uFy9mH;87-DYU3#W-%~6I z9&w9T)-@dU;h`PD^Q1lvl1I~?{;f*-SnQ?)pSP?qt_wC{?lZ#NLkp(EL{iNR15y&! z5&%3?+Y(}RM2#2z2$9&i94GIkPok(e&xE({DIge~9ZH~|?~%*%y2 z4i|y}xQCf*#tvPCDi_yZD&fy9;uV*kzsx)koYa|>h=o9wZ|Nd=xrQ_2g7F>t3_O%O z98i%q#15XiUKe&@=hir{OAsL>Xi&}of)GZg0LUz;ISSLZ$ntpV=ueAi5y0Y1S;oqx zbMSCC@IYXbMkqo6>?Ua+)|4C9LJF4{AWywm*L{{iyZ|$VX=n~g*@qniz{9tI2M6$? zsEc#2kr30{($J^5a(YI5&oRx;mBTeQ6A^+4yWl3&TROOPs^8V8dKP)>cM(7!6--kQ z8uQj$YmJzu>OOP~ow|tcL|q{Bz#9iqc%V8Avwhe;bk6j+!kIdbC@j5kns11}J%;)| zeN0eW4+t981qr>$t+y^hK+|lWbEr;_%-UQUZr(&1Ga*j1p}yA~zd#hJ$T;TK!V&>f z?Y?XF0EnDR1HwqteNSyVgqn^SV_G_G0^_>!FTYyCxtN%_iL}Iwren=LeFf88)pQLu zBCf#xi-DV-pi$TVkdIn@KedT^VSpWQJ%%gW@REGKQ2o*s-(^4u?is~pT^qA}N8LS4>kK-ePg#)n#(7>&bW2;&x$V4S)DM{?r~^(1)IbQ3E-&5Mm@gC& zh)^>YLPK2VZFu)b^MS@UMu zbxijRbO8d43TtcJMVKT2#pL-W3@^DqVkD2q()iW>6R3$hT1WcG) zV`(Byh&WG^w1#W`Jj(0E!dQEzwPLN4j0$pb{aGGu2P9(2CgiZ*K%8cYxByu3a6v9z zckUP^a*Top>hW4d*GC}baOdX~7mc<4#V-kyX-Df!{54S5#~L-(F17{>6oBTT#nFbi z99PTYc=4-a+EoAm7zvnA7#IybEbe2BeU53W$s{2_!^7Wqxqk4P$Me*p1S{DTk0i78 zMVW_#sXO63#!HQ*i?iM4-dF-NBrDSN4OJ#2;Vk& zxV24~D|duc{rP=7?sE)PcUYL>>nbQCb-Lhf!Gwbqe%sz1N$zU$O*2SAt1cImh^XaY=iJWPw{kD2?J=Gs{3^N7eP%l5Fftuqr^ z_)xp=zK;a4VL-MXp0bnP+IzNbYmJCi{dqEE+z(hY5jCNQ-n$3_soL`~lfL99xXKAp zTI6AtSX*IBt>-C7~w(EG*F{Vx9qT#LEcg@w|-1nX|a;Vg@Qmt9*Q zB}~Q}h_HF6TRw_^;&*-1kNnWbzOFy>Klq8y{PJu2cI$w!&)wA3!+c>AmIa)M606l3 zH$fp};;a=M48WfIM2j@Ohg*ifY>gjotv3_^LU3}mc${`0x(`gBvA#2|=9 zwDs1SFtfYg_q_nzHMQWnbHOnz?e9&vRScXk3m|&9&gH&P%RPClGo)~FMVwG16ov#t zEy3?W@dW~?a}5&N8-Wm<)J%M;^;^gK;pHmz10>tz==UsK5aV>5T&m0`pL95AtA}nR zkq?z7ex?~*7IgSxMEg15i2^e%=PFSER@6fN4_CHoT<(BN8Jr>$uE;%1^@u@?ee7eZ zg831beQ538y%~*@zYB*5_-O#!DT!ORpcUySKgLX;jOy4p1R=A!N_1EaQxr%ME}gGO z5haI?c?_?wB+e9oV+7akm!4(_Rp3C~(CQKDLg-E&XmKJ#p2QTNllwyle~s7-FP9&A z61k8a+-$l>AY0%hZ_FYn98AT*Nk~TkrN3+`RDB_Hq4GF|nOV}}6By!VR94fn9AHkP z*Bg_Owll&AF*iwwFpKY&;OKMpf>3o{Z`^xt!bF_cn?*|80aHdE0Z2r^fQ=a=$iX0o zf$I+o&Z02=rNxiya!cTH2BcXUvFDhnfGB&`3qOdkH{Lq+PDG%lbK00TG(0d)j(z3m zW*fvzjc{wynh=p%?88#)Rv(?pMVDKr&dwMSp%KGupPo<=&kb$8;a&$HCWhSKbiLeAhXiQyNm-Ldy zP}|4cXNJcV+mQ%}L!zI*8TtF&brkbY5c$s*y zl&_KL8>iQTkhm1vbQx?=J!`pa!-JZ0i3Niv5Yjx|{qPT`>ImJT!nw@tAi>At{$c69 z+zdPvU?MeRVHXZLt_1`k1ZJV6{#!2k%#zM=sG-k&#nVV#c90R3{5SEVxI9F$no);{Z~eftf9s$B`qFTJ{F7h(OTYN#hggtRDNc`>tbHfmqgTqG+v+SitKJ$`oNQr2zsHS44>jBB|L_ z-RGPMRautLL)gHG!q6CdV__y1#G1y=X-93n?%p+%Bt$x9ojLY}R*4r0t62FLi`HD~ zwM(Q(Q8+2!ilXa~$?N_KG*Un&(e*BQ5_9F86&P z+IeZG8Qb3$C3MfR@Q6TwL(j?L}teCAym=s=Yz@3sD^ z68oUXrm*sgiG%e@pEDqjRhr|8u04k&7DX?a>gk|L>7+0oCf3~T9=19UGI#Zux_aE2 zM6O`gOqp4kUe);2ReyaK&d8fZV{v1$01K?ecm2}(&)z-Eyp=x8ynLKPjs%!{lg5Fy1vNg&4?QpPwbj0r?_14Am%Ce@s8(uv^ z6MlH;TW6wBok{#q$B0Y_&iNETF1tXJ7t=X)%uK{;@qC|S=rj+2oNhB>7kuclH74)~ zQ}{KwtWr(xq&@F|!BUr#_DQ zDsbURgz47%)`W=6^KUN&M{Pqf2(b}w(r#N9W^qjVX1q$MqjbzF z$0Oaee#M|3mFFR&hEiiYhObhdkoYJ!7o{$*az9}IGi}(2j*+#vTMRTJUF%UyD-Sag z^0Vz31e%+=sjY!dO~+5k-q-NvAY2F`PqHS98kjmI!VEn#2Mq&ryb5Waj4EQylUFdn zqH8?WELKp?vhSTCYgyY$>iq)4Bcms#sk%kvnUH!pf5E?biG-{)s2Z#|UV;iv4(->N z{nUJaee;DX4FxyT89~$XNy$XSERq9iRF?0gsi$hnJUo3I5CASr&mLrJ0^w>3E0pb^ zfFENcB&uvQB1UL=f3g6%f$r+QPef=;txIagq%~#+PkHk}iK%+p%>V$82y}#$yubF& zc&UZmH@;_ECSYPBHV0VsM&SS9U-{kb>q%w*>htj*|MaKdyYaS3Cb3ON3N!%Euvk1W zSl|E|xj7Yta0+W{x1P>s+F0U-Ncec)5n{>Yq)QHC0sz(F>L5BIuhU44fEgj28=yN@xasU{DS5YQ_pG7F1zNnsa$wmsao_MI=8%J-XJe(RmPPA4xWRYW*~0~k|M zT-(-KMC^0y`y6wsWmYhxkndsZx2-i1kJ#sU+_jk5r3N!ZY9d>ghcxl{n3>L7=-Vi? z4w5k4S`#KfRMq>~=hPHEL0wV>;S=WG+P1>vi7_Ci#h98<5nrL`p5&;ipOVD{8JX3D zIFHIRa;SK{2UFBbO7S#_AUyOUXNBuj_xvws9yuVd%!hTthupfX{XCp!;zdV&AfA>* z9Iqsb;}ANcDI>xT4-LF9dcNec9J7KebsJ9O%R$jTdxn*-2H-@9)Y3NN=IL8CjlirV zk8k<#hMqj`(^8%>%YLb?Ul1f7Qp7798&65!f$MTmb0h>Z6ee+EC$buVmCL8p_h6B8 zwJhdLEmK-b?pYR>h@8z6QNsfb9HEHJP8|97iz!~2t`N)ti;QD@0!qUYg=nghycUG+{*7VY|K*kuV=1`Edr#1pHOSu8zO?5 zkD6Os;nLt@tXLUu$$r^{bAmfH_Nn`xCtqY}mM}HKTjS2ih!JEtS{NDN;=at0uz+|3 zAWXL|x5kZwz^3_B9V0h=g$T}0kf~x9Z>5O>6J`AA89I_Q7$6XMC8ZS=Y8{wN zMBotVWiSf}6mCQTz{{9^e4z0ZQI_p{;eryL?(ZI_z93J(?tXKd z-`AVX`R9M?Q(t;}JbQT7lWR)=!s(i4TCe0~d>9c)igZdz5(b>64F4 zRuh>KHXdWK0~7H?NBcfbphH#A(z?<3Nh z5E###kVQ!%B3SGdvOu%Uddw9CBLPWcB*u00=I8GiJ)gVJ6fH;Ks=<$M#Nz^%MR*Dp ze5+8F z<$6L7$bpr*F%uI5a*e5xlB8bTTfj=^jHtgdtLt~s-#MC(lGf8XA!ks%~ z5TJU16VYwkZY!^w05TFSv4cZHE!iE}-FPM+VV8X5Ov))7+0Hx!53{7lx5q54&sj1-VNC9001_d$#@p8L z)_4;n@EjrU!&Ot4UdZj#!wA#EmUhM#fRAI|$Gnd@%@LUy0)hy7=dFu035WZb`+e-g z%man9H34B4vSO4A5>?gpl;aFP-{sr}&BLs^>%Xf`sI1EPV zELfXb#zp)j6j*Tn`fS*osTakT$%BZ#`p{G8v1V75NtpbRY%tIhiH28|;6*U^wB|-k zDDS%Gu%?#TGn8Hh92~h#TLqi^k9s7rffI1MyryE;oxWzxDiO>q5Tc z{jdD|^REJ!14S@Wzcv(x7wOB0`87V`IQ@b)6b_ja^}As6Q!S5IL3%h#6ahXPbx+ zAk1lxk29@)0SxFXnQYCSuuN=S_?3t4wsmGS*KhyeODwNH_vWK|1TsXr^c=&HfQ4=w zZoO?=C*b`)@B6&(sUmZKEL0Gx`6~> zq)9#V=6XXSb(_=XOs4=M$+8O~5q5^H(XCT$Bf+Qnv>57Hyr;=46#57OxbSjTu zup2H}9zN0AuV%tb0fdJ{I@ZGhK!A-X zL=v{BHXCcYlxT=F~A zgOAccS|i5Ri5S9NIJQn(XAveqGoPAjygAg=i*r&s`0|k^wVj1=>yn}|fJov;mW&5X zCJrE6mFAhrLS6EAL4}i#In^ML5Sgj3O!BP>QzF28Xg2pYr#k>7`Z@~nQ^#fJt=_Zf4BT8j9qr6-EkcU=Lkky(#cGFo z9i#vtQ|CR^9x3<09J+!qH6a8HbDc3WR={I9$pA9BgRl#CmQ9425W&O8)LnJVydKv2 z_BiJ-@(9k6KT~DbAp!<@;Ja|9wVPUWwA6AeO>2H~!;cB?g_*iQ74ek4u?oRzw&_w^ zSD{~2K2EzX5ENX$dSoIN@?}s7^Ev>*3{8naoSR4wZdBqcB5DPI3szIEERr~>uSZJ! zKcaMAwIUq&!6#gi#e}}tb{6;aIQK78>?PGMyWN4yz_aCmg}BRgg*hbIoQWF|O3sUO z8$gFq1ulm2i10u&QercoX45qnIq{D&;5~hx)~q|8%(^dV+Bp6BS5xcK7G83}@{#LZ z9=3+=W9+In0W>k?aUr(gc}IXh-4CM^Av+?_B7Ux*LI;~>9$_9Ln}~=scMniq;D>At zE*`1Fbo<&n`^zq`|JCpR$j^W7s~22M{KyY}+n@SpzV+++Lq7HT{r~e1zeFPCJubnNijC5)+&d30FgmMh+k>)Px8)!c#q$^tz$R;ESPPK^}-#ze1iH zfvMH*l3~)B^I&7fv?I=_Dw`z-20&l}A-T17+nNZO$A=%}_A-5qpMCw&ij<%8EF+3k zif0`R(Iu=7Rf|t&zMCR{u&8HBW?`z4hw*A?B`Ab=M3$PdwlXdpgFS?v#R%6gm^R$2^nFYZAk2XuUBtCI*a% z8L^u^&N-%y%9}07a~>8*L8wB06TAs_hHxB;_d&bDFv_NSp;t_Vp56MkNn?zFUFSZ1 zS64%K%1F#rSp-arvz^YhTu9PAp zV4NqJ*SWrezYZ5}E@caPd88{x90qh^jNpyigE=+LpMf) z;L^<_aGpRJN4$R4tOJADT-0%>!rUX~B`o;j1Ugc%1J?QAQ1kIvp;K`(P~k+Z;wiN> zTw7$IT2cZyfj5$8ecO5yCP&aF2mo`b`{Icb{2cNh7^S*BJR;2PxDyK+73a@9KZ zhuhYZzZ9@f`_^xK_EKN}&%AN>2mmb%AG$F2E?eiD03*(6`+$A=FtES?WOUB6z45Ix z2ilzbuzlKY5W$frR5*rpz-NtH_I2k_il%0CXl3krHBd6%oDO~zmbl6mzYPz7c z*$t(FYSlk}O>jKwyY<%{B)*Edhe)?yvVcPNF@-@0xM*Ni_%OPY8-;NQuhSq-hIG7$ zqo8ASeZd^P8`KlmU20tNXpW$oWu=Fz-1FbF1@-XUp2g}w(zk*D;p!7D2*Z(UCX84 z147NFr!r|yb9HowOWg3q@xB<(haU#i0j9e(;v_2cCb@$d74mWdR3a6FOSBK~cHl?7 z=P6pKj;L&`mHp=t8it5GCm=d76RtoG2LMMi3mqBK%Yc}uB4K1gKEj&I5Q#(xcHDV% zAqtnz-puDz2<}agz}#y06U59wsX`$lNRbUWc?z7uOEUu(pJ-*nd*4lV{NDzS-Yg*8RnImdo zJf;IwPHWtYjNB4yC@jR06C?F|`AR(?b2_-QF+usmbMzgRn&|Q5Q@niRE3SNP z%b10^=EQ8KhHPdA{>`US_h~v8wx#D8r&Uv;o7Ltz% zxMFjHF@zul5Ye3iF((Zct`3MsM9kKhMYxIQI)@72jtpd_&qgz{rT+B=<5*u??JqCN zO9;VL!*~?VPgx@G($K61J`|&0gU(}m;HFXI0NmGg=;-* zrWMAx7Es}iL|vT`1R(%5V!F-Y`}Di|v^3eC=mV@sw6%^9Zp=vN9%I@_iFPE-^>KyuMndGGtP9;bZ;ge*BF32eh`GQUMHm#ib=jIQ zky{Maed<)O1lQKE85_ygS{FgYJ?GYH<_l_^qeEdy-L{9$G9LKx3{oUqvNyVQ5+Oh| zhiOS)F|BDiBw@En%6QV7k^Yv_90WXx(mJ4TNd(EMKnk`9fFts)5vGcPE6E19hry}w?g002G?*?>$X zO~VX&ku&W*#E$rE^%yKCL4WDUV%&%AF7LfU96;u=E$us{=3$IMNJ0@~0S4_yx; z0p)QTW`WZu7pNKu&B4s>`#z_uV>zKw>RB{}0N@}rU^DFSx)ed^=XeY$FLNb;Gr&iKmKx852v1gOQ>ecW}-D%=?{Lt$=R9=IXEnDaj8 zzE7PIAQf7K&K$kb!`5082%6^i8AH*cWe1HABHWmJXT-ScquNx@6(AqRWM=5Xoq21r z^#%z0ocoG-_Z39gg|@9Vk<5I}ZO@qQo_)=d*x{2B1@SN(5>q98NW~Bl6OeFo@5S~= z_#I5OIin`#WsJua#G$6#r2(hwF^O4^VZ!ccy3)=kjkHMh>7{qzVT#b2Rfh zm7xZ}?r5qsD^zKXw229sp>sZgvOLgXR?=1V+8YD_a;n)ZBEZp3>)`TthhtXdc3@lM zdIp|R>*dy8hv_lB4hW~qVMFC7JQ!7_#=wkSb_@kW0u0WPBt#$>A!LyC*1Iqgp+St$ z;ZsA?wNh4PSPj>j6df`(WGW|T#yK7#F;4e{fzz3wuu;nSqqm z&D)KUr=4pa1fg|L$+Tk&Hbgidvvh7QTK|vSs-{J19aF;SN0&>oeilOCvuu z?)Qnow?>GlI`^UXyNwyT+RL>8%+S!hsp;0FHDO^7c&wB`bAv!8OrUzKFMyb_NkZ$B z-qeZ60abmBiU#|gAAY$n{%2pmC)WWmh_H8g=={)x87+LTJVlQnVuT1G+?s5SA6g?K zH6KGCrw%i#;=s{?7#hnf+ijDEgj4nT{k~6)$cPC82&QOEy$c|XtduQVYlc$#0WZKR1l`GErG*6yG5xmM80RqvByA-ih zYjDYWO#WO@V$vWZF&Bz8jKL9^C;}LcczU;l!$gi1pAes}GlMvD_^>*UbH^8WnPEK9 zFyoUE$gzb7MrzC%m=uw@bn`yvK1%o>$Ds3TIQ5T-JY-m6BmpRlDAZicn@E?~ItkO( zIKnk7^qg#7zQKw=iBou4e_1lcnMp*LG1uhodhkmMUu7B2>uQ{0?bPl}09DTl7%KZM z*ufnjq!9}jDx0WCZwGQb;=qvSGA5rXHw7hU4}`3E+}xPh!2%2m$q>wN>w<`LSat;w z5l(SNot~2`L<;aKHKr4i5MhwJ&FTBDQ_X{-Y>3&gIRqm*XI6Z}F0?T-k(p2Peax(%x@?UymoXrw`&~2gK&M&_mQTBJ=2k*) z@zD99^_CHdX7^N4tEs!Mc9$51U~7E4Nn`d1(-_mou&H5|9#a(Nt?{kP#==0RwyWJo z8t=-$2k64kJ70+1zE2w()6s*nO{JP`Fko2duze=|2Z*quu$XXbv^Czi3s;%G&$yQw z&k#_Ens95oap4| zq9ARAOe{iKP%W3^zg!4RR*XvlBjk9kH}c2wLPi-DEa>YHcG6`GCO5?U7gO6+zd5Q1U|%) zcv7}by!Bx-QgZ-hKv`hnJVI*r%@+gFdNe=r<+uOn_k09?TmJZ)zxI{C_zSNh(gGy< zT4W*>4#wB!9OFtJgTgyHLu=w5YV$;%1)9aaPlRCVL}-qtkTJu797_kEyBs8Bq+GH! z&Xhr%X1m%{O9RrOsXDK9;U?T0G1q_(fB|r-t3@UfnECg7Q#~nvpj_5!^cUj*OuWGyl3_V`_vGlL&4rD1ne5A_*L-Hp;#1nIiCyD6fMk zh=>_x%33qu8!5S_$#M{e%sFNTAy5jnu;BE!92^x~`BZ_J%g{JasY5V#dQF5-@_DsD z_W&>qTgP!4FoDSzDT$?V>n)FqLbqm8c{w`WBMi}ja$n`E&X0o%TOuZ`T6~>R*W>N- zUl5)^+;dZ@b&J!Hnp9%)JkTW5ONa#-2?N0(tinqyv#FPv1}!`Yg51e6)g+|?aBe>@ zoRh$lXBBWxrJo$U_{1u6TKMpAOy#Xf#7(5kZ-nY@VfT5z?^7KzhYkS4CtzG^YkZsB$3~OJG_xm8rWzCb<$62#T?qvv%*-Toce6R8Qa;ue zEer8vq7iEKNr)lj2Um@g-RBkt>V&wtO9aH>^jcFdJX@`$H9zE-0pMsZ>cmJWED*L- ztKbw$K@QN6_|`Xp!KSJjNudujH#m)57424$QXUYhPUuPyoLZEMU0aiHI_d^wC~{v? zKbfKccK4w+6RdVhCR2%0K!Q}r+&cHp(gcuF8Zma&RBQk)q+QK!g@I7recIgBA2Y#m zng--!p+nO>8 z#$|RJr=PpN;xIH1QcqPhK%^2zq=6)S%Gbr?0bvmCU|=(hENBL;xlWqwg_w!Bp#Ys` za0~?WKr>Xsa^rYdr1*U8Vq>FO^{WTdwWd3wsmEerfr>yrM! zCtSvr-dIt>l3;QqtOJrD1c8|XIrv})9)b0~1_h?(rl#pDsw_EsL1lU57~@5r<8DJe zur;pt*U59Ke|aDe?h5|;tMB~wtgV0IQ?LJff9E&e`<`b>(2A9m;OU54($~N%{ce#_ z!dDfQCVPUz@4V0m`-;mtsi)JXfH9B{*Al7Gb}B(wGVPwoPugE==yObKm!=>K5Py8$Ep51Q1VWk2x9u zcSnL8!z0F7UNg7TphASynHy8|7Vhe?8w@p_V3x&;BT6fI874y}LBd1{H4+b1V zh2xq;Jm>EhDM>uFq8|gMb99-@#6-79S+DZUbaS(?2rAcIS*iSm+WIW-UZ5i$>d9nm zQ+QZJbmQKIh=qx6Cd&G z%MlQjydFkj0Uiqg{O+uW`o4|@ZDib|CwEMZL6{pNmCLr$G`LU!2tvd{9H@jEq(+lM z&9$PbqXc;nRh}35I3^miMR0x~kKyE@^;=YINl0hL;R58TRA>=Dxn_86BkT7HH^Hrk zFa3Ldld>vwg(#eGJt=K#q!)PNN%vhflRRZRGoU z%BPm*w@#QN(Y#vTIIsrc;f7%8gD$CMZh| zJtZC%Q_cdpR5BzXFh+B-#n&%UcHvEr(!a9Fa$$HqT%uqVmqeS2ExHU(V4b+Dm8P;z zQo#TK2|>_n2z8wLImix2bOJ|Awo+iaPh6Nl5zXkRNRLAs5yRb#T-DR%0_2$#bBbW_ z#WDBQf_P4`>mQ5Yn?s-6%-4paG>e@6X7nR3BK_Mr<}p??C027a&_K%`E&RAo;;Dr1 zOzMtEFcFC@A`AQ4h+H1M`u9(K<+o#b{Yzhb`+xnH|6y-3Rg?A8kkvK9)eM19n2;Br zAaC;Ovd#@5X6)aI^gO%}S&>ck`_vA|D45tL^tIv_Id6v@yY1_1RKnO5Oe^##Bg zJ&Gh$+PSSu6D9=rq8-)HiUI+GAdsuW z=p(n@HW6X+(4lr8cHhCYK4#1ry)2Cd158a-$DDgI7qgzmij#|rljiD~I9S%Wcxzcl zAqG*fg|Z8qq#&@;53nTJ0GJ3GO6FM{I4UT~7Ouz$cPzqohELWHUn1vo%8;LvBOjg_q~L-F z&*{`Y7MV%C;?K+?C#(P=OudOT79qi_!##S(agjb&c ztGECJuHb-cxp+0~;7V^wK)_0<1dMXxGXauE;;3^Pv?w#n`+dQOKlzt9mat2}VCMJQ zk9+STA}H(<;Wqt+$m2EkgbQsZ6&jT;W**=$iM(&vK?2CLP%h4lo4WY|cofN15j?*+ zI@SM}{TMhwa2-`~Y%;%=F#kyUr5X@UH9+dy1}tW(=2MN3Mk64pm_By`5CTSO04D66 z)MNTo)u9=w>K?w%XewZe`k;vj&sq;;ovx;lZ+v<3pb2dvt&uPS#?U#{?t7$L!W}$6 z&m_2XqHcy5BGek)p7kaqINg2d+-J-Is$m8hPuK+>Htdb6`XO|S`;7ZY#n*+gjVWLvccC*=JD?lL+0mo&j1J zQaz{N)rPsFAbG|%GesAA*rYWf1T))5o{4GAN+26F;taPRV``?T0ON{FWvDothg)Z+ zFyDtg?)yFy9Th2r69N__Vu-kpM>jA9b2y}G7g1JR4%{>Z9)8^h#p&Mm0|~m^IT042Y5R*yX2M~}Ohv=V z!^`>|%Cnplk^_cPX2yz%K3e>;0XcdAdMH>jh_ao?crzwqSlSp0B9z8Rgg{ZS*gCC` z8|AQ}Tj0rgT6tl|El_@$>H>n0C=!TY*?zRricb!}iR?=mpH6o-B{Y!`31K>e7>Qzq zGXWt5c4wh5ga|$*@`oh}Fv8tZ4ejPI6sComTJAp0r~KlRne|K)%34j+ zBcz$|@NkAl@4X@>;>keeTXh6Mc( za&P=>`HFbL&BAjUdpD#yUVz=u1{`pO$~m50KJ*bs%;2>}GJ1kD;oXNe1b?2#x3k;_dHA*a*< z5Ae_$@EnVzr^A*EoMf_$n#@DFT9zCn)S4zu+jI`h3kk9!&<>3momWx#WqW&(Wh~GD z5&%U765z!^bNS=qj1@2~PaA*;fe15*pfGZoFA)JHrwkJ0WTZq&w5IIo*L1KTFJn^{ z*`;ao2)7rLXD;al*J!j8SAKEtJq^huY)vdcEkMJ;GXC$W;QILU<7xegr#u`F10OkH zxQEv5o3yVc><(@d%;8ErID{Ga1PYtajj9m^k^}mwlgXVMt-UCc)WW8K4Ut|z%Jg82 z@)gF}oJ8s4?P)&n30}Ae2Gd@UzfaW&DLPOZN!-kA2a_2N!GP%v&-$RCJ6S6Rh~p$A z!hsOs)3OfA3zz~dp_|PdT6urjDI+t8Lzp^q7w(c=1~4lL+L&N=*kg{l^%2y^%+Q$L)HTgi zYm&M$PUAw@xf%#BpPH%z?TCbd(^UrLRdE2%l;!Ms(3+`Rb8jMz8zm@~sqiBit&~Xh z+&#q$AEx3GWO;3F?i35XZxQNfE2S3>MQ4Pa@__t=9#V?RH3hH*1EWiX;UOi%gHTeD z43(hevFs+4dP0MPbItn zrmAP#BUF_M2pQHZTww$G8eE>P|LQkh`!_!Lt-nob>o31E{wIIoXQ0ePKIUYG+s3&w z3W%v@p}=gw$$~gG*=caZ%Ln|dw&BJ1tDbvRLh^p@s)^H;NYbcpB1GWeW@G9!Qw#US zj97na?jeO)NVdke&cb96Gb3z=?-R`es1hP_19oft-7h%~{R?mHTjPh0TSH+0@S$;^ zejh$G?_^}+t;@r<_52mZ3GsWHHZ!FJK!`eFE>W3CJu+utsKp8_VMHlxn5Q?ZG;$?{ z#U^~)+HGr%In33HrM=HEUxg)B$T2ZSgqFo$OE{P}M|=%X|5KmGyO4E&n6c#-E`*m6 zIS7ovm0TsJK(oY}IZ+fF^(-~xlJkv6eOp8o2zOivP!}YEmGcg0=fio`h}%^LovIfw z0<~Pjt!-qnQGvr~1>Gj~`Z9|EK*tEeiyq7({aHD@6qf z0&J=yC}BDjxi?Uq)z}@Ls$-XaEY#LJm2f%)fsP8KGwZK{9IibrOXEb`u920SwlEI> z^T^bt(iCAJI@YC35uhIG@uK$&k1dbminv($3vQ-UhX^yVv#6S@KOaK_ns7@7J0Ycu zRV@OESaK@IOc)66Mqc^Zp6I}WJw}F~D-luSh5^$7q6uzIZoM~Vh)_>L=iD=kJ1EZb zpdM(C01HBDTid#9n}`5{>C`>R))P#@9TBrgrEP=Wur*A$r)06pVt2UD*flhVyZNc3NNV0g@3-DJ?h)`f#(mEFOrK);>ROX$w{7dK zF(HIHsLuO+4D~z;!i_MPp?7(9>s^?T%;LW5K5SU1VHR+VD2z=A0*CGDo}|LsYPb{8 zLzi1`tubH>8~fCKk2%%Mn4&dkLQPuI05fqmBY{%WfPvuW<%dszYsJqy_CM90qz|zs znor8>!#j2^mLoz{UB^^m>7tE9h$Kl->-2HUd2)))&QQZ63pNlKF*uiZP|B1VoF$_- z53giZyP9FR9f#c2FPTQKCp^u|TFs9P-Xnr+3IEq;YpGJoW9S}=p4r(5ftve6E%|e% z!6c^hRO%g05af)CMUTUHvPgBu`1Xh-tOVCXMd*<-?6__?ujBF5J11jn!3UU}0KzO) zQJ4el^gz2?r0He_-8%oMC)Tpb_U>?&(Vo-`uaw<=kq3K+8WLj1Y%Z`~v zX=;v!5dz>RKmGc@@dtiel-JLP{i#3q6GsCX5L1;0%#A^EyPlA5S9fM+1Yz#2372*V zzz#-m)Mf3K$!EFDL8RK1SMOt-gKdZ~LKl|aTEBG`O2^h5KGiEPaJ9@UixCij8C&D6 z@k5g?Oo-;btL>V38lgrJdB0~AB0qHQjlbh1Gh5!+?UjdZ6JQR97-r9RyYDe3cpwop zq1)CTdTY#tU=h>x`55v-)hD~)dB!pI~!cLTWExYHrA85$wigNX|n&8sE;h_S&MhKo# zvqjE8ia69f=P8)KGcmBa86yjt+5{VW6J!xVhE&Q`hPo~EHLagH;)-J+o!xvA=;BDE zI-@({(8rYzEA@D5no@gAIQ;Lz}sF7(0LMPfEkqCo3w0$Yf}x8b#$Q)VdaHq)t>X|yorc7D3itz7!idS#R-)H z71YYx3E?L(DgEbF!LEss1YcQh_EriB2ZmG+V%DO(JDl}IHrb(NF6bzqG<+~1aZM{IFUO9UNN5q zX8>L)Pp%222SyBUnX$B-g^czn$9_N-lxx*EF)mVCb(^K>%4Vj$xNPbwJ{2!q>*668kZg(20{?x zL{@Jtoh7Ok_YwCI876WJYMG!lzBRt}-bg%RT28oaPB5>xs4;C_9=f!~0dD3)bsv>1 z8^{UBB7)wteQiu+YIE8?bWFFv2!e>TJThm4=^C#2Nl4BQq6lrmTj$1vsYF-3&p9T9 zN22I%jhhHFnL~J}dM1rs;6qWvy@Q(+r;YG%DqpasPgx!-xC;+SZCs&7Sl0DSw^CR2 zCe}q-M3W;h^zgSFs;%px6lcu%nO}>*MMBEinCFBW!ZLV8Q3=i6D-9qsxU&SW)b%S> z^D@S}5Vp_c$V_9wwHd4FHR6-_#5@2iJjpVq{g4#nb#ii~qpXBF$o>q^0vk^II}refr}_1kc9gDlb2tun15yRUB-HAf>`CNB??T=sI{iR36uD zHxe>1BN7mkF}r%C1+TWL<;aW|l>Ol0<$T4fj(@#TR33zYpZ(mMUw&tN!z=xtdU^fd z|M&miA-BNFPH=M@s)$S}g|?|0HYZNyy23n!g^AA;QB1y=xoL5O;N&V^wprOjsu?q8 z)Z)53O#?@!ai)M?NiMh zh*SCqSm@+M4m$VSYv}O8&>9c_*o*VX%WCsrA|>h!A<)qrjKYk>!39K^>7aAIAVoRD zTQA9u1b`B5aPawL$TuD6C>2V8@yLoYL@1K3jdCE5tp0@WQf5Q(U@hDi4@!woK1{;IG2 zQlDV?F42V$kTHOO4K;1|=8on_`mS%Bhy)X>9}ut@1pT3ZiUXn5Sxly2l(E>Iy+Ig`#iLyYwbP>>dPOZ4!wGh!}fVo(Y+| zv`V3r%LU=BaU&w>gh3T+GKcy;RL$)OJt~*exvx$E0yExvyY;@^I%4c&A9KvPPm5{n zL1_xyn71z5#=QwQMg()4b3X2)o-a$FWGa6!qd5XRxxY7oXE)j!5n+ViciX4kM+}XH z0P|Gvi14#p@vYtczR!K?Fjo(UoK8k-^vbQ>HfbFLO;zu6-t)@<48*VuUx(J@+3j}Q znh?18n0ha;U>=3J5{>MmhRw91BXOo;K$tMU^03`{Ye-@Cxa+QZ&tzvnX6)QH;fE$f zs1~X^($=}o`1JWzeL^Q&^Wq-7k~;80o{T5k<8zL9Fx(c@zCLKY_GWHAr`a%VA{|=K zJDO91O>0nooRhJ{PX`n7V$M!MEd<2`@IyTw1Rfm1?%e`BMK$JXre+!zpdOKvWh}`@ znQ_jWr7XLi$T+H^@*KZ#2{Vv6Q5S_PHIEr+770gjGc`cZ65Ef#D_m&Th^NKpi2Ht8 zULO#pm1lGWLmZgZfP7w0KvTU;0W^t0I!Z(00JIX9r#~2cnvJ1zCO0j-%-L(Wb!a8i zfS8)~jD`dy0+DD!UAmAoY7Iq#cEBB`x?g!wIns(s>COj{j$70$1}Y&!gg4M)Vs9-2QQJqWyp>rU%XU`spFpWfW-_>;5P;)bKKibFah%i4XUAVVaLm~pe z*wr2%XYws9+=?E&xOkg9+-F2N-e9cUOI{)>YN&-ANi7*bs za|#;N!_{@VOwf+KVOzoOL7zs+V~_N6A6WJW7ve+@*Jf^ zQQb9Sy4U(oEjR8FXdpRnTN{MH!%AGdMs6Y@97ssqh>;LO5PU)y+9kR+-mQ_wmG)&3 zl#!}{;n78e82}lHgd<~&A*`t9sdcG|g?pHTIiwowAX>!v)|A@uf{wxfREq!uL=kDs zEI_Jg81N#PPz3z|{fi|coULlO~5`d@45i;TRmmyMa30KnX* z*)&)8h~rU&Cl&S?^cfh3im=RK7=VP0+1aN$tdaiG^=A&S1hPX!I3Gn#ZdfkYGn0h>+H$H(HLMrXL@NJVOh+wKZ#@NT0suma^DIM>OnM2ze zvtR(I#hAViyWe%@^9koWbv#>)C=6TUTjQ+}6DA??ao9d>%veodq?oKV+Pb8dCBmoa zuKTX4UT_@(lKf(})*1qYsqS;8%POgOSO+NNpO%-K>l|}tabxbj8gn8ds_}qYcoZ}O z!c%g$b>6y&psP)@`<(kUbpXUB*o8Z{{D63b+8EOdI(r)UBd%uROI+gD$W*;n+mUmF zbr79($8kRSg|&HDGIE~?7OoK*VGtVLC5?80EA~8mp>tMseOaxoXRtE75YY)ntyxx> z45V^FD8YFauI!7KhcnfFt85DhLhuW-`SjOZ0!uE4ixm%k8E+6k&|&^W=MY9SGsOk^ zHa956|HWEROTjhG#jEKYUX;M5q&h)Ual)Et@%FD))Z=vuByNnGv@SVfhUN7@3moqI zw0&eRS{X&L+(a+r=0co(Ox+-q!8#mHA=n5Bp$X?*M`rW1F4c@GLo;8v&?*2+V_)WY zc3=TUBtI~@aY2X$!Sdn(SZ0K!9>td}b-BJ%=SoN%z(h){>I5_$%{4qhns^!0y--tM zE{XsE_#40S>OaNu`fvQotN-r*_gC-<9_OI3LIlj|j1C(ybMGlsG`FBk2}+S6u5r`i z0jEEtGWnha+dUco1jMvK1~=-=oIK?*=hOmz__>Zuw=58%Fcr{gjk?`K&BnAu@_P{+ zYJ)CJTjz%^h34@1miKKh`-Oe|KFn=u>_hk6r$s6vGXx_U*g)X!(~<#VGpEHXIYfkd zlir1xaxHuxbDuLq1u+hRLb%X`bRN<8WG#8+AjG>$Z2wx7^`+7|(*R?6AHl#4%mUT1Ip*PhQW#+A z9KeItlsNK%%);j|%dM}wFeDYv0*2;TdeS`MD&X9Ab&*uhNPEP8cYB~xhn|VNh2{BC5jX4V$u#AJBlb06j`*yi%1rE z&R*X;$0dz1=X~e4);`6ml1kpSxp`{WK5KvLz05i07|-*2P+Qj19ytEx>9CwSI$;$Y zjawDK)#%IYKIR}LMTixlIJJ#Q0Ld)$OZ6M0Pv~J8R~Q?Zxv8GVOJl}SBz5YJfqcv7 zuGna1QW<&*glGdycVd`Hh%J@A+sBwX!F5&u-p5CY9DE3uNeDC><*` zO_0QyVIC1ZBIQLU#xQTd4Q(*Wh5Ked&&1lSk+Lg2315Bg(ro_ zgsy?pg_HmVsE!KV;_6vkMS5)wWi3-L|7~P6$$Wh9IC8*st^l{sBlXn zqmCIjIz3%a6GNKD+;Sga!Rlu;6wHG<>hF?OyTy` zS6^S<`e53CAcMXqIF-+arh~@c%^ux9J^FQ-^^7u`@eG{ISb#rOK9ygNcBnA3XFR$; zy1iZ1Ou$k&v&hl(^*pM!j_|kZ?a}*)unCk-uGLp-B0MPP59@D_t5;Ynh{8c&K~s5s zY3HdbltiVT4CTOHS)yCWT3pFTZ#_k0uN=e|ua-@$)1CH{~y0 z_-v}&K$^te?5*dcyLsjoq_d(DDQC^S3bp&GeiB%1Yw|NOm1b1+dn`|VW+@fL4I9p^ z23a@1!~mL!0FgOBH@QpJCd*?5Cf(yLj85hdIu}fXqHzAq36L@=Q>qYgQ1e8OsAg^D zjRlBrfy{cvFh9@I$9r4^>20>wPnd&4=M-v>4D7~amSMR<12ohpI2s3LbV;`vH zlQ`vWvFw{)#rpz#VnE))I7PIO&^g}G%j4~eL6`|97D7fi4FCv; zL3e)f`m1i?L?*c-Bi*jf-b9;ds=XiEE;hU^qb)IY-f|w6Pm@hU-id&Vc#tN;L)!hpB{GgFb9zg z;N8iGLK$ER%5MGZx3|l@s^#UnQI*&8csX08q<#H%Jub6ukqPB$Rx+U405Y%P$<#P; z3gQs`@;F;lB6^s8{dl{&yN}6%ioCwGvq_T_rev7g+f@V7%o>pmd(X&JDs}G77JFK~ zC_kTxOYi>dU5n4@D}k4_s{Tv{Z%u7E<32u?YBE>vb=_RYq#iL#GT&Sk1sC(~rS)f<= zBrBHb}XPhL^GMJd~J%+jR$P zj>k<{RhqD%wmx$As;w_eb1s79@{zrlQe{5Z-Gejbl-)Wd3qzU;n80nsvnP*Q4!x&m zbiaV4c2p6zP-k}?^+Z)%qZ#rbH?p2rk2=3sh~xITMrk&ZsD+jJXmT{LWQ1SdKV4Vv z79%J~Rpe-LG&z(Hg$kI-GtA@dinnfWmzzZfhtK<%q)Di$uI%R?e}*1W!^nK zXU#OAaySni{=>qnMN*~|fGv6cF@8BkML?MWPas(!#HBbNmxzI;{CY}JJ}d_9zYs#0 zW7MK}W>R{QJw`+XgS6?<+Dkix#oexJs7f*cmd1KEc|EwP01|;IYBcj$82ssP`Jl1G zy{fL=8t+tT?-=z@VCucs`F^(Zl)@SrH6`?Pt~AfW>{aKB7kB%Kn*2c}co*FO5BFYh zMWuk1V+Bbr0kkqJSc4T%pabsiIfHdplVySArV`gPkXs(fYTsg&*Cjqi1}Lc_&Q!+j zQ?S9TdUW@Q=pLed-2CPZsJ}EO*#kG#4o?vR*a)F~M;@?14v|)&YqQrH(p*Pqz&$d2-nADKn~<5FMV?4-u+<$L zsWqd#E@M3-wIDR58;%lw^loMpal6-6n ztKVuS&n&9n??*l@;y2FWBZCBslAHrul#%HU9t8P% z|6};f)|j;F#fn-*5|N%wukL-CBT8>glo&_vsysDU?#YZhCv!Z!AkG!(F2%DEd2IkH zfhr4-H(Fz3`c?}mk2A^h+;%4}=YV9?x+-H=29*bWwy0~RA|9R56=yn%VamjaDdh-% z^!|8UZ;QsI49MHFNQC9eD(HKQyed^+KzT8j|ixY^bzWf zyQIj9Q5*S{Sh-t0)31xIv+^DkgMtN54Yi{BNunZ!-c{8j_#r8mU!dNp6Gs-qyav}k zoT<=F9f>47dKVUmigL_w*D3eQ!BWWQ4u~)`6V$zXSgmcxam+*A2os_$&Uml|lA6ft zc{UZ{YC6I#9>tfi>`tDN#6&(}u(+XEc&d)(GR>ulva?!~y{nI7D8W9wJIWq-RiO(Bina&8Cnb zAL|(>Rr1!ZeckG?=gHef$tI8zIG6%So&}`_d@XJ~76BF!6+RdYoQ!sAv{tU(Udn6_ zD?l9GK1c9y+s72z-G%MS;*M}600UhhW)l__X(}r6(i$}V3^RL}l|w1WX_UKT7K+`r zmrq8cVinKTu_lfqh_CvL)h6WIoWH?Y!A!&m%HdfHf`vb>>+P~`KGgcia9e=qxp>6W zUVEbiRwXVVNdl!?x`$acQNY^x#V#{o%QSBWUYe&qo~!=C@^U-<6-=)du={EI29|H&Ube(rDo>d*g!-`f13Q^b`#q9H(oB$FJ5x3`PQ z1l$w{D0$KBDPtYmkQAkvEG_!L6Wp_?(O?=YMJ*i5b5KqpPNX9#Aw?i5AMhdk()6Xt zsS1W?MRVKL{c@`TatuY-kOdx%k3-J0HDylN0Ml_kuS z%A}x%6!I#FFY|7ZSk~K`r=n)6B9v~uU)>^t2@2)PQvpZ{C*U@W!zJxvK15!WzdV>3 z7fq!=w_?Hy;#9nzayAkTW+Gw_db{HB2)EJosT)<1C$v_T%qcsjspwnHuzbQPy_ZSs z@(+ACYCa9dmzD@ai8{*-_gv7j(YwC8azhrEJOdC(Uu&G)t*@Z7&Ed1F$ujpi!fK58 zFkcHQ@kRnFga)Yy28AR-ScBc&!*@H+ES_>#ORg@)t<0k*U*w8MnmM_vy}K?@Em@<| z13{S)9Gn(ermL%)CzsYS??k8iR7vRzAiA@_w<;UMTbA$~VJj&;!OBd4sA^`!ggg+Q zdR;ZvUl_#^4v-!}LC65lv(2~wQh!{Cl&J|<|HyWSA%;H&4wf-^O-OAd=D4D%en0tjBTK8^oIYl^82T2JDV(5iv%U6100j_VP zAp8_ECAAH>k1EuqYyL*?ac%3|FILM@|u zaU=QcltU^%D=hr2_qWHRm%(g^bTJpY34>l)BxBA)xcNk5|HLfuavDPy=6~v>F~D#G z(2*Z{*7@1_GwfNcOvOim$eqKD*a++Sm&*rF4V4w2sI98lz5-FA1Z9l z-MyPzkgwOZu*rlw?FM&3)~c|VA2gQB9VwFTc{DnxLMtm9m&Mtl`*1W>;L>_|wkhL1 zy1c{ncBc&PC%oDv#4~+n*M&uTZnChT^q?Nz-Fmg|hd^ziMe$_y$#3p?9BEvO!EK})}xtphrn4m2LXjCv*u40-oZ5W!Y zEVxq=Q77}k?}Zn_@I95$GJfiZFyJgJKLSMTv`cNbVp=lOXEd zoKoi4O_&eiCafZrO;Lnwcc;pIYgooL3w+I_DoA$K`EcBuJ;Oi%(|wZWadCEod0^Z?uBLJ*Xir)aYVGvkINpH z+hWhQz} z4psZz$c{|Q=uv*tarmEk6{VaWov1Mqt)PY`{N=%U>CMHbM{}popmS7N#{T z8_6vr>-&$S zvetn;eqC-6Qv?E}BsEk7ssy2j_iolLM*MdvsGnrH1xU-1a0?M#S&)jH_N#CPUYZ_F zkH#tt=7{|I?W&k=3yRcoLTFJ$R9}=;P(lh1bc@H;9}j!G+=3Fi)WdZU0GBz}8}rLq z0%{ddm=#NcGEe0fXw8&1tnRR?jO+!*9BWMvT0v<%lftknJ|B?wEfMrxF-e&|ToqQQ zdBsXto#kP9k%cEgsirp#sBHBJ1W6o?q`EB3ZTRt#NOQV+Jg)2U=+|W)Sq6oQBFYhy zS6we+puj#NCC-8HiukUZI@D~Lw|@OO`HDsmY2tVnL-@@rQmN@njunJQ zQpEi0CK)y|eIR+Vg31wz`rRh-6X$uVASu(XieDW=H>TutnV2ij&m3>I^&_9m+fN)UujA(P4T;-Ax#ar0)+8juk-;Ek98H-elOC77 zU6p>G;Zfn)2}vR%2L&Op-0_UcEV4lMFb`%@)#KpS7~JHdw}-n0A*$;DLQL2@-D#dn z4vL@q+rRq7A^*m|_ESH;vhU~qym4wbKwkKQeek*^2Nniq=-YbL*0 zT;1Lt{i>Xqb^M;sk_x|`{BqzFs1i84#n*3-PnT7f@u&XOmp|?^%=aGt6JNaK&H|ge zu`miEBgjMYhH&6^IgKg<$b<^cvHF2HhZC$^$r;`zR3$_#P z=HcePWn3-xM(znU2~>IozSWtmL)Zd#8;D^XOFks(7x4j~aar$eZNEGQyNLNvu` zWB1B;9r$`e$6{Ed&2$i)T-YV6oFJ_vcLu9O6k4qE)*;nOnu&jyDG1|a!J8`3Gkqut zY0eHZr-g8q*HM3HrV>zQ=1U6>w~P`Pc`jjfXTHO*z>Dqc|BY$$j=adK6YL= ze-sU3;us!`L-K4>kEe0=C&xir2pAexh7f}^hvp8g)jTY`9NZqOGda?(Z+1{J<;-y; zVG(J~`@;PP^wX;MpD1;jcWE|Nx z2Q4t^xwM5w(Dpbi`Z*0(q%?*qt4dOR7dhi=R%XohT-Fya+1?N+qM)?t>5)K2I&&9M zW{9$yWNfbW_jejIr2W>a|4;wcFTXbaZ~cWo{m;GN`-NZs{r~0P`?de~Z+!1Fg4ew7 z#{Kdnwke|&#ntM~yT$nF>7E&*c{-qcYk*3i z(Vd@X-;6QEOHNNqi?FL#RB}6-wlNJr@78-KGLcwq4Pwfy`Iv`OCIBVkiOUbFQ_bA4 zXG~AbZE~Urh8}Nb+7SoAGrRkwU)~<-ZBLzJqjdFU#W` z={=%?g@}+WG-sH3Q~t2I;_nKcH=1D(l9`05X*Q*%^+U#JzkvHeGqchuX*B+c>+s1! zDs&EFPFy{%?!|J?E&gWXtMA^h4|sCmJx+2fRfbzC(>)l5=aM3K|5Qz9GM|_)^ub2< znWhuo=~rl}C92zz(1GU!6bA*w7y+3o7RUxSBTvt588T1ZDeb*{V=9(g$~%ig`4U1pw{HQVN5 z=m%vor2tK|DWByH)nqdE@~x(*km^d9j{49ex|E`1@p&I&Zw;EW}8NdF&khO(UKC)AV5yyVTcA&i~3lnK@W;xCJ`m17x6tZGk|2KCKw_a zWn~d+M7(0C%x3i#F=_EwdA0xv4}9SALo<;O3DdzKppNoo-|1G+w{bdL{A$|CA7iq$ z`CTaQ;E|gHbr;?v*wo=yB6Q5 zFU9~)&E$tU{T%TcGEm%Wl}msqpd+RERv36K(iDIm2eI_tYhFmh{B(QPmvMhDwEo9G|4W(l-~Lzs zxmDP2-T(T(|LgzTU;OotXkvV@pFIQmwspSjB85V8@6$yw54OB0;1kAolh|p-KF`$n zH7=K$)dkq12C=EaFQ*;{B9)Lj4I2^YUImh`HeF6B6#!K^w0&_LXA>33kat!8uu=?1 z0MNhm<1ep&@@43x+70UlUqwngLLNo_+KJvsMN)LH(Za!!)elyd}4 zL|X4fOV~IkPnUj*nRA1}ZXTN|+?j;sXneLj720?-tqa=iG062vGjgV!MMF%cOd_d7 z)xc}z;9vq0J-gk)qdPeU_(rhJhIaa(CA~j*<)-=8RbCl&JNA9wis+kg@IUAe?7>9t z0XWb#M417Wfe;#hFEdyQ=}ILpt{H{8Auiu)z;e4ny`?k znLoNeF7MqvD(nL+s8p&$(X>oyloXdow|p~PJE!TnM}|jsiq2!VI1d(Kfa|dluG}>95%N5Y_)GGad-m9N^1?io)ZgVfQ>G#! zG>K{lB%vr|VvrLY%mtas`L`hh;gp_3TTCVA$hleF63g5YT8CSFRgmf(+cNYrE!!ov+wvI7bqW)fy72@6-(V*qCdn}k)hsUBJ$%f$vJPY;S- z<8A8-sC-T<0-i)^XpZd31B_ZWiWQoY0`~TyIGU54V+fPTDuDU)i#; zUmbmWT;0SxYugrvh>Frdbhtkr{kpsxRoeGZv+;uFtyyhOQ%5~PLBvxHp-I%8BFrqB z=+WAt+Vtq&->$FCBBNq4n(A>LQZ-_@+tsaGcc-KvP;!J|wjaQ={hQAKZOl{9l> zv|Jpcsu0!VJot;|hk^x3arNFUuI}Bk0%am^cHUCE7668k!ciFG>SBqC2p$4@vu>F= z>^pb=?47*-$(D(a9p!f>$i#b>(DR5KdpW&Jd7n#md}FipvxPsj49Qf7o9+=|!YWdS z#__JrBEFk>WeIifWOi3l^h1biqrX$%q(Jq{`Ro!j-euI`tS1qTjD z)(GNgI2&FM9A&vA_h5^B^m!|-RAN~p8Ws|%6krHLnHx)E7Su=#IXZ%?jvVEqFWNHQ zr0{h2Da$JG?})~`%*^bXr$U|v1%h!3gdY6V#M`kW{^ctu<)~Kdj|4O)93eT zZ2aH<;qUz2fAo7l`3L4yjZz0>5+UNQjsN1iWz7&Epcr2t-5{!QmFJfkAa13ga?+69|n- z6)APXjIvyN5D{3kwRQ+KGQg8G2u5^yyuH0WF6%yyfk6HXD6jw1-~VK`>|)6Lu;K18 zs?Vy5&pLiR);aGEuBS;Yd_qZsn5Y55yHgTVV#8>qN*#Mv5?RFMg(65I7Um{;wx&GB zLmqA(#e?rw(PZye+gwE*0+a;bO#_jXdNFhE3t$uORZ)|GWO@OHPmIoS;EL#4SGK=TF0oyL6Q=J;tw zH@n>ctH{yX(WGe?F3D6}mpv|Q+Z^FU@06yo9gQcCj29=BDsJ&OiV*7E(R3PHNN1@(-F z(_7EpD^tFPB#=p48>>Kpjq8X&Q~(GISd#nd$j&zHkhIS@>?!Kq7=~-0Lr*T zkE`2bme=Y}~bj4Y{n#+D&&^O#vj4SL~u0m@=W9Jk?OEvCdN^TZ(2+@}x*4xWcmpQAvYIVUZ8dx8C1+ z@9u?*ti|ll{D~iLdHt~Xy-$7T?IgWl<(~e>XKUZ4V9T9KJgNEnElRxakR=f@sLy^7 zJD6RCU0I%a%Jt$BppoKkhT!ssN~KDJ$j!_~qO%osw0Do3!l3Eje@ffU4{g9JO)AM^nMe%d4t?db8e(TyA__ zl|t8qmFYYcr$%P?yt-fAEe5NFWg%?02_`11FH0Zj8DZ8jZnTuJ4squZM))0UzZeV5 z$I-ND(fcxnAt2bxUM?9`DpTt)h{PSJ1b|(iWh&i3y^XmQau-5cA4-tKRJyref(}OJbNbk&@ zlZYoD?Ncqb_H(@NJv=ojp4-$LkLz=KyU%HT|LE3f=`8CGgJeL*5+$SyqDjKIW55dT zyRhz00YZ0}Gi`3Ch{}VvOs3Y(Lt9gfQ+0QHyB?Qyt7?LXp(;34UYeW=m1It$tTXz< z^3n4$cyI)#Te+%LrL~H}kNgQgy*SQ%syjF1sSji!7aqD=*&O`GC$y38-LhuKxd zYUE9J8XdMVnY9Q+H#fffJvs1m6R1}zj@b(xK~l?b=}}6-Zf@nGC}n4ihF{6ZWgcb2 z$H^*MINrZ~WeO|Hgm#-9Po^@mIg|C;#l9c>T%O_LE<Vb8{$Ukc5-i@vb3=2#^u7_%jc{F&;f0n16s4}uRgdyAYf%VA;e97P&QV#lgrKzW zPj8P$cZ(!u1*vdTZ7P5KrTtjT>)-mr2j1DI-yQpr18n$>gW{%y*8p1NW#T&e2hbr7eT4NPffE$yND<9S_%gf{PAajePCqNAEvzExTOgN30 z0wREPm3*a)E4Ox(W53mPkV^RiltKwA%%-|F!i`>_+=gwIjYnPLOGP8FRA(jRfF1XX+$=}pZ z3_0G|G(={(Mq`RmU89nHgGRHhj?kF|P&;IX zMO2QxnO(h`r%`zFd?2^B@bC%TXRX-csdxHZ?kn#-aLOO4%Y=7st3Qe}GSSLgVs_5% z-)?vqekD$Ivcb%8=Zcly17a#SB!yFjG9W3AnMyv{T$@KMmz;G~krQIN#9E{Kd*{dv z-5xB+D38dd*@P4-f|Iov(89P3=I;KXV?NROC8+j^V_MLpDz76riDf{`!2?|m0fUh> zA`_^jJDE}x**MaPBKD)}NS2-^o|r!*A%}`cQxR@57>6(mw=r_XCQr{Ll|w#}mFDs| zT;AqE-73&gnGaDYQWM!NM;YruOcVjYiMT0?@WDs%hB}#JWS-$Jq_Cl=2T?FQ!z>-l zEGmi8qy)%IhBL2v)#HQJh?U(N?q=P)xerD4st(rQoKf(NMlxXY_3SZp#4aS@9bv}d z6jUecT67tlcWZA8f(1|jsJfY@2deKkiR%h8kur-w4{j_4zoOi!x|KF-`~o!jBM8))Ud z-|0!Qz9PDAQ9(plH;N1qV^*&6+Ifz|YxGgiQ3;EA9-JcF%qf$l&~)SQpBV)mIGda! z%wE3tuy4Qq7%S}G{_6T8Ivwv0K7WKB5Z_?>e+H~QFa2>htcGTNwta>Pq=Xr@-zjse z1$~IxnSgRDW3qi`H0&B#OxqZqJwNpRTie@5;~+Z!L>U3Q#Nl!%r;17?XW)gPOyVwX zK*BBND@HTzV=aJFRho3zHQ(J3PG+PcfbuI^^G4WJ8+ z1xg_n%1r8nWR@|OjhvXIGB2jN<4UG%EX@3JX#I9%ma4^)(!(-a;Aq@LMD&zW4dz>q zUK*STN~_rA?NF7g@iLs}-6_Fxvao0-dD)S2g#iGv=r0TbF-k1n!*iO)?-DFGnZq<= zp`MjiLk(>ztVgS;>dUN*bX4S75?Z-&TSYNvj0sRvJyaA~!BysX^mz0zr%2Ahp_#Gg zODH}RleYtNJZmCVL^|Qqm6u&AP@&@x5&rtkJ!4TI?-gh(cd+Amx3q}d^Ug-CvYi$_ z4yLw3@bfPNo^;-SRApU0{Us&KnKyY4ZU!F$w1MAqP-FMre14{7o0G{!HTn=rPNVqx z@#(Vb<#=hLNGPA}e2t;qh&s0;vzpdHz=V0Hn|P%%|So9gx^a%wZnZs?xf%q$Amc2ALWuFA<& zPGA&ueF34zQG1imR@74_kj!D!!yar+{7^euYobl$DE^Ykqx0460XKJ#TSzf_5j<9o z3YDCRsH2=mggY)9nRN_eBEixM*C-PrbgB?>W;6Fk_ikaH1rUt8V^`TM^$tX2q}LeE z%;i*S(i)2%O_`6@-|Tu=zsxf-Q-=R%5Y0VDme2I9Z1HHT(zfmyiS)Cz7j1GVC|L5X zUlv7L=S=E}xa@M*hNJ1(PHjzKUF_=J%+15Pkzp#PrwF@(;fT}2duO5`(xXXJQ9YU4 zQfNj1#H21te!?6Yg#oO_g(DuyBJ@>cEBwFgHdK#x9M=Bd-};S^DV; zq5~tRk_YpU%Z#RaG!-5M!4=(8#z>}0FyZ5r<5ioomVFXQ>4m*!%4AaJCO8^jUEVIg zx?R)CwqvyDNN~(@kef=Aq1GE{XyGbpuHGMyw+uSk*;-SD zvb-DzW&ipSLj2m!mqY9D^ZRc<-tzjlzIx;`EdcN7Tccf%&*~^Xb7wwqTCs6RH+1=S zp5duj9@mv|;|{Vgm+^Q!?h&t z9yPe9Kw0;~HIJkcqoPqFn7vaLt#}5yKgVU`mTL`$o%!IjFwhq!jAO7jI+phhw4>})TxfAbk-xgMek*D zy3a#IzSkuM!&z;TTk|>e-(1%=km*DUsd!Ch=8}rh~KTfp1h+ zfAHBngK2+wsoo66e%7$#_5*BNO}<~-^Yh}v+>Zvl#*qbnwD5=k#GQn=JdOk|0T$p> zpn<%#2<*Y;?BPVvCaTK9P2`X|F$2iVa>u5bUuNB`7q7hZumcSfoBD^|-H}zwjd;hT z9qKkC7CtSAa7v6RP#h&{SHw6bV!6{YPtnCK}At2 z3pWxb^-z&6)P?N!9ZrhY9Bj@SDxdJ`!z;Y8>1GqH+$`y7AQ$tplQz$6Dk9)Rg+am$ zwySeF;Wi580v6Ui;@!uG!She@alj~P2qV;4*maq5bi$+=0ztYUUD6`ERSMdWXy3=o zRm1zt4{}Z=NUE?lhH6S}huM4hqGk@I=@e<)oUVDR)`hIX&g1IApjLtHha`t3hgXXQ z3C_sMb+DwQWJqye(3rs;Rv#g}^xQcCVmTTLqn|zWvSgL_%CpcfP|c1ZpCXwud=8uF z9Jl7|j7`0Z0FY#og3>6n)QyQfbgWUrA?Ca)OLB6ZLiki#LC^x(9Apf2^s0*6|9RBgkUD&6iJKdRbC5A z>2)GJvzuSt9+y{n6PcBxb*N-y?^Xw=f!oRWrQiPQJKuW!af(AO`q;Jq*j^6$W{nWO zpRek(jjqr8qIUB&=th)|JO`RxE)$0)z>3V<%iJKsb66$nCE`|raHX11j!U>*MlGg0iePp zQd|)c)T&suW%+@ohD`{Jh_aE5d^lh=p(v|!CQVtjH5LUcM46G49^r)*@X~Wb1fFqu zGAVOpirEu29oM1$iK`KpKNODIvX1u(d zvlN3U6Q!ATD`J`#soFC~5NTndh$$s0iTbci-2p&Y%ftZf^CSwF(`mkyRQ4;{WTMZ6 zaXb$zexM(b`wEM1kkT)SBv__FV85rEt7}Ff47c(CUlyWa2x7v+qVq)+>U)T`LB?Sb z65*EYA>I5k?-piNUQyhFL*$)czVbm9hbOl+c}0j-S6DL1C^N&tFYgbzPSsOetBOce zSXJ!mR%fIM++T*Z^^`6-Y0hr>0M!u}^L&8f;KA<9kdB&lw9(Gy25E`_Jkvb7`|tte z%-ouE=UNiOsC!1gT+EsrhaQKr@{1m#C4aDqWbGN4wdy-MJ>CyrhfqA7{0d^n$xT%n z>k%H8b<4;AaSkU-64TXtbUU=2=ZlIqIh$xT4=$_za@=Ew5gy_OcWukJ2;W-$WdLFkW{UA_3G>T&h!+1(lpsI_e~(XDxzxL64{N_3YcO+`ppg@k_{_7G zNZiA~nSwHms_|hZ{-z;S{xg)!xzP^3FBNK&u;F6_!;=UhDW8r8TT;>o?b>>Uquy=4 zk6ugJUxunFJ^+=Ybu4hTa|We(FuceIQYSBPX1cq3x=SV^D8fK-v?5^0scB9R>kscG zoBxGh`i}4mQ$pG4~UC;*k$o%(S6FTs{xhENtN$tB4u%yKBV>N z+$&i`lySo|Ewa0t(xGU|jrmkQ^*B`@8lTwSF2DM~2%xMVYXm-uL?opi$6+HZ_r{%0 z-?Jx>w&L}0G;P97IjKQ_t6!tpyh#Lh#Kd|}_S(7*kpZK~t25=PP?e^d$=1_3hae+4 zh5|Aekr~=Zg_?}mmqLY01*+YUy}}&|woX(=cFzbW5KuZ0+5zAbXEu8zNQ}JTTM#js zSUFKMJZ#`Cn{rbX2$%AM?kMAxK{JP0`NXSxF7mJ_3WAw*Ryk#(Q9mPW^6uqSX)s*& zwOe~7+~zGIjjevo8RC%Tt6|wS8F+fDaF?j}I<>Vt?%5h>2SVH>%)4gj&UJmKKhN#0 z{vpb{Y5IEC0)L)*-K>^(!FW#shHWj+&mY7m%t|uJyeglXAz;l~>vUAhXaunca}uHE z*QzR@;~0SwMbx(NZq|FhdN=n80lnu!RO0VsASkh)%x--u-4$F}s;D~ReP%oTXb0TRY6S;1;{u9vPfe`Qj1e4iAn4 zO+=b1l7;jQy>}bw9QBdzsWSGa_~t62m1EW;&^r-TUhmN~lQOIu-R-NO2w2Y`%M4Kw zW))^d9s(o_S;UM@n?fE+;t=I_91IChH}AdfSP}*2fYv1q(iFUM=83^z%@PyNsJ@YK zGwt$C`D}7FZK7JQZjy&vhGotMcnmaT&Pk&pNs0~V(-lDr%7?p{!4mI{P#Y4)p(>IU zQJ{&^4uW$pHlIl!xi=npXo9JRn!A}icXt20QMgaR_=djYf2Q5*{Mc~ajF`_tg*82y znm@d6l3+99Z|SJ?1j-l?C<1tl{yA$DF+^4rN$}V#E+kSWwxDU0PvRtaP*Mm25IIFv zkAtXtclViC{PTbRH~-8Zd-?DG*M8>5bjkner>?^9ReaA{nOzI|j>(s~H5u+i%{|(1 z{s~O$BYmKp#s(7^w*l`1Yj54sKw>+>DqMo00(0?@;q3*QureZ8IKZYc^7?ARd8;Hc zw`i1^C`EX-%?_OME(N8;=)#1TSHwe7g-J!EiIzu#$cXH+!NA?FV(w#D{qptoD%zjV zm4Rxy6iJ{o50p^GJWkW|(AMNQL>sd}E_+~R4)ufFrDr|m``V$m{?k=7J6QnY6W94QmiET#p)b6sHph4L!@aR`P;FJ+keH?(? zecSvBQ12K$Kitxt@lgUv!g2~9$8oeKN{r5&m8OUwWQB5bMVbVQn^~u{LQBNpm9I(t zd*yKwk_+H4V&|+^rch=jtWGjfB^wP-)mR`V3nE!EVV+KfwvCOaT3dK&E4c`NbBG=H zGQNU>7ZMoDsr{7F_f2CW;QQzWK6DvAXZZZae<^R0P-gB~fw!h*YvR9yqTRtu@5=E_ zY<|~{)M=Y9(xB$2n^PuOFnI$IP_jEB$lRn`H8^Urb?9*%jj7GPdIW`qeVUoDkr{y? zi8Y_N)9R;*2UI@aS!Ci#!M?yiSes3m=So&Ffsz==uR%4K$X*_aZiF9Ld>xh7y2gEL z-Z37g7~ST~J4$?m$n0AFcgLE6A)cYInSt@^vN6d_eJ?{{UhZO{5~_S0rQ}oLY|zj- zz2OPQM+Grb52^G~l8lgOo+K5u9;@q z>|g%rfAvrQ#6S1)`g;$5Idn3l)*R*L6(!BK%dOJKMl#HO9Uo4SisTr`TaOudmG>9e z?n%YVC2yI8^qi`&2wmkZ81usVvgH;DA`8EImeb}?J+xIQI&12|kse94RObdLsBF|~ zm}X=JY;})!O{N+T<92W@O@DY$T2xSxiFnIHv%=$EnhdLX$5lB-S*kZ|xqC#J`qD(1 zJJ1c?$(`IYRt?Vc4fTv13dTgf9EUK!H024Fsu3EbSAOft*2%Lq6Q zVdmDPp6#@SMY!LhY6vC=gp?%-%%p{3%LzcLF7C{0h>^iXBEp`vbKa5i(-txB`FuZU zg~km}|CHwk6RGmcp_RidQtjU0*+HBGmtvejzzVHEH8UIQ9X*{QKL{&UIh#|Ue^>uA zj3b1pOz4v(DPUdX3}T6ddzyJix>MeY%CW%I`2mxNviGERC(8%#%ZDl5zWO1*p>q1Z z?TtO#_xt}l_)KzHzLPUN_dnJM44<2X;vMqjnFgD8lmlZ&MCUDR%&i@{iE$#5EQ7sh zn!$x=7&YzKW!>Y^{px-LH1;d-6f_pkdN$k;j+y+&xCxUhK#HMH)%2WoOqTTaylv4T zKMc1e_hH+rrB@Eh&T7ud<-OTHw58yzlh8e8ocS-U^>x?h3n}E63^!8*$<~| z#eIUXfCN)THFU3;o@4>fVP3OFoabvVxCmeI3vjd8lq7yaOMIoF{#icq@6EwtHkG+ z+acyW(QC6Ts}r~h3m+r|B@*t`9T6V~{VVk3R#lFODlk#FXD5GLoq>uqKAJ?v!+h+7 z35qg~WdwPoMd*T9QDR zNrhLAUhK8YfWh-PYDXQGsCjx;LnLE(y*KzBPwv=k+?P()DLta%S(4C1n9-CH!jib? z>TVWOOqi=1OGJ8b93W@zN{>#DPLHDdBlirdV(NCwEG%Bq*|Z5ns&9@@mtB@C7Y($w zpS#q~X-OHaN>I__K7G~0lF^Psj|0pUX@Tf&6tza8e5lAkGLo4D5JDLWX}ist?j`N3 z*C#T&NA<#4gnUI0EQA3~oBq79%<~TbMPaSXco2h>QU!-{69{gH{_Ims6kMGS0A^{$ zn_)!;h^}#kw*vLaBDS)oh*8cLg(alYcSa}x3muKmLtdJyNQEO2$;bgljy6(|d0btt zYX}dfyqgJ>VYg)BPF#8~h@hQ2f&4IF$`LG&XO9{_*Uk7CAogT_#HuCsqZNKrP3aQCkt z0%aUcU(b`7$q50FCSjaj=QcUP?8}>7|4=7frl+1jQdU`l3O}ld!78Q;n7=|R@zmy- z^V1TaW>PxcQPe-+HkdNV&39v9A1cl+L5!5HBqu~r=_%a9qj;cmt6G5GB`QmwGU$5I zP=(;FhP_e#lz~b`Hj7KH)}%G5Vz*!o<1iU9RaMNwk)T8t1JUujSF{K zRT(bD_CO*dvS)f!x}QYTm)1n&i{of4Z$ytwinJ{)Z5}S>@o#$<-iJjk!|EEYh7cN;gAF3)^0`NnmYrK{3zMJ{jHWH&9P88$fXRmn6)>N5a4sP;tbpPt%SC5*W zjpG|{_~dE5sS!CvdBaP!m!JBTNC;HqJR}jGJ;I5kn&O!t=KwNg1jfu5p?TblsDWyF z={P*#fz=N1%x+d$Q8h-rM_9KSfXUZt7R80Fd8st0;!tkNM^hCBvwOT8hL8Omm$LaglV(<7R^oL?Nr*`z5S3h82R{kqJ|V`hlvHYdx)d?ngMS!JNbR00i~ zLHQ`(1;O5X&Zkb! z&N8_L=6D)+eNH8u&%BXw(|5(;lgoD))~hBd&SCUah6<}f8#m>qVvgR^+@@#|BcLMZ zp=AJJI5DS&g9cA#d0kyVO-i;CYZSKgEr0R5fAEXn{oc?0onQN_-}%;G`_8xj>UX~V z&sbZ3-Q7W7oOECc=lxYvV(i#&dsXrV=4#H4Nv$DkIY(ynCAG zWoeW);=qxZkj!~?K$JA7@!csiJiCV<%BQjjzbLCHg8|9x=1y}yIASXS^)LY{7?!7s zx4ZjnKTSeyNjNiYg)$JJ$~aU&xvfKm@|*t~E{zIc!C#|Dv^-2+!VE(7CGR^j8& zK;~tc8H2#W8RQ-v#3T_3iP?!ON)ARyY40NeYfcJm3}iU=eeFq{dy9tl7V_xvadDkW zphYTt2%13nX>h!Sf-WmeRMeJ~S|x=J0cB=%&(HYo#)HL~n$F>FYJvWEtdp*EA*b(R*RQF?sd46nt|B#Bj zA0&kU^Z2%=(tDUWK6^{?z7d$;XmY+qiMjo^br8b6SB>=Ipj-8oldy%UI$z^8q(@k! zG6e;TG-;eHEGnsyHfBsnVoF}v$Gp2-1uU4uPQKgKOvFs3LYeK$)u`raHMmb7dDnFl z>K?u`qVtC+uZfX$@*I7%JD_w4ipn()l3MIIGTk{M+^aW`e)oxSn~pG(1lPUH8I}4? zyMB#nGXiN2kEnDZ*KAqKVK60AU_A;$fl}WEuPkMYXza>%NmP+kmgJzZD$x=(S5t%{ zGr~M$=@xMiY12a`qfvsQQ+g@3_epo&O3`n67xKg4X5vPwJRssA3z&;LHKn5vyUfr+ z$wGy?MYkMUkuiah+I)IOrX?sSsI`h(05r0L>?%9ooQO>M?~Pi|r*;9r67VYm*0l>E zS@P0}8( zNEBuNt<_zawZd?i8E~Q={XsxrEb5ZZu1iI9PbqwVCfWr~2jV%lMW|KH#G%Est$s?jDgDW7T{`2f_~@euou zDnci>XU#z|>PX7!dSq|}b4GZ2OLLmz${}bj!|{7}-qZLFn>ar2N+XiO)5(EgXZJvA zMAl|k1rLvxh?6M!wi=G8*rW`oc{fs~?l4bt_$@ElSW&|?Hq5T()wFjHWXb_Ogq8Vh zOzB8}Twz8r>*YiuN&nz+9m=PqLQWMGIa*xqm)T|3uPdUwZ==%?gzm^5o@t&hOF-Hh@MjDb50wenR(_4h$ zFO8H(+%0p>Ofcfjd5B(uMl?^I5HlInnE6luo)%Y3n83b@Fp*Gd#^I)31{_IIXl83n zX_O%>$8ogQT5HS*_cwFvZk~~YtJ(x*dr~D)r?~TS5_1FJ88(B4SBm8dB=4>8=NVh zty;h{3TmJe_RywrhOFXfDco{jFQd#7yoBn^93aa`i}?C+Hu~axX-&RB0`|$XSBXbV zBcwWrVscUHgL17EJX6!#$WCAF>g6Ap?e55)0~M7S!|1jv>qfNMb_wty(d{!chg-op zcNd$H>r17DVY(WT^u! zoQ+iIU^L-4XOlxzn7%w%fj>-tSk{DebVj3s2_we_ zw$ws_p>dQ<>YiUe9+C0I%h6Q6JYN+3tG6D7xJyDt1a*Tay>&2BC=cP+Lk|@ZPNIj? zqvvJm!xvOT-@N^)6E~e>10Slso6?M2aOtE3Cxi%TW*|9^R-e{AA5?m+aLWv)1UcW5 zttB}bwJ9f^p7ho;=t_#C@xi1*!e~vk)7#~5aoI?CiRkVO<_1*LtaK_P00+#GFvoS} zC0@BYCfpGxWvL-L`V-8yA> zYODcIVla$J6NZPn%Gpu3R=}tTK}(95Lf`;Zgx^Syj{?rtS~|_aGx3WO6g)ASPg4Co z&V9IHBCibhs}+e{jka>+^~iR(@wBSGD8?pd@t&MOYk3rn*gZ@Pfc25^> zLaD>-Su-zT7R)4)TgSSDHCw86ezdpU=gfL!he>SaKJYe1!soagYdy^8WplH=JtYV1 zJcmsrf-T-P!Rrai53Rmu&EG~a!JW5@=dN|Wrv!fNn=@|J(>8Lz8k5bWq0yxny`s6I zMS30O$&h^Laj>+;No*aSV})70m|7@C8AeRZ%u?ARqw6M^we>1hBPu~|CYMZdWmc}p zR7Kxdr1fgx&VAWc_xYi^e*S&zOVU%*gLziiv&a@ekW(XGuLc<9%`ewQT0}LTbM!no ziq{@FEJSxry$hJIDdA@mDJUXA;SBe1mrM@jL!b;{S z*%p}VzoyaLY+c>|SP|pMGm!~^DHF`D& zz}9-YTXd0hSpdBWLz{|ZLs4HX$m3r1CZ{_E;liNEY$vr6_7TX#x<~TvmA~s2&ZiIo z2CFDB?#}TYAe)Q+7!eZ7#7#a_vteGA?*d+r$RRv+JlD4=L6jDh4rC_hFq^i6=Z2qp zbt)3eWiHH6QkDX{k1b?S-qSoE-5-}f9^Pw{pai)1u`))GBBJFj9oAIyNNmZqA0~-I zS-P6~dBfJ;$Fg8(#AY9O$-bu?3(t(UyUnuMtfow|9JM#-0GUr-#GFK}nR0WJMv3SN zB=z}l*tL4$M#XLE!Ow~VY-x@8)Ir6J&W;SDNF-fB(1t@R$DSSO3TVy?Wg|OfB8@U#GiQ4 zAE?4}%69_GYo7)Lcy1T5@|3akW5V7uJU3>lY# zRI+Mouhf9q;)!(>c*j*Eddjhzx+bTzB=fYO!u&VoR^X&DnNP~_@TBmd0$!9+fmteO zO#|x59L&LtNoR{YR(Rv7$C8+%#>k$Hj?!T^2B~KzMK_!LR?@4Jdzkh^<7{%Mv`VK` z6&97_ab0h3Zy7PIBN+R%=fg*`MJGK}h2`~lQ5a>XGy{+vi401QrB|D)(hva7bb2C& zCw;msfETccytWr1K|UU}LS|41um!qMdgkTEy$9$}RXR?O% z2|@}fuXc0QwlNN!7(l;pw*f>O1*8;UUWu6y0Z;Pe?%vHRZ!DIEe6miNZ3B=n8(bJdF;^qos(EBw9ODj~tF5@`w^smr31y)E!Rgkr&W} ztTCarrWn5a6eeS*QOhP!f~lNjBBrSr>jc{ANSkUbT7QN@U^nxsGIIdoQT~y<02C zel}vEDGGE|0$lg-alRPzi-=8$%R_t@x6B?)tVlvQy~|vDdL6fH;v0&x$Cs{d+z|D| zy9GRB==ev`1+(-VDxwKZz0O~W#*Tx)A8RZDQe zVMJAq*4oinc(|T3)6@GHoX4EOQ3Nud{qr|!Y4!ZJv&Z-kFS_t1gM63S^_+xMpx5Hq z6-fiPyu@!G)|g-|Y9I@QMO8#t2TcwTG^$oAt*Xl|H!}-zPaAGSTG)+=fZw9L;VXIq zE2AsOL1Z4`UHDQtRL`Rc;}B=Ehx;`^bY-rSCVD70p#pI-kw8^a9hdsm^RnnR1i{5) zoDqrZ)2H?LL|1s82Q|^-P%sif7dxMsthD8ZOkkFpOn;qb1dtWx}d4 z-NMq~$ydr5+Ef~gKwnyGl1KALx3_M+-o0VjKnA(x6}?7<=b^6$i^`YsMKgbU^tY=& z3dlNBZmVUSxl2kjJ&2bB!F!Ld;+%hW)zu+e|ikNd}~KL z4-)sVNFVN>fp5WrCPoYr0yBYFRbS47fVan+MP2Ay80#z?=?S3&BqFSg%sOfjdFItC zf3adE+{cP25G5czBK>h0L(j&NlAhgsfJz44YHP0M)@PrGgjj3gnnKKnXVzH5nZU&* z!F11KNxfD;(mgVo?4yW?0Yb`B;aU;_GJx)rG)0b0-Ws}jER$q~ z8MQVF{2_eX{urL7#MtoqOGLAE6e1$CM|v2Wa#K-h=Rr-PyFDtOpc<9AY2k(^0p-bB zhVE(pzLVS6^pF?=QPrbq6Dh)GwQ!O>D8APpKmW`B_}}>l-*wOb>c8}pfBC1s^_PG8 zTmSh#c79WB{Wt!@fBbj<(eJJOF!wDnzps`)liv5SX(uZW%5l`HUc&!5PQZ$XA3aL} zWHeTU1uOy-OP)xV-zU6^7qgyWTp8KLqFTLCUS%65fH6lmjy;GhBBGl$#i4vQIa(8D zMp_bOc8l&#L<7?*q9Qb&XHq0Mn}&r}5BoCf-ER8O?Ii@LlE`5_zV?Ti#LICiD>rTV zQq*i@_R@?&-3#PNp5f^clG>EP;6sEt8AR?KeG<6*j-L*?Jo4&Q-kd527^>cakloQe z%_)#UC89v$U3~pelorm^Gt+}E2mEYkET>YOK}-*Gw(Q+<2W6R9n1*LI)z*k5n<7&vvs(rs*7c!!X7aMA?nWzTCkod`p6TJ;?9uzxCoC#? zp`JVn#+L+9R~AxcrqTQa#};0fxY7DcLQ0F`yeb#s1QT;{+ojs^^}NcQr^^@#&vN*- z&tVY~YaLy8J%UE`onxs1>T1LW>wcQ7jfdqnsM0P|`TqW#(H_b7;LXIH{8f+0lbn0a z><0#mm^0EHlNXO^d>*i&ZI%^y0;6DDdxX0`tg~dP=bMTJ#sV{7C{)-F7ExA_#;iqK zAJUk}ffkGPV?DN@{AgEtat&>M)@9$$Clvb+B*4z+fS6^^-NVrGX*8Ju5DN>Zc!;ow zOC>Zg7*bXWW&uDPN@>g<@F0tlQx5~zYJPl1OTSShr-5_KD9yr}`JW2Vb zqO&NQ5bN2r{5cqI5Et_5L3d>IlfCE0Emt#~IG=L6mVlW{_pe&`gn3Xdn|Y=eEUjWM zEdC$A{F{I4AO2<%f9Kmj@n8H?U;eqD{Nlgxt=B*IlVAL~Z@m)H?|j|=Pyg{h`M3V= zuiuo^@4tk<^IY8wtmnk2+_WJ>;JatCtW$k#_5m=U_5Mi+gC(jlIUXct7AB5hxU)02 zw2rWJ_jD`J*)vn^<3A%4<%B80!ki~2wI+3+MygY!r+XN?yQ)We>M$^Xl~WjvNF`(( zZqjV3v9VjeDgZWK*5l+4i29qyzm!*_|TXEllWWbSjHbV{jAw;=H=N z^?rFVrU@c-_KlqPnIaCA>5A+g*6Ejb4IE`HAF-D=p&`=I+4e@23d8G#* zO_g8IlZE5cTe`)3boYr&5ePDanP1x3r18Os?C#m%87Y`2+7XekQYHy#$03RcGrKP9 zjwIHRE}lWj!vh4n~Mr7%X!El za$Ww_+vOe*2rA;mdv~IXAlWFvOo$;>Ny{*g(YN;S7+SZaA^sk~)C?;#R@4VsOo}ad zAVHKYQ-2#yQP3&gxqAgp7KSP}l|v61q0GDaVRG3si+8r`Kd=F)!NkNQ3k)GalipUk zTdSl?v6u?nJ)oM)PLV`ZO}ACX;aRGOCp%5joroGU?)e5F?5UsY`+4e!Pr)*Xv^65Q ziMvnw79}OQ5r`BFjsZM~%tdOR5WMurv~Y?pPN$PHH&Ivb-YKHSIbi6a$BU{evrr;D z%=_b70OhHdSU$Toa@anNZ0~x-jOs$%$h5 zyLmsA^T?*sj>e7D{p$93xS3mz&U~phj$rT6{&TVjhN^OF$8j)A>FE38>Q#mjLHPdmhbkCU zN~B1)ZX8|mX!Jtbl!cj9nX)m7(dA)I?)RRVL1YdP*L`jzj8lQ+OlGMqqa-Ec_&+rN z1InDtHn@^YT9U)##lu5fzR@79 zoIhG)WkNbV-mZ{Lg(_I2F}{2~MfKC8yXU93>$>{Rp|6<9H31#LQd9F#&N>?QGRmax z;bGS8>fU`SFzOAD4EMX2$eeS+FefEEd#FL=;ACbNjP{Z>vK=78DzmX+txW z!%dR!6t6@&Du^e|!zw?OD)n;Io=ey@XkfMb@PKz>7k}kP|3QU?-uDT!5mNjt8WCNS zdsW*yV&>ZNI|1Xg>a1@d-1DNK>__l%hzMS|2{Wi)pqa>;JLF*1D47w1akT3Ds6gnZ zdK^tfM#QuGb(IG;=6vN|$TOLe*9_uUe)rR_{O+e)`Z4lGo(fVtQ&je1IycV@<>x{x zK+j9}nq*^3hIqRB8n54=+M!TLE#7sx9;7%_b?x2SB&g7 zif1(o5Ho%W8HlQ&J$vNodK^tzMA{3QN?g{j{>i$T1=v+xE9jTG)x==fg-V~pSGPw+ z*G|;hl=WwL5CsdF$EU{?5p=#Dja3Mch&o30@R0BPcmGXHD{)p7bKF5u;1q_kFyp3Q ztz2KsR>%77Bm&8B1Y$nT*aKYWNM}f3vA9kfJii)v)y)zS@De3N&^()C>YmwY^d0Sn~LF;Fdxd zM^hCMa711;2+licU^3_LSk&9GH-zIPOsXhs?WuYm?QE@7F1#9qGqT;x&Apbr zsrByW;T9fsCaSYO^46x`-}RJ%Zh{XpqP|mzN2aQ7Nh4#iQS&7ab;x~i4fpqo+_j9VPYNm#uuFwbKH)p z5i2+nX_W9f_W{>_wZ&fgxm?wkBR`?|GV8 zBJz8nf$e#i=SBnF_n9yn|8{(qT1_LnCiFrI-RTR(2EiM7l zR(a&+nIbUhv>VA-et8_^>vI+uhoQnv#!5IA!#02;P0<=R<-&T@h2@1rAAx5z=R*(@ z6{KCOqq}EctGf9#9PZge#n2k=+ome2K&==f(RUb32~(SvOINfUAopFGwG|^(s5LZ| zo8VJjRUj!Lzk-C#>IU zZxBcYF9#>l+rvE)^I%r&%u#k!YVVv{c!YVHlN%xsgG0JM56^uP?0$gz8!E%jtjz9i z(fH>0Lx1~L@)77=zT)#+xl?@c%!Ij(s~kil<+N7##qArXoH?<=c%j$4L_`V}zQ}FQ zUw`JFEG|o+G>r54WpZKb&9Bc4$-9gIh%^zV+*2-YqN!?v9A77JWpxJx0Vr%-(0EAbJ3?E#fqS6o*j~>iVD8`@;ux>b$j%TkW8Ue_q;AL?+*kv z%gA))j4+)MN-#Mx_j?^v;0l>-Kd*=(EWC5n7?y8K+C;6++8V7%Q#qQpS^I*JmK6ix z<`$zYT8nEwaf_e8ORHx-9_4b<4v~qJETE}0sT85~i0*!wb@Q3IIrd{pneVsX@hZE2 zY4hvVw>*lP1ZDczDXQ`vS!p>&`jhL0m6%q}H_CDnM2g6m=&2B8@4a6ag?Wvsrmt_L z85a>Vkn?m|nDtQ>&xN`syUf&%Lqw608U3;=JZxIOk}`|ae47f9J0UzjC_(tyCq#HI zGB$(YsI70^P40f+k8ptjJ-tI-_8WcY%xoWPPHM3-8pgLiHixo)+GT))z(T4i$||i% zP4rj=O_W-eMbN@7@7=1EOW>B3DKLkfrGlWR@JJ#=P&|Tq&xi8sOP;5mrNTM+l6dR( z=)+TS^;E@i$f-F261jZtZBT9cRPK5gs;@0)4z;jFERU9gDSoc-m#|cJ)h+Q_rTS zN>f$V1OAEn8yuWAw2ve3Z17?YXOq{nHPx3xRg%#`_Eu{F&Hhx){fG(i{4{W(OwEJH z>oI$1>ekD+<@-s58Zp!dLybiV&T44_X?oC-{`#kdUsw``LCX!7fx_^KgtX;)j63@jHv-gO+sZpw`mV{k3Lp+0*bi8v+t0} z_jTFQzn>XIEBbM+8}31A6cJ6iv{GjsL9Acht}BdaAjD9{&~Yy^C_`rX%32L;rl9zA zeLYf7Xb_rXI-Dp#sWm!Uz>GN^+6;)*>Mj8h1AFvaK9jqiT$S%pBKf3|$$TboO}y$7 zxg@<85I#xe=jV&fc}`1~84=d=a$lu1m-LHSxOB{vN4X%Zcui(CAxs~hqWW89A+;Aq znFyBYSM=VS`q}h6+RM?}DNSFywYTm?fHCX6`!)K8a;P)`Q#NE%C^15r(8DdlS1C;_ zs+8$tI0p0lbX`df!lBYw(WH*Din09OVywsvL>L9QhI@xXMOAB{2;u%lM&>qNtTG~8D@Da)1W+xa*XXm3pwXw@+cO?LO8S2uO;ypzvlHcICO`9v(y0JuE%kugiP+ z%QJaY4DY;jQ=hlOrq_!U3A3bh%1Gs=tcoK!lX}8RF;mj7$XsZ)MM}EcL%bSjcOYYW zMQIiWrlz)4h4`4W@WL!)j)vJhe~&XbgG9MCJq~SJEpQZI)jh(iy!LWR4{<1j8vvHvjQJuI%Yt*#CW5(fx9C$X#ANV9@^R& z05RISVc}kWt~+vzHc}Yj!?%As`|!CG%zU5ugKr9s$UjrTd)k zy`6n293p$9IIB|UY@Dr19A*JTzOV%?Zq8kEI0KrpH%s62>w4RtCA~+eP$2@Kfk<|#5w216MNkWDK{43ibK@#ca zaW9NpU=!}cc@#4_V)k#w*%YlRC1$Av;2f4MdJbZ(XqK2uSv~wGse+rJ3NCiK+|9Ez zCq+Z*l@d(JB49dI53Pj=k#SLp;n%EysVGcOSc>6E$Ps`R0IOzGX~H;|NYHr*RC1@7 zWdvCS(%z__lzO1G;_wm+n8;PAW`lQHIg-dcVBX9CW!Bb^lwfr8jFKXZzP)F34-1ln z0IXmV<5WR;&oN6+0fQFRvIZ?)EKLT$0g5pn6G2d~r80piMgdNcGL+=vH1;YQyy8l3 zGx6-0&4kTz&ZX}e(Jd@RCa3S6B{vxxAs7P!4(x5UK`kB=VugE8x z;RvT(#0HW4=pA%K$wLSTVFBr8M$Dq^1X(@VWmqlW}O^C zJlQWnLn$!Ve?}gq!DuZZ1kbdd?h#kNC+d;h1dU0EAe^lM%m%onyH^|)16=MQEWKhk z2SNa3HWg*OG@(rSm;XEegUmH2C|TruP62Z=r&{i6T3vfv_4Q}&%uO$;=eSn(L6bnv zxh)L{U1fUJOFqKhgs7Z8$rJT9%Znka8+-~z4HF}xM&n}uFerj+HkIS(%q#*ytW2J2 z0gmZWENRyWSUlxu(wMoGlee|j4sFFxlVN(~I=a-05d$JRw8sS@a-sm2&XPo_DVp+8 zAZp?2!3{};kK|ntHEm5Kr&KG4TB(lpOr#(gzvk*yV*;#mSWnXs#uQC(Hhq0LL?tr1 z_ea0XJvW=e{36x-9_nr&Md)bqdcJ(|a=sidO_d04{z5Vh-IH<%01EUW z+KP}-aHcIY;f^kw>l;AhnFUZSb5FB~Ab^F@6irCD+J2JIAa!SR@;P(3G1*wJYEFIU z5N!-la=jO|V+Mg}^g;OsoyUzD+Gf@}0dlBPc+7SNKGvD}IF92un>J?DtiGPWQqqq+ z9!yeFd69DDW!3{QM);LuSN+Plyp63DLorb_MHLnX!6yW0h8j$X%hd6Y&R5<7-p$=-(_yLvN{2nhO{!DQA=S1Y z19~276AK88U}WhMTRl-lLKR`+%=B{VSby|>UG+qIugLTaVj3=8VxmGp$jGB-Vq_%@ z9Ze3Mor}`Ksb~{zBFe+)a_G^tsho#?@p8PLdNfuBQQFMl94GDAdU#`Xo!aJG6H?Hr zV5AD4hrYfZuP?`;QeKhE?CtUR^y%^G?eX^Zc)RTCVNMnl$q^FC5j^)M0TqW$hB}dk zSZH|G>xH$rx^=f+(M(HaL@9uZw$@aeNMrC|ZRI_)e-cCNM#o$3#REk^gYeUuzML z(>!2ZX1;-1Q*ncFv%>F&LEbFV(N}9r+`ch~@!z?6XT*6`&v_BZRXFUXJ5Dj-x3NU1ksKmvvv>`Bhu5XS1$9 zpu(-md92Z% z%%5Y(Q>Pc5!~{R#BI}9BWdY1G;}su=COJWfB#Fb}PJ<%8^1x^TzE@Xl zcFmdJ`_AWUjjFsDtVNQ;a6o6``EYn+zb3n=_Wb(uFyt{XI>tvq21Y8SNpUA|p3{(nM0W&iY0njk+*7L;>;i~*+D}hu|BJU=0s2ojH z!IY6h2-R&AAv-F6Kb38GuJKz854lvVK14>S;m2 zpWfwpB^RWxG9}0GWk5xSTS{ijj#mKfUFmLZlXuFqadHRVuN*@=yTWUElJyIzOA{XZ z9E-r%wTw?o)sw)Q)-so4Wv!^(`us^`H(`Cc)SAkdhZ5oI%lnv6<be*&LGNomiU3x-jf&;ALK_`IgH5V=U&=S$O}P+UGfPfAZ8kWtTiDwP}CK z_E>IgArtqS{_M)azo6t+BJc`K{rv2>bkbV4E^Hee000Id1XrBZm`ps&5~JAT6*-(r%Dr1 zJw%Sy;;q}KM}Lr&Fr(Df9@%?*!j;LdCpSi8<|e|&qvxl~dfHf#tJ<3(dAN0$<0O;` z&%CTBxre^AGLn?ETguuQkr_VQ*dR)Ge|vDD7 zVdnnz(e(A@6hv5Isa6*L6|9$}2!r&Z#j{ zj!J-F&O{_)YzhU5xeo%flbcJ>Uj{fVIokk!K!Lxk^(U=@o-#t4m#fnrL2Z#V= zMp$<92TVnpwnJJIVM!HNq4QApLYc-8a*^M3Y??&H%`1}xr@X@A;XU{zyCH?+N;FVP;?bqz)K;0f>Zt{h3)?iQ|QP3(|UwTZs)A*@a8 z?dq3%r!l3B$aKpx=wu_bCOAb$OMtELBjJ&;1x4R!b_)RsBF@Zib@=ROqt}C#`L!lV zJfbvRhc{`uZ|i7xN7`fssb!g4j3OnHyECk@yfivA6%``Gm*=Oq zw~Xk5Beih~VYG`_s#kO~w2}!SwR1#;A4Np=@ayWIK3&}?NCy&eox8@H81uE3+c%O4 zP8K=OqkM~?H0x&3EiDF3IFf>dSg?}C5*x?!nZmRmnQp(=Q^0}?>RSGd#FA;PMvBC66<4^_$|!@I>> zkD7CgB5o!d+_<3);xn_vGAdQEP60{o`PR*a@5sQV=Mg3(cZZWsH}9rYPJm z_51j>+{pvLQ{wEdZAsu=`ftqrrKFPKj*ilZauX?^ZNZKXMr#O$X zXMTsn@6?mNepvTB4?0wv^2?#hI8;78`qg_kyUe`ztLfSFZ2Hnz!NNFO6IQkU)#K9@ zHf!w|G>tK|goC;f!{ccdgQTfeWIBM$x|vxI%5mzQ zV^fbLVnfnsDGKX|zsn2<6+?IQ$VXNaMIyO#6RGa`IBgHB4^gChgd~VnRFZ^Uh&_FP zlrTjpYXlzf9mYGnU?f5U)>I3=5Z$A?m7FPzqls_;WDbvYc8}ailXLSbsWhV|jC?jZ zR8<>@&Eru$3G>Kzakb03G0eMMlo%0!Gs_BTgZ(wyYE1a3liwx+^c$Y;Fln^1qGcD2r7JjN^%cdeChYEuPjmaON?qpt|-6eKg?KjN!UCu*X;r{}eMx;Vb zI8%w*IMi~b3PF;XvXX`)MtJe^j}#B~(9EKqyXT=CUS78w*h`;@B)z!`MFr`ZW&Opq*r#IeFkSP`th| zwjkk6#%FG9nle%A4yALK%wOsVsJ<&rRMu#+R#nlle76m9G(0lDKGaG_ED7r;B zMWnI_$B?H~aLYQymhLc(L%f+iJLN(A!eM zEnGd4Fy{UEfI8Y2j4^)Ax?ah_9^tB)NjmUgEFv7l&cbdmb2IY0P0Dk;%FFhM#XYOO z(yc+D(x{_%LV`sFhc+ZdSd#k!bWJ{B<^FBUHfVw3_cD7e7E5V?ZGu@&u6fwu8FnVj zHzek!_Tc-J*Bg3mspswv$!M_0mIs%dxdv79e_ANm75XwwT~Bh?l63JwF(M*!t~%|P zzzB&8-yr-JHWC5}d~1*_47|PRQ?+JhL~s$2Idj!Mjm^>i`s+U3XYZ<7b7sb$F@A&O zfB%2|fAuEXgf)$f5tdb4COhy0I$AMt9)&T<_JZ-5D7L821*ZubX}V7 zn!2T`w${C?ElW*s#4&Rvs&9AE(bg0^u!O&~aDm%@PZ^MdWKltQ@igKiW1a*7N?1~r z8={a#P^!==B6kEOT8AMj(uFUTOY~AL4j!>tBtZA)XMX>bUis(tpWEKU%x91RAwM zQU>iP89X9|4GY~t=04}R$GGQQB|^c^7O7*om_Jeq&RzDVims@V6DRV)uUNlU$R!o- zy_zeFNEmE}%hgzhS(%%NFjrrLKko)SnvCPG;3qvh)mL-coR2(GD1USA$|A@JvfA>} zlShP|M*&V0PRb4Q+HKlYRk_yIu_+~dhQ~7@fT=>4b_#mW_cW`1AW#F8df`%K&s1W^ zMP34dLzpBd{mQ+`)%Df;>*`;x{(AM-t6x{YuHJiVT~%{4xU$Mo9a>?1k6#J3thC-G zzevSuZ?$6X+Ar;|*00uIybZaJvtp1=g4g(J;5W5-YMS}8L5M@~hr|^KEAxHJeY*k#5NpSc968u4$l5|xO zA%1?EzZOVq%A0)IQpObeFkLe7u=%#Mq@>wFB_OK9eci@EM`AhR;4$Dy5@lv3NcE=X zMA2&G$jtmGR~C*aB zCXFVC&hsRsWx+|&^MdT4-c$y0q8QCQ+!CI}D=RvPhs8a83>$TH! z?7riD&+ni6=N|9jBe#$cuvq^^HLT=U5JXse@7E>T37FH~@B5vDleF5B#3{z` zC*u3j0dVB^g1wkO%fa|3zcW5HN|b_80ZV|>JTT|XOC~5p5>>k^#V4Zu5nSUmkmMQ4 zA6k(b+rx?6A`!MZme0tlluI*b?s6>CLu}RUA+ufz)(3@z2Q*yd;XH5*h^|do=ZyA~-fe`KsV%6;|gb-iBK^?F@j*Y&#k>*}xG zdaLDCj!?LigF$pmO!ty7$$7DkTY@q-=A48!kuK6zFV$CTuhy>Kd%JpTSL+!g`^4zASC7|RYXS~|JLKY;$7ub-Q70*TfWPQXIE1(pI;k%%C9P_(J z_4@6IITqh~7x#&yI_QpZydLXa7eK(u!eBqRGxa?)2h~<&bz@A#tD9L+`k+&!zZ0yB zFY7VW(lPB$=Ku6*KHR5AVeFC_l5;9>E;?!RPdpaOH}L?_G#*JJ$*TBkB2A@>wAQ+6 z7EZ!xV2v^TzEewQ(^5?MB$id3R$scKWS2D#vDB;N!Ji*Bw%_8jP$0zbB$3lo{CV)lrtUC&>bOe=QE9n6Q*qZQ5I}!cNF@5fgn1ipuYfVre9V3A% z<@;h0M9~-X;%QdvNpRa7$VI(_n|kp)M@P0iG<+t%GLh zBlUwYbP1z365)A*9?=}*NHPdIU61FCY*%l4HnzUb+G*xmXq8e)vy3R*-=bz%UMd2l<{gYB zS9+45c{eVf%dAc2MmQ)-bp*>|d*k?c79DbBb#Vl{1$nG4H`Y@~S?C`zjwiVON!Qz4k4>$RKfK>Cxh2)cZ@D-~60hx!2mDy=tQ%0;ZsQ`j|UywGd3Jly~z; zCHI(q4;v$|k~9d$H1YsJD0`pk440V(aVY(tnA+u>P$Fa!);(-KyA`H?1hygmA>?sS zN6*1y{9Z!gw_y&4<+S#1D>P{zE!*T0D_)zIQ=o*Y-kkQ|Fd}C_rEQ>nnbv}Z`PIcEe%@nFKLNclqOZr92>*v-jeAMfi@SYgv^u)ZGN z(yy1CQUqJEz5Y(YN9?5>M{ z_W zT|gZIVVdtbCzU=V18JP-Jto2!5`kgiQ2^QqHuLZI`<}+q(mkJm)-wCWGC(BEly{_d z?fV=)BkX1-5<>6vufLiqznGbM5{)s3nJ}xe3ND7Ux^5N#do2l;47WH$=00t@PxHIl zy;z;Ou6i|{$opmuB1>Qw5!>F2dAM@~ugl_eQ?#uXIJCd#-RbcwK59EFZk939DWVR(s!rFg%94`7)jyIOcdzPZsO5kR1`1veFs% zu-Ozj1MVuV$-khg_P+0XSR~4RWcD8Sm*o)84n4dxrd!C240ZAc(?Z(ijmOBu*`$ z!ssn!3QFL6ggf0M!Y74!7wV!cy)n>1%(#E%o%D9?D#CwJv zUcs%a`D85qp@z+&ODYn=Mudj7)_P+Rxw3e8|o0d}7Vw``g^WpjRh>H;(+kGt_kk8Eo6i zcQqM1gEjUXDvRi3xd;)8n~<=6w@m_lNXbi5;qEcWrmxo|JkkQOj{y7RJJBcEht7vB zKI3eUgB*|PPcOKv=Z3dl;wTa!g+#dRP$?g}aR- zF=}PItkc3v^cC}M-UTn^)0VMTmaI0YnCZCXu#@bcn9dG6I}p`TlMC+{>!+QI(A9ILeP0 z;wOjk{9tx_7D1o-?lo>JR%|x^0%J}K5sPxmEzNdF?dF_ET&N4U`!ng zy>+REugkjJHiLUf(B7oewH3#=5NX-k0Z1<&3twb zOf!(ra#H4Xx_U=shGN=!>I?ATaHly5c{oC#!hH3XqwIUoD7xh$FA@!-Aj_JUL7|jJ z-zHKu5+~|4n>qr-iNXwCpKf@Z14|Di{2C5s$B{l4$)sJQ^$JJd)298Of?`r`l>$@m zn<$h;nY)Uay~h|t-8(6BDoBbnQlQo)#KO+L%LJ`yKHZ1=D7Z(R+q8`{|B%*~39wo0 zorGfi={qcP?CS5I+Pajx06z6xA1AC6{pke9VR+ATJ)4~x*&t18gUj3Vwi{g~`5TpF)aJn8!@38Xe$mGYY;d9Em`qJv{?s26~RW(}LyPsx#o zaGaO+XMAF8dfoaABjQgV{d0#tX=yodtlCHey@IL_g8Y?}Df zBA>SACw+ySg6vF(3i{9+eU!^M$BcM9AaVLK=#glx!y-OrC5Ph;rw-fW(bH^)al*sv z{6&O2X=7$I%~5F5Y5Xuw@AJS{2pH&3xW#Hw&BMaU%^cz4K~AO(s?4lR{bsx|$8@tW zItL9{0ZFmLjQ6i?40Mk<{Re}HS{D&q+9E(S8CCw4(Bo`AmWv_*XEn1g)JaZMxi-0V zwy%xiQb+q^N2DG1^XtGqLoOdDW449Zu9_qF{9z$2g)l=nG|OvtUi2xHN-|%JA_y;B z44Pwl`5o2_r9xhp4=X5+v0L&Jy(TLs3~ML8rB#WYrokAOvxm1vwG?ffd>9#ErZF)x zO`sxnYnT;5=BXYixc^0THR;;&b*J!QZb8!`uc>6pT?%DfDiLbt?sKz9D~Xwc0v?#* z_jLJR{U82&C!djLaR6x2x~4meg6;^5kZ_4Io(nBOt=lF?FT58+m>2r3>}%ziC!baz zhWeia%3$88_C13^P$KGbEj#PPiL{_`)*iX_NC$DjjF@Gi8R`(_glZ>La(~Ltaw&7L zwp!ACMtFo!g#)e2T~Yv(iszDStRDptSutEGu(GvsL7ynu@-K@t<|M8PR8t@ZBJ%9> zh`}sP1x#}>s?BMby{Em0zo);=-{!Z)+u}X#XWI9e-}m^w$IqDWX?LkmDl`HQJ9Imd z{UM4HMdUZ4MEV3dFI(UT;|}M+c^Y78Z&)_)E~!XTowuyI`QRcy?nnIQ=`Hygoq^Nj zw03v7FiN*25ed7%m1raJiuLyiSK1v{rB8jr@$+F=D%Mn9Vw!}rGB+hvQca0S zM6!N1sfo{8LW4AbfLjbA%f~>30j}l!BB~_WQKem%0#q4hA>jx&kOQ9jtET$8uGg!G ziQu%K`aS1+jGudc-{UNYEoB0@D!=VQCq@mi>8<6PIEyO3lfrm>W<` zCnqLosu3#q35mupy>BuA7fg&mG-sGmo_Iur%zKB&;cZy&#UwHu+SI_(HdwN;og$d2 zskSE4z!FsQX$l7=x40(j`2)4c1D=A3L?9x<$|Mw=>TVVW$SIb7n2?XTK$wFy*MLDz z8@)lTty#szB%Im^M938BK60tMH}@@7|puc;Zkz z!V^RdoDN0K*Czs&`y9A1HC1JhPk>a^gWhAB$MYF1V2!Y})~?>KUg5|f3a1fto4?I| z#{9X*&pqCAZe`K5s$7p)z+c#qUl!A&H_H!$10U;&Qw3Bo%2=YoX+DhZiF?F*#QV1Q zZKE>WbM>7u&y+l_uAKah-n2<--4}MVbYo8_1MRW$f)9Q~q3E?#l6YmmBLInUWgMzJ6rlmb>2$v`;yZCm zRf6n*mL>k(|NK6`Q!BT{ zh0lxq$PO}eW+%dLIqF{B{bu_eW9+@BJTXrxXUXS@X2wo-Wq~wdwB&_)%$S>;{gFig zkqRwZwrIyuoMrv*p0f@MCx&0ds-#T`AT|#pifOCs-=yBs@k?c6o9EJ2S-L_+uB%P* z7{k_Lh_6vsyGhWuZAAb}}Zicb;;REMl97AO`Zba4r;^$nkGlw=y%TGJDJa8Ivt1ikSgnWVeIIAZS;JY6k^-U|{&1+e5zR zf2U|HXt`X;VRcr_Aa=rw!Hfna>J-MqrgP2Si30G5;pEeu%(F>oq$z?2gON7%J7Q8I zRFA>wgX*oSKDU|cCWqvQ(zH4g0h4777Igv8@=E3*%8T6@A|MhVGG#8+j#>Y!@TtIB z3&!6{XZ(H<;}hZj$@9Rk*8v|>++E%NQB9ro_(^nQt{~Zc{H*8d;q4@^!~G%h)DiD> z!1|}I1fKxwowvO{{g~l1VEI52n9vmhbd|53r=`)X4*;JC3<9w{G^^zhIrpVVsb`vt zB21V-+J!S`XPG~ItU&609LD3re&U%P&Qbmk%m(?J4cuO$P4#Nc+<`v>61UAhiSN8* zV_T43<wNM|a@GtzSUAgEG&EH~mzc8t39>zQ#pi1FG|sy^8W zn?BAU0V!y*sQDD1(-S`GOKG$8HiuLuyDog*K1Y?1hscar9wKvRS2RVEAX+O83lYoo zX}R&ny06zB##S-H#yq1@rq`hfQpYrXM0nUeiOGe8l)I2JU#b!8G4J|_R>)0?y8sK< zMUx;8o=!2$?A@BYuEwfg@=~4O7&Of0hNlX^K^#Hmao^@{)21YP1Z@Ir zHZ4XCU4zJpDF~iwKz7iypdihO3I#Lz)eSNyyTv|clRaycYQIxqwaKzLGN|LoW5u~E z`m9bO+z@NW`4FmC$-cLQD?CGFO|z2WvJ30jjg#H~Yiib+s57z1Fnj@yFXGu?wt z`D!doEXMEn{+ZJw2yYXfl}x~(S7)%rJrie~&=d-(ED6H&h`2%2b@Pa#cbyUTp6)?U zy+`>lgdahUq|Z3_c(3=MGGlw)V@z*NUcISYxSFcHb&Px5V|WC!sA#bVC{#GvH^M0& zGk|&{74E8L?1?~iPcc%2zwO6wC0we~n4l!kH|RUpsQE6lVd>m)E^SvHSvK4=jOb2D zXKSJ&U0;p+)$IFi?_tA6>1-JU7{RAm)+SS5m!{W=1z#)*QSgoVz6Y7xd831n;jdu` ziycN7)P%pjTJM^vlE`SoJJICnL42qDV;dMZ9S?x83iH)2G$IcMu>k>riV$E$#o6lI z4RJel7r!&J=zsRFIODXA2)#9t@vZzz#v8+#Ln=_K08$xHax6+VlVG{k;~3pMXsN2W2HbRyF*1gM*MS=m z4uG2`dN9H_wcI8+zlb;=kp~rU`1UFaZC?R9 zMQ?|2;0W>h-~t|H1rAogp1AFPG~8lZ0E8P`NdmAkcV*3OHuzG#TDil_Y`9I2=`nNk zUAUtvH;z(%)h$ngz3;|6<@aXxcApbN`Kqv_gnj7MAZ&fuJ?1^_o-~(TVQJH{J?1gu z-+qr7U$54crRl{?;xgtp^8WGnvFNcNhP{XR+)2H$;?hIcD4QuTjoic7 zoC1u>j#XS20d!N$>AQ(yUaJK>H``7-12cz)oJqLh;klrhHtrEbN)QIFCIE$VPIk<5 zicsnXkP;yxWw>*vhOUgWok1KIpaK@{No)mi_%L@~h(?d_7*6Ko0YCyAsMN)lL_K16 z3NqVr%LkPK+LV!Sf>Q*8SwvN@)_QA;`I8TJEbQ_lAT12RDz8c4V3h=LrI)hYzBwtES@BihzK8xD*_VgqykZNXq20poC$+@xVXoF@E4vBCL*SSz#wpq);cE$A^#^O$n9EArG%Nmk#yb=88OcRod-M zNsa10H42Lns1dSD3iWUasWVurA5IZl#3xCr{vflsJwrN(NP0ul`qRuJMp$4Py+`ft zyFeAhY^7oGq?!}b8l)9W**ayx%{4EKN73hi={1T-d4!<)z4`L zpXL_9gw~Wr8kj)BA(FA-EW&MpsAK_O!In4aZm^jonDcnF2n(O?!#o%6!;FB&=t2VK z5T8m^p^9}R#PJAjy9OS|v_-_U3OkGz4P8{Ziw4DH3?4Bo9}3z&pO`^D!kx&YX;YSr z#T3bza^5gxZhvGAFLVPDNEnUzYW(WbyAZ=8rp+;Y+&0ZK*xIc4AF+&yv@{vatb&l_ zjPi=v;u3K$X2lsX{U2)R_+MI9A9K+&&FDd(?%fU_}l%$r}oS*p3keq zAEm@v6Yrc(Tb50NX=aA$G|lIHqA}Cn7#oAWoE5n^tj>JKJ3TNCintmz6*RV@W|m`h z=ac5<8wi$6Q%>Di0%l@QEXvar5x{h^+w!G&5naqI+?!_MMTx*)%I@Dms`2LrsmM|z zg2-7Wz?Y~f$c$$!!q}dNEDUOHpUvE@wCPxV%ckE1eS{O1;}Qoe=1lH9B=6^?5XT1N z&=!4$ynM<6>By(RvEw{#@h3VnfAk!}VP*?=Cc4IOIeC=e8Ce66Io#c5%z?TOKQ}QC z^gK3MJ5~s81X;MdlRLW?O|>bS3Nv5I3eHIeRXEf1?E&KoSGN_TnWy@t%*q!sM%I8B z<`(|83{ICI?jlY2YUD&2Zthmq6c%Jo{>%U7|F*l+c%qgfft*AvEUjIlZCel`B00Y- zQ03m`>iW9ct8W#WTq<8pzk2`bNn~uTwcb=x@K(ZUPnm;A9zZEz3J0<=J;I#=?x3oT@--FlrtET%@5R9BnrQgodc*jY6%bfOlmyE)>jI*BJ;Rv!IGaAEv<5P|Y{+|Ni zKiSi3?e^dnKXW^djyuvhl(Ou^o>OILvx@YlSL?6q`nq0U{dM(TWLjtrdfjY}Ip-L2 zBqcWC=s|FBq>Gbmtsv`PnKL)OUH$64Cuk@+zj0cs579$z1W-W_`^S>rILaJiW@pK8 za$a83MC>lT+v4ps)r_oHBdc+D4-xf`HsyFKgmWkN}UX9>}VS<(wrdB=qRPUG# zty?X}{q;~X^L2HA?<^Tg8}2*7g$S7&=RuGJ$DRlMF^bz}_jT$)0+PzM^(jA#+MJyy zXesw;%-dd*jDy9!Uh&w^z!r=0I*9@ULII=4&42F{Y1^a%~?=$dFyS3YyAw zwcd*;&x^#i@j`hYEvK(6LgPnsK^tdVk67ZdE6PXnG|LDYQ3hAmJw5Kft!ZA#6~#4@ z3?YJZg$%Ge+^}xtV2-AwQZ+=KC&59I8@c@(QV>GYHCIL!=EsVZv)&pSexyS^iebj; z>_1|oD78dY*0|Yp^Tf-lpebKnzApW``d4pVq!r9h1Ok+%FlS7Aj)@Q<7D#7ltX))9 zRKTE|A#18I>j$5CsOI4;yRh5}d!mP={D6%EIm&Y{Ep}?FGc%l%27AB+OTTsmaYxXC zYSAG(-fOl4(hq=GAj;fw2Kv`azb@%YOzv*)`~Lgy_xJaGPaEdbk}?5;pv3Gs4bp8- zD%M_R-g*$Twk|3VGPm~_)0}pqX)XEI+Y?JPfnUU!gCu@3%^thq^2gEVi}0jLkO*sv zVy}Mne)X^3UMd~j2;xx%aR$G?hrP|-W`!eDsxl4dhfln2-UNEyd2DZSMKLsGWdhvY@8Q#YjUwZzUJ^>+%So1x zB7{gHq__@Gva`UX+Xqt~^K(c}4lT+LOb5_yFQ24(t^ zIk{EOoz=VoOfHDjH$`}sSA@tzq^?;|P^3$!{9~L~0|3-mC*3thM3jQ^sz*S}c^Rb} ziqa!wa?66OfdV-q%EoQFqqW3_2(zk!C^l19BuiLsk|{IGs}MxOYs{R#VXhH#MvlDO zDgZHRB2c1SF6A&-Xo@M)_F*1YhPPB$-Sgn)D8Yv-u)31 z{RncMMHD%w=@n7e7(sQhd^B-X{_LrTVSS^Y7zE-}lYN=BVMM<{G@Jr^MtcqSErGj!*P z2Oz}9AS0e<=g4bZ%epw3DRe}T)MRP@2KWe3eiC&T77ZYtp%T-gDQhaEnWTnn#O%T@ zY}4Fksz=-`{2|&dFbwz^mVa=3DJftPi(-bB`Gjjz^uil7q?U zE)h<6%Xu@dCb@6CDa`yHP7naf(G*QNs2~Eje+>9{B5NtIRar!YyOK3^D|YYZGH;5s zd2BlEuVvLSkwQGn;Xs607$Tt>Dqsj`2L~dODjf)qVKc%Q(7oW?nA*`-$|27q&08tIvh zh57!_QSVT8X$f5ME;tRR&2=xQYq0vI);=VF8bzoEI+i)p0m^y&A#h zOfynh0j8^|2)gRknmY=F_&7AfJwWb+D4u*3%Hc8WQS2`bV?-c4<_us0;p&$*7VfMB zvY1iIdF%%iYjaBUr|P{ld*&F^MChK#)vvG+%|mToYM5Ap;COJS^vp}Jjc0L2HatT7 z(>0n^G(XJUJ?Jf=NNK>=v@wo)<_MdQ$9f!(A~qeI@tK-=N~L#_M5^fE;g(}~h660@ zBBAU}R`1S~YZDNR0}Qn=!vk!skkt5GobISALxu^aiN({H7j9-&o&b=lfQ1^QXxy;( z^kI|-YPub3i|FYN3>G1xm$ESRrh!b7wtJGfzdab#RRCc|Q@L_ZYcXkK!$H9cUdP-c zmgO7<&7ipLJsk92S)p?E43o_H;Es8tXR1}Vm|Kd3S(9qd0b$nAP>B;^Za%AJ#MOv} zMZ|sD1VM@5K9L~{E1&Fe7-UWAkqQ7GZf0h^>7|)d-&GqoZTI+z`}R2!#|lnkY;H5B z@id(xm#7N2JU=SS#L3*BjJTDL>OhjdxaoFl(dRVLIiq=pd*Nov0#fdLL46RzKxvGTT^*muhyLJ`<_-pk!4n@rU&E^!_1xB zJjc?#NfW-5G6ZU#v@7}$Zv;hzjWNlMBWh;UTK=%UQ$b$L-FRm9ry(|Rw<_n<1Rp-u ztuI+T>Wtq5=}HIVt?;QK=UM@8yDkc=Uyv4}U!=y53VR)Uc{jT+qt13{UuE{vahkQ0Prv1OmjFUUO7KRufK^_AJ+@yh&5w(sw0hP0V5#VgQT z?=6oni|`oXwXr6wNT=Zji{>FgEg*!(GTCpR`ITUQa>!juVppGyR&DA0xOl68dfFHf z(v~^4kyxM2=E+CiDGCB}-)5FN!t~xj=2yy8lk zs1wOK_8DcZCz&9X%g6 zIj4E?VOImVQ&#a5l*!FCA_S93%u6BAu56@%@ZlxWTwmLCr^wh(xCvyPlgG${M+z#K!76$+at}hPOP>ey0Ft1_BoS{KL@c7(v?W1v2nuz}SPRJYE$*D2 z$58VL>v)o0_9$`H&xL@H*y4$2#I#5NcqR^;h?hkV;vb}-&*uKwl`Q;ActTHu(=J+E z*2S?d)Iwbv@jJ|GX+5l#$62SwlrW>FB7zEEWX|+Q6;^YdQAM_8^2pI`E}i$$ggha^ zUQfkN?wCzT00lv%meu*(+tO|xU$hfNfw6))f~YH$LCq=P6YmIjzehr$m^oC@6cJ(; zZZQ^G5>cxmC!Z1bgh&F#xwE+VE#kgrdU%$-!GdK~_dG4*6j?rs%#6zwMH6(T-dF`^ zW?dF9nFo>Bj$wA+ck=cMI9R{C#e(%Rvw+07u{YDMO%#cVpH61((}hEus4zsqNcEYW z@5a{$~ER20RcTh{y6NovQkbqUl z3}$SWxv&sM?{qa$W+Eif{GKta$e@qGeN`9;EA90sNB-m$rBlf6!4$8ihpqs})b^=r z&)}zjI{5!H`sV||Kp)cXGkazIfKn#)!A_5pfcg1B#4lLQ5*jSdC&L*~&fYbeWniia z6SIJcwQ|_F)(-CB=2Qx-N)|z!3!_{$yVEeDXslgXS*}I}8;k1vl9sa+7vWAW!5ymF zn}9Qvf-GO9Skb0Q(ePBz2H3zeHF8%|5GuXHEViHHuqdrJD1KWBBBsZC3FBok>UwE;}BO>hqZ+9OV^s`!)ghSsod%8T3H&kMfOT& ziqn}IengZ#IkG#rG?gyPf?3au<;cp&giY8)%tZyN!J4_IEP%4B@SI4d#;nA?qP3(o zJf(>da?q|efXJQ90}eMtFq3kl5Jqn;SP6Sz1U+s1I`yTK6{(j%dZ78!H(U@s*!!WzdRUsnU+KYOIwd@Sr%~KPV z-g9b&6|-E&=&S@sT4hHaWnNaSoZx1p9WfIJ2_rLs5NQH@)N=8AhWMP@|A63GF@gtC zU=Jwlmux4_492SX!#Q$76BVW~=e+NcOrNIORGNxP zg^)BNW$Aj|gTLQ&k1-1D^(?*CK7dc5c@Ue$I|4q#>931L_+obAX=I2MRaf8BNOHRe zz^%956q0xwi<#j>e9G9f8g=s9h=!#`Qy^E%5yAYq=d_Yvfg)&b5fO+3;t}`oh#Slo zMelMoq1Q$3sT<|8(g*w*Ld5boSKcmH!rT$koRIaj{qw^duP}zKvxyl&HV-HHavdL$ zGZei2f@kpXO4nG~a!Cg!XY%}S70gW^d0+k6VJm3e-OXu%w;s*x$9j?oks@EGpkXnm z2_TB5oWE3Aq;*A$f^EO=%y~|{bR_oB2u|`;L9BBHR1`6q({FM?w1%t8^&(NU>l(w}-`_-W zF*4rvBBd_~@crY%JlN~S{V)=2okfW?wpZARhFSdhnDgqs-3DWS+JW$Wk6F5 z2@4-(w1VbVnpv57MyM28eQy*CkMX<5V36X@)Nua;vEBC z?j;W2Q=u)8jXwv{XPfwpL*h*3I?r$%+Asf51k)~r;6xm}MbP=!9IkynlVpBn`5m!b zen=Bfj&_ZflHPLPU_dkewy8F-Fj1&Lzxvhn9`k*VpZ9IaJa|xdIiM@O%?6iD_j}s+ zJv`7HWf`YDy>(zP#g)+y_9dp))P`3UhD)YcSJ!pEwrSeiy z=4!5Ui{d+xtHRyrbhDSh*7wyprZLUNR#kLr{M=*RwuhPbSc>ElWFs4EO;j_J6eUPO zn)g17oQ}kg_+aqWBsz|k4y zz*Z`>u?G!vbGm2n?SQq$uZx>Pz{0GoUCEkEvwM!)Y|NN7==7zeX=_@3I4N=h1%$&Q z?qN>TA?ERw<~i0&S)_}OfB*gW{Xn}nP@X8*jK1HAoJJR6LKmi27q##AJRO^y?+pmGR3ErezKGJ0m2f?_XoYuozC$ zXd2C+S&WE1=Z`oBn@KxbxdO$F09ZuO@YjPn2k9X2*$_sVLqqlp`W24 zbCT8Ipull?KCJZMin=WO=Wjwt{29vh+v9jpGXC%Zb75s{V{zdQ3IJ1>#wg#mhAQDN=NqsaP zYfN^KE=t>-y`F%948N**?8nw`kCSsRUx%wMi8?3+s`1#BB6AI5jU0I4tcne!rE2F) zD2XyYmsjB&yT)l;Md zbl@H_ZDz!JENd(zU`amunuEoGfsNy-j;ze;cw&$W<%a?w zrQs1A0fI9kLnBgro2S$IfXj@Nb0ZLChHGPCVDY*v5%_Be0;JmoM(~O;egL}J20L@4 zfAQ7jb?Mc$OQ!pB5T?hl7#8no?|Z(-yvH0HOiSb|Qs)nD?`9k1yBw*c3v%YkOe`8f z@B5yX(1a5F2r(1Ve6-|O_^o$48NZKT?@8@+hbMG;$sRB|tG^{>TET+(^oj^I4+taea!=KIPGm|io`{BKua%2wOQCtLdi&CONY0}>*X zNkfuBX}n@^7tV2gMUR2$GlAi$T2ip)o_S=bboT^XP$|#K_I^YEA!!+gxNMd#;J&dpd;D*~}dZ5_*`AIu1Ii+N87bJzB| zUQJavQya`Z17f_XHKqUJuciZkzsGy7VBPG{VC9KH5b+62Ut!&0P9C;^9X`UFhOh|G zds7iJo1Texb4;6cVc7`G=`u(ssd^?-F+u=ox|}TVh{vd(eX7kd=RN0LQAUTKIu#>Z zx)A{Ap2i#%JxZeD*A=~KQ$o(IoG|Yl8~RVM_>Us{z|Vd7FCRtjE=nJm`*E=RB3{yw z5WJT^wZNPyL&vkl`%UFMwBo=DtMgiR<%3Acj`f>t`kYe-X~U%?N?NdRx~?v*wK?DS zxZh(8tHET}H*t_y*G@Bn1{q*{-%fh9)g5VVp7H62{Lu|;SWS8)9-p5Dq#q%K4 zV&kZ?cH+b$!*E5utyl^cMu#wT58lsnKi9-oysU$bkQFZw0Sof!W(DF-Mffi3J%WyL4v2%>c5Wo6P(klNl;>iFQ$RxCde0)2x(WSdM2l#~3zX%7O%h zhjbE}Ufqm=h@uQIHw#+tASGwB7L83M3O|B+grD;an<_U&r{(WaXU7dUcd6KN2 zylSfMf*!=?L$ygRc#f;WrhDAvF<;*P`l|{kb06RDF{XdftF;E!ri?GS_22LJ?RIRB zpXZ3q0B1TZxx|4sgKQ+)!n@6@XXt>aax%XqvdUFcGxamF3gDe8rk)?%Ab8?sPw@B~YdLx!9rV@vpUf8Vy zS#|wdDSHI*3Jdd~pJ7B~;YTFUkypOP-t=H%AdIf0vUGgR8+(!px-p}0K3i`9UR~3} zl?P5=`olX%ZK3@aX?06bPK9BKPmma5R_vJT+*d4IPcg*u%`5%!2LkrM1g_k)q8*sW z&yS5eL`;=ila?Z6=8Id?)~D}C;4#hIygX$_5w+K?k~ngoOGx5T+&|M~y282>q!U2Y z5dV60RsMeSJ;ofK86!Xvu_$F8h#5|}@}h|g2x>%ZBLT`f+4 z5@p&Vk_Qy-;sHQKT9brRde|%%n}1}}?Ke8&14X&ed*KmgG2GmTMHA_&O<0?%s+wo< zESQ69f}BoabDH5|B;q0vkTU07ir3ntK&l+dZ0_$d-eaVa!7P@Mju$niSUtu3BqH}T z{3RBDN+RAoNQ5aTau4h={^Wq*pAjKXaUAEU?HTySN9B!`mtJdF!r@@vZxea@vMiw|8n(J_ z#p7{+^Fb>8h@;Qku$^*7M?xxn{<|Zb;1{L_^knkXdzclr)F6j+Q?lcWG5 zNS#%X7?5h7st<_ppbw#ILX~1Vsrxi>}R@BuepSAs{lgfau^ycbiO(}uAa&D4sdWz5Ux zSx8odNaTZ>CJ?3Q8TrOwCdQhXmo43C!vP9NnGWw=mIh2-F5VP~Bnf1hv#xv34K^sVdSSO&4p2D>@g8#y7{$lXw3_GXju`?r zMFM#uB4QHx+7HunPi9AHG2lOA5=*~YYurs2@6lop-9IRM&%pnbVtST`^iE${gGu+V(;IGLe^JwWO0+DKL9E)udy|N8vv6qZea^WM43T1~tIOBbyJT-`Z>MQ-4;#bA$e11Y-uN71 zt<2hB7?Fn~Vuk4PBPv)kv>0w-w_E66Es6GF{vHE^kZ?6-5xFi9<{NWn&5~N)(1^^&}=5(7rFo%lu=B;(@iee~;Xm4}a96qPnw3wMJ<75%;F=y`eou9BH0uhV0 z48yA+JPzHz?0YgBIK$`)ESJv#prMKksTWRB&}=z-h)k$ugyk_t4MX zHj$QCJmNhZ?AlzIn4QHr2>U4B@dKr8Kx7&Qv-?_YO*>px@yxQ8T&BwF-yD$RI|9omW%{Fv3hFke zd|jRxDn47!RGdT8wCq{6YgCD zG{cPYd46n{K|x^*w600pZw*cou7ix$PQUU&9pPkifF`r3NbjoJGH^b3nwzX?;qR1x zOdpA*we3$nKVM(h?Anx``>sPjuud2!uCCVu?rf8Q!qXGPgMQw(n^V88Mk!|c>+9ml z?>olQ-|X>O{E5feSxsw3k)XqA6hHHRPXRTN>$-aHt!Zm9=1}vm5c9YjbVMyKGtewB}mM z-6$7Z1I-L9*yUdnDO7c+XD{^i?%2k(P=2f9AOver~k` zl@B!x(`d(pEO}_ zJO(e}PyP$hvt1Dn49CB3@9ItY)i^>af+i{oBR-UAK|BNBZwneCqQYI7x`rE3nCGCw zAIJ2mX=1Lh_$Tw;&U$5c=bPA337Sjy!OWl-VRyBiPaNZ+)QqQ)envk_i1?)W9i!t1 zd{p>p`UMxn{-ek|(JBrE2orSIzH@FVRr!315}h!RMpsK@?{J@PHf>BF^JDc_Ovn22 z4{RNAwqes~OuL75)!w4WAIdD+6xJ}zeg@ss=ZH*Atro2y8WnH7ru70)8lQZ==bT@! zez7v432-$=`XtAw-l@n)M7e!2k1FyUFqw^jQi((Pn&U55Q%6f66QCbQ?dq+UW@C=&Z$s7AbCbb2KBoN-%@#Q3mC4#!Ne8PK%S>FDBgkdCnJ+;+ z%)-NYm=n4ZGa55DMZ`@s6BFedZ};J6J#a~}(>2zmkQl`LxrZ4wDb~FTi>Nv^qvy5fOLzCOX!{ zVsZbsW@8%N!`gi_ahHIA11z}@4g3)X6o-h-oVH)&$?tnq#Y|@-eULD>H>v7%HI>O{HdsU{Pp5C6ry+2QRT8+0Mj0h}+~u z?BPacY%b<8gH%MoDj=|MaP}4vgKS?+@#`hRQ-4|@=iSArIki1wB?bUax{9%go%{S3QD31fYWNq0WciYtBMlZ&DJjQpi~ zZmF!k^+P)-LXvxxb^3d`FP)e;g1QDPHwCdE6cN*>!5=3`U3gD~*Mk>{`54QB%#t2V zpqNTQGrKVs5Fjc-5)s1G1Y@F*x_fB+>^17SvdEQbXv(*gdaL}c2y&OGog{*UKG*u6 zNAWS_SQ6$UuhSBa)2CPc?38Q!75@vP8jlpy6okm)QZLneRzkd(tL5K_hTucB5XZq# z<#lY?x|>H*R+e&x^1zSyPwK@dP1`-EdV!TrqDm~3{B=-jFe%8F0wXfueCPD#Z{=x@ z&!w4CI^(aVK{2PBTdah{EJ?!Qgkc#$U`Fic;6~ z@c78QbsQftJ|i~Q)8IG+!C17B*y+zzrr;5$(8Aj#AYrdrcIaiqA=fOib;>vB<5@=&sjXh+c{1J)75 ztRD0p368;UJNJJ;GM#fJ;1KBTfl8g(OEA&<)!ScP|LX0lU$5R@tzD{3q@@_9g8B+@ z29C^|L`)86a)b$ma416oWptJ%+(j1D6s1&5?~rzg+)L-JTJxrp|$q9zF0IXFSE2-mFChDP8=>0jiFE>cHb7b zI4N~_k~4Om`aoE^R&xE4)*gZe@pKYuP#^PrfAD_ zo5KY_SfsT&023-Fswj`Vf|ZbItm&%((V2bsXr95(*?oEgQ=H=-PYU2c4~}g*WctkO z{l|c@&lOWPH_W~D*Y*1A>+9?F^>w{oyr!LCNA|j;`r(RFIVUuS{xZ>@4R_mBQhJDOzu$ElzXY^0Fl2>3s6DNRUASTir zySxIYOB&~y6OT883Qc0>lf7VJF7DDQ7LGD|42%#eT0(LXf_9!&Jk${IdYU5$O1V%ENXPraxSSTT4EoG~vb} z2QQNrUn%x%iyi_TMWE6fuoq+XBBI1vLNyP`=UOAC`Ba{h=fq-Iu6fC)x41MX?lkM> z2^Ug^6z!OpD$jsN+;fg>*w*!ir3YUjCes|#+8Erl3%5pkPvO!W)NSS!mP`h-`NlG=%o*c7*=G+GuVjDT;J>`tVw5lKUGC?jB5VrM139tF25P z+cQ^8{*isQSb`gONbsOBEodT(d*;T}RY2)C@tb1W6ERqQd;+{E!kr24>0lP2-l$KQ z!-t#4dk}-0#syMFSH4ujX-v1h6#zT=n3LgMEAU+vSFk5MW5TPbWRb?=fH}PN( zPDw!ojdW%jJOUg|RiItf!!a?InAG>g5_^t(=XVSW zbGnBC*3`Oc6OBgq9G050;1z*%Fy`Ec`8|n&#@vWp6{K^)O? zk1+MqTuYyp)V&j1JtTfI?K7s^)X{SL;{Pw5d)?y5#-7 z=P;XAf6#%=j}K1;KWxP{SjyMYRdF@$EdwNB?)RYY_e?2dgZ|a~SJ#*1j&JG87wl-So zdYVqz6%j$M&jK;_`Bas#n`30kHUL6xb82zP^pxGZT@0e}phR+D=uggfV!*2Ac`Dv# z?E7Iq{P-l|p#MLsx*C3hGW4!D=_;3sE}z*RzEvJ>EPPLz<<9*n@XWWCLOA2sQsF7g znx+R@Cq4y}cO{BuuG89>_A_kEIm&Rr&z3D7$B;)1o9^Z=#GPd0^rH!bFNTDD$0Q^5 zVpj#K(e%RW1f1B6-tUPZ>5YZ2?oM`lOeYEnr!WHrH-O10hMC+$LR(h>y_!eZcdpX2 zbN6l4sCzXl?C-%ueyMcj(bOtX*TX5&dB&UX`uyab^mhLHCMW-^@dbK|Pw`=hV1zEh zt`TfUtDdFqcJUtyo;XHETORdb6IU$AN+{TJ%rtw(@}uvg9r##$$Ffoed5GulzZ5`+ zECsQ1T~{FpUtCz?{IbT3Y4aXQL@8TyQbAbo7a|-^GK}B1+{1I=y;2^q-tqA8G5miT z^sU-DTH}kQ2^yd*3Urb0d%ma5>6JkfDOX7q?hKmcG&Dp&_JVx7vd78E{t$9 z_i$*zi^7Q}5(^Cq!f^Tfo=)`F6(aF<)mroIFDiC7Ind%bsgX{DE9U^0-Clcv%edW&gfHi<~2sc2&d2rbH}HF1xa)5B>x2a!*=H+82k zg$Td+($?Q&-ebJ)H@PpiIB!+d3aFO{kjgeu=IiRwV)pF$?H+T?X+dC(;4DM1v1D5+?nEQpd`x=_ zUrk=UwJc+rG|^k<-{0eX+n64~nJN-wL7vSLO1xGy)fw@f`c24-rHLp44G43ak%dQw zyai`j=N!ZB@9}o{7hfvv^%4lo@3&8 zi>YM8Var$T^hvswX34apD{!?Xbb_ zK8$>jJ3)n+JQG^+z-Kn2Xg%;1T35aZp$}tx!~^g?ZN0lUIF7wf(9O}}<3T{>+m$x5 z3~5rWX%S0vl9rYV$*EN0PwdZ-xl zyah5jH++TRe83`G1xxv=v(zYC)2++|;!v5bh4W3RtMzq+$5Ex1>U50BL|sIfnz9FM zczSe-NPRG2-EB-#V2Y@sDSPmosa{>@<7eEAyU%Iw8`-Zib8BJ|88o*VNBo*I=SKQY|M?}2cG3y90__6;lmUIMK%;G(q*_v=; zQ9)O^OLjpP1Tw%y&;*z2)wD^~Rx{!@{~6}mhD!~!fCw(1@vfoZVDdNY%a zIVUZA!>YGxG9UQ;-aZj&fj=2hba?kya0xRQB8;x+Dy?gBDpRuT9v*dxTRG9giaSx| zb=fS#4hQ`PyjF2qnr#lrF{F@e^(~O0YD(0criF;hG=>c}TL7gG?`$c`Vww}p+K7^Z zlA1vZ3SsM7h~0Fsd;kJ!SLs3KH0BgWmj-ZCoo?>af1Nw)^>(NC2qu52Gm{E><8EwD z;qbr{MZ2zSCmt3UlR;e|jNa6OhL4jJ{gJx)M@|p}t)*pR1=?+x$AL;r0qrhh7rHTy zneMYTJz>yD^7`XJ+EmzdO;+GXewF(t3K0E+ggZW5lTS{1rjVRT4CjT0%@d-EFmncp zM|0R@(@H^4K~ZBs6l@|a1JlBS&B=}S&; z)n82&Afo9pJbs4#+;iNiBrXMDJqdmol=cVWXTD3RXmfK$GgzG2g)8--G0^$a?(R(7 zxq-;Y!00SQme9mp%6w$xL|?ZLW@rs6o_S=I;cARsIP zj)LxZy1WTuhfSpnx4_|1e2_^eFspLcx$X!M2}qT%)_di*OI}}QHp0#4w73oCnGA1c z4;k%wrJot(`n>DwIrK^7shM2Be@;Q13U^^u$db=#V-dN$XSm7xp7)%?%}R~8d2fYd zpVNQdceqis=+Y!{vc{mlt}bju^B&WDMlo4;So$N$nA17pEx4MhY7l>&nS&_ngy+VtS-wIzx>XBs(KxBFyX_ zBn7Y$5mx0_r@*A!#wsGP0KH}R^`Mv&baO+nDt8g@g0R1T+|1_fH*s&8=ZYv^mk{E< zXJU8i)dS{?o8zqnW)*3ISLcZ7F=NgMV^DAH)wNvdX%i2+=lu82`#nYu`xgT6Bg-eS zsw+C`#}yBlb}6ThT4MC18`@pruMm|5MC%b4-E z+cfLyUG-|(wY84-kniN&&oqn6UQbs2@yQbVH0C}Np{ye;+zl2fwyDyv)@vt)^sIwF z<(wxr?OZD|SfY9iFo;D(8fj}?gx~M`XN=o&W3ayP_+@8^KcrV@GrOo;6=lAo6G)g< zwYRG`y;POCYO!GRm}X`+#FuUYc$r|N#${-h5h=Ev6sq?><kQB znhx(QqME zQSE{E^vccIs%sZI;@rSgkKFdmqyC2op^X?|QO)fTBRC4W zKxekmucR*I_UAV`Dc=QIPxs@MsYQpmQ`yJO%x5;nN7yRLeX+ucwl~VnZ>NEoU#4n~ z`Ba-SOYhQ}zIq>XzQ?$S&BzaLB?PSxjTVJa7lcplVDqtpIG+|ZlDyp33BrO=QGK5J!*~Uf57R~e< z@$lupT*2ChJ0?CVs}J!zmSnK0bk$dDS8H9hi2&s8(<+|rp7x&po^el`(-K^@ob`FZ zR4aVMVWv!Fn>&-7k4T?y{c@`@$zm8gm0JCs2uqnWaesFlO}<0I@KLAGHD2gdWJwn3+>A@0-wzM~1rb9i^&f z65*ze!6n(>Y|iqCeli&r1R~`UU}>#0*mi~p{E!LZlQOWrj3axFcbZE97Qs)7R*AVc z%zJ}f+{2Su=9>ev#dVJqKhB_!B}7@$&(A+M^Srbi<-){#S^^&2FV3)0-%*K4p~);& zUPOct4v{)o_(V{~N>ap=gjkV_wLCx}aTYe=z#nHkGSx_qGOA6EP~YMhfOCq@S% zpyvt_cz8iuOPG&*{*}s3vogLC5wF9n*0)p6UEBw&Jjq3+fAo5 z*vuJo(wtsVNA7dQJ4}n|`S;g9V$XlHh;=P^t%bO88P;+iSjygIRmA`A)KR>2yQ+thQ&So9(Ip;-}9a}rez3g`jzT^KN602kairi8-MV8 zIsZ=R*7|+cKb{luJ&j3IxG)KazyW%W@n@r&=j~}V&iO%_nDc)k715IBm_+RHifJ>? z1iajP5k;OpxwRSO9!1T5aQ+nyNl{Y50EgCFDMTn_(EdkuWKz9>UNR$QVPz8cbto>t z>(QP3`dFi>{`I<4#CC3I-svj{FO5qFxGCrXcCB<#eo|(t{z;uv8IZR*^W5uTi>c!z zkI>!?{VpVa-Jp3fGmCJ`V_bktu+Ct1ew4#lDXhHYMvFuStQZ+=fVf<;PA%)Dw1Gn;ev)_T8QSHF6nb9}$w_egZd z%AzBDj4>X%NiE(|9gJt2ScK2zQ^^+DCIT>=8<$Ulf&h(4MVKI96(UM-%18Xz9)wuL zaGJyI_L1>C6eK8HpdkBE%Xko*;hxfkC54J0I>s3EY2vICi%v=ETDIM>Mza$&K5J>@`!`8mw@4pO~;5`lxbWb3`E^lDgQHo0{Eo zzV9*3J_s#f0-S7G{M<9k->y5~$C}Vr7YZ|@ut03ydClJTRKT3Za6!Bj3~kb;1ysC>9zm+2Oi+B)|9(UQkqTNY1qV*d_>dZ z=RJt1X-lPr3RK9Q0^FOvu6C(PP!eD7IltfUpE1TL0Q)+Ad0E*uc0PFb4?#>wRH@`; zRrZOUFHvXyB28o!e5ltV`_yb!IOA?ICOBU^euW6)C&6QtFgznX#&8>MU0ZLhi|R`* zwV!+3!$zK0{>&F1(B-i_f3iXF(R_Xct?$3>88ggVe3ElSh(FSptxE_{c2Hid_=V}r z+9!a)fN1L)uZvUbA1b$CIy`n<1(E&F!S(1&qyYs#W_nI3kz7p^+X8Tek9$sE^O2c2 zgNKqFpb(s~-G`Qz@JRwV(!b(3p`O2=`EWsI?qkes##Fd#bzdn27nTN3rTcsMdsHqn zRfudENStCCMWoPzU!+|`00N2d*H>r3zv(BY*A%LdHYxwk|MO|_9ybWBy@aK)8pmKK zb3ESB-JW?yN%GLv%fN4s!Px)8F@GNNZ5xS8EjElo0G#?LB?^ zP@nBjZs11i?&LAxYfI>BI>4Y)vb{3l>VVHw6@E7GrMt-)+*1i2IC`9l{`k~%V8^?c ztsTorx=c4u8NcGsHKS~o&%wf{dp2)orG$Kf%}c}MTNIStrpG;fjwwyGt9H>Ysu1a| zD-WYF={@`&K9&WWHi)O-*2}cxVYf{qH@aF}T}7CiFbBhh>E~z6j4!ER9HLBRS(mR9 zO?R3^G>E{W4I+@P@#+ylV@@mePOauVJS9n`dZl(X>7rka$@zOw5`l%M{MY~8|L7j( z@t*em9(M{-{TTI@j|y0WFy@87t1)CzjhfU*qYL+j-ji*VbJ`OU#&pbK*NnD8wjbLG z?{_KUlTo^-K3w2D-r1TZF#2tzUE$=1FZD_JewvsK+<1sLilExZSg zyOnc@vFu_6Im!ex8S1R(h4dm_FGM7UbfLVR! zYxscATf80vW^SUg@Jv?SJ1sEfIh~Kq>x+nBV&SGuh|`jk@`~~*mIUJ0TKrVteQp8L z7*)v8*r@U~*j+QMXH>mztmkG9&sQ#rahsbBt2tJr`P4iTE$2?|X)@=HlG*xu0ZHY4 z$zitn#MU2e2O&vz>2=7j-6%>!RVaw0hpoY2d~y#~*5gKAW3ToTp81S=GL{@1!4a|s z{E59fg`714S6M`;LZ%bIAC$y3`KlnW3X10iC8#NN{;49OfFcu%xX=@OF0-^63}}v5 ziZ);jQjy;Dt6xn76@F&#F~9F|&ny+5fVAb1DcT-0iJ&Q(a>l1O<<_*T_SSl9SMqkW zQhRzUMxm_W>3wjA-!?;RXJDiYt7se>R{1&t7hMSK`!g(Vg~CbT@N~ zO106dnU{NAJ0TExQQ~e zXZo^PWj~dK{E#zdSqw@Iqq1h0wvA4^peg;_|=EDURA!X zrov?_nwFc6-9-JvC6CX@O&om6t=(8pp+C%)-$8Fh9*p`g4%vML@c7#5OtYC0Fm-2; z2iEeCkCLDy166OW5V|L=DErnkEaMOzonOof3M8TsiB#k+urZFX$0tFWYLN6`OJX}L zQ$U%^w-07&?g9kQW+0A4zTHnAQF^O~@kiOTaAffS#q%eiqJoS%q3Qc#CHb%ayZ`qz z|9RivYl-*-G13;o2MDSvs_DGW1*#_9M&wfcYVEJ<`q%6F*LD5t>aX5<7gd(3lo1a1 zOq$+@HNF};WUK8J$(8c5uQx^Lim>NEz4aeiYC%q>#eKETH$-Kh=CaXhOyo%c+=5PnWd)9A zsMVv)jgZMS($kA#C6;G_}cC#+3akSuqPVrQ=p_Atw4M+oFJHoF8%k#_B{ zPR1zb1R^yG8CnEJcC7B6e@dn@C8Pr%aRX~u5dcLl-!sEuT`jUudS_bnGn%0A9kb?p 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

    '; - } - ); - - add_filter( - 'template_include', - function( $template ) { - return get_stylesheet_directory() . '/static/no-timber.html'; - } - ); - return; -} - -/** - * Sets the directories (inside your theme) to find .twig files - */ -Timber::$dirname = array( 'templates', 'views' ); - -/** - * By default, Timber does NOT autoescape values. Want to enable Twig's autoescape? - * No prob! Just set this value to true - */ -Timber::$autoescape = false; - - -/** - * We're going to configure our theme inside of a subclass of Timber\Site - * You can move this to its own file and include here via php's include("MySite.php") - */ -class StarterSite extends Timber\Site { - /** Add timber support. */ - public function __construct() { - add_action( 'after_setup_theme', array( $this, 'theme_supports' ) ); - add_filter( 'timber/context', array( $this, 'add_to_context' ) ); - add_filter( 'timber/twig', array( $this, 'add_to_twig' ) ); - add_action( 'init', array( $this, 'register_post_types' ) ); - add_action( 'init', array( $this, 'register_taxonomies' ) ); - parent::__construct(); - } - /** This is where you can register custom post types. */ - public function register_post_types() { - - } - /** This is where you can register custom taxonomies. */ - public function register_taxonomies() { - - } - - /** This is where you add some context - * - * @param string $context context['this'] Being the Twig's {{ this }}. - */ - public function add_to_context( $context ) { - $context['foo'] = 'bar'; - $context['stuff'] = 'I am a value set in your functions.php file'; - $context['notes'] = 'These values are available everytime you call Timber::context();'; - $context['menu'] = new Timber\Menu(); - $context['site'] = $this; - return $context; - } - - public function theme_supports() { - // Add default posts and comments RSS feed links to head. - add_theme_support( 'automatic-feed-links' ); - - /* - * Let WordPress manage the document title. - * By adding theme support, we declare that this theme does not use a - * hard-coded tag in the document head, and expect WordPress to - * provide it for us. - */ - add_theme_support( 'title-tag' ); - - /* - * Enable support for Post Thumbnails on posts and pages. - * - * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ - */ - add_theme_support( 'post-thumbnails' ); - - /* - * Switch default core markup for search form, comment form, and comments - * to output valid HTML5. - */ - add_theme_support( - 'html5', - array( - 'comment-form', - 'comment-list', - 'gallery', - 'caption', - ) - ); - - /* - * Enable support for Post Formats. - * - * See: https://codex.wordpress.org/Post_Formats - */ - add_theme_support( - 'post-formats', - array( - 'aside', - 'image', - 'video', - 'quote', - 'link', - 'gallery', - 'audio', - ) - ); - - add_theme_support( 'menus' ); - } - - /** BEM function to pass in bem style classes. - * - */ - public function bem($context, $base_class, $modifiers = array(), $blockname = '', $extra = array()) { - $classes = []; - - // Add the ability to pass an object as the one and only argument. - if (is_object($base_class) || is_array($base_class)) { - $object = (object) $base_class; - unset($base_class); - $map = [ - 'block' => 'base_class', - 'element' => 'blockname', - 'modifiers' => 'modifiers', - 'extra' => 'extra', - ]; - foreach ($map as $object_key => $arg_key) { - if (isset($object->$object_key)) { - $$arg_key = $object->$object_key; - } - } - } - - // Ensure array arguments. - if (!empty($modifiers) && !is_array($modifiers)) { - $modifiers = [$modifiers]; - } - if (!is_array($extra)) { - $extra = [$extra]; - } - - // If using a blockname to override default class. - if ($blockname) { - // Set blockname class. - $classes[] = $blockname . '__' . $base_class; - // Set blockname--modifier classes for each modifier. - if (isset($modifiers) && is_array($modifiers)) { - foreach ($modifiers as $modifier) { - $classes[] = $blockname . '__' . $base_class . '--' . $modifier; - }; - } - } - // If not overriding base class. - else { - // Set base class. - $classes[] = $base_class; - // Set base--modifier class for each modifier. - if (isset($modifiers) && is_array($modifiers)) { - foreach ($modifiers as $modifier) { - $classes[] = $base_class . '--' . $modifier; - }; - } - } - // If extra non-BEM classes are added. - if (isset($extra) && is_array($extra)) { - foreach ($extra as $extra_class) { - $classes[] = $extra_class; - }; - } - $attributes = 'class="' . implode(' ', $classes) . '"'; - return $attributes; - } - - /** - * Add Attributes function to pass in multiple attributes including bem style classes. - */ - public function add_attributes( $context, $additional_attributes = [] ) { - $attributes = []; - - if ( ! empty( $additional_attributes ) ) { - foreach ( $additional_attributes as $key => $value ) { - // If there are multiple items in $value as array (e.g., class: ['one', 'two']). - if ( is_array( $value ) ) { - $attributes[] = ( $key . '="' . implode( ' ', $value ) . '"' ); - } else { - // Handle bem() output (pass in exactly the result). - if ( strpos( $value, '=' ) !== false ) { - $attributes[] = $value; - } else { - $attributes[] = ( $key . '="' . $value . '"' ); - } - } - } - } - - return implode( ' ', $attributes ); - } - - - /** This is where you can add your own functions to twig. - * - * @param string $twig get extension. - */ - public function add_to_twig( $twig ) { - $twig->addExtension( new Twig\Extension\StringLoaderExtension() ); - $twig->addFunction( new Twig_SimpleFunction('bem', array($this, 'bem'), array('needs_context' => true), array('is_safe' => array('html'))) ); - $twig->addFunction( new Twig_SimpleFunction('add_attributes', array($this, 'add_attributes'), array('needs_context' => true), array('is_safe' => array('html'))) ); - return $twig; - } - -} - -new StarterSite(); - -// Namespaces -add_filter('timber/loader/loader', function($loader){ - $loader->addPath(__DIR__ . "/../components/01-atoms", "atoms"); - $loader->addPath(__DIR__ . "/../components/02-molecules", "molecules"); - $loader->addPath(__DIR__ . "/../components/03-organisms", "organisms"); - $loader->addPath(__DIR__ . "/../components/04-templates", "templates"); - return $loader; -}); diff --git a/index.php b/index.php deleted file mode 100755 index 804e555..0000000 --- a/index.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * The main template file - * This is the most generic template file in a WordPress theme - * and one of the two required files for a theme (the other being style.css). - * It is used to display a page when nothing more specific matches a query. - * E.g., it puts together the home page when no home.php file exists - * - * Methods for TimberHelper can be found in the /lib sub-directory - * - * @package WordPress - * @subpackage Timber - * @since Timber 0.1 - */ - -$context = Timber::context(); -$context['posts'] = new Timber\PostQuery(); -$context['foo'] = 'bar'; -$templates = array( 'index.twig' ); -if ( is_home() ) { - array_unshift( $templates, 'front-page.twig', 'home.twig' ); -} -Timber::render( $templates, $context ); diff --git a/package.json b/package.json deleted file mode 100644 index 93f1e65..0000000 --- a/package.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "name": "emulsify-wordpress-theme", - "version": "1.0.0-alpha.5", - "description": "Emulsify is a Design System that works with WordPress, Drupal, React, and more.", - "author": "Dean Oest <randy@fourkitchens.com>", - "license": "GPL-2.0-only", - "dependencies": { - "@babel/core": "^7.13.10", - "@babel/preset-env": "^7.13.10", - "@storybook/addon-a11y": "6.1.21", - "@storybook/addon-actions": "6.1.21", - "@storybook/addon-links": "6.1.21", - "@storybook/addon-viewport": "^6.1.21", - "@storybook/addons": "6.1.21", - "@storybook/react": "6.1.21", - "@storybook/storybook-deployer": "^2.8.7", - "add-attributes-twig-extension": "^0.1.0", - "autoprefixer": "^10.2.5", - "babel-eslint": "^10.0.3", - "babel-loader": "^8.2.2", - "babel-preset-minify": "^0.5.0", - "bem-twig-extension": "^0.1.1", - "breakpoint-sass": "^2.7.1", - "clean-webpack-plugin": "^3.0.0", - "concurrently": "^6.0.0", - "css-loader": "^5.1.3", - "eslint": "^7.22.0", - "eslint-config-airbnb": "^18.2.1", - "eslint-loader": "^4.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-react": "^7.22.0", - "file-loader": "^6.2.0", - "fs": "^0.0.1-security", - "glob": "^7.1.4", - "imagemin-webpack-plugin": "^2.4.2", - "js-yaml-loader": "^1.2.2", - "mini-css-extract-plugin": "^1.3.9", - "node-sass": "^5.0.0", - "node-sass-glob-importer": "^5.3.2", - "normalize.css": "^8.0.1", - "postcss": "^8.2.8", - "postcss-custom-properties": "^10.0.0", - "postcss-loader": "^4.1.0", - "ramda": "^0.27.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "sass-loader": "^10.1.0", - "style-loader": "^2.0.0", - "stylelint": "^13.12.0", - "stylelint-config-prettier": "^8.0.2", - "stylelint-config-standard": "^21.0.0", - "stylelint-webpack-plugin": "^2.1.1", - "svg-sprite-loader": "^6.0.0", - "twig": "~1.15.4", - "twig-loader": "https://github.com/AmazeeLabs/twig-loader", - "webpack": "4.44.2", - "webpack-cli": "^4.5.0", - "webpack-merge": "^5.7.3" - }, - "scripts": { - "a11y": "npm run build-storybook && ./scripts/a11y.js -r", - "setup": "composer install && yarn", - "lint": "eslint ./components", - "storybook": "start-storybook --ci -s ./dist,./images -p 6006", - "build-storybook": "npm run build && build-storybook -s ./dist,./images -o .out", - "deploy-storybook": "storybook-to-ghpages -o .out", - "webpack": "webpack --watch --config ./webpack/webpack.dev.js", - "build": "webpack --config ./webpack/webpack.prod.js", - "develop": "concurrently --raw \"npm run webpack\" \"npm run storybook\"" - }, - "devDependencies": { - "core-js": "3.9.1", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-prettier": "^3.3.1", - "open-cli": "^6.0.1", - "pa11y": "^5.3.0", - "prettier": "2.2.1" - } -} diff --git a/page.php b/page.php deleted file mode 100755 index 2918bd3..0000000 --- a/page.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php -/** - * The template for displaying all pages. - * - * This is the template that displays all pages by default. - * Please note that this is the WordPress construct of pages - * and that other 'pages' on your WordPress site will use a - * different template. - * - * To generate specific templates for your pages you can use: - * /mytheme/templates/page-mypage.twig - * (which will still route through this PHP file) - * OR - * /mytheme/page-mypage.php - * (in which case you'll want to duplicate this file and save to the above path) - * - * Methods for TimberHelper can be found in the /lib sub-directory - * - * @package WordPress - * @subpackage Timber - * @since Timber 0.1 - */ - -$context = Timber::context(); - -$timber_post = new Timber\Post(); -$context['post'] = $timber_post; -Timber::render( array( 'page-' . $timber_post->post_name . '.twig', 'page.twig' ), $context ); diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index e7f21fc..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,20 +0,0 @@ -<phpunit - bootstrap="tests/bootstrap.php" - backupGlobals="false" - colors="true" - convertErrorsToExceptions="true" - convertNoticesToExceptions="true" - convertWarningsToExceptions="true" - > - <testsuites> - <testsuite> - <directory prefix="test-" suffix=".php">./tests/</directory> - </testsuite> - <!-- The suite below HAS to be last to run, - as it includes a test that sets some const and would contaminate - the other tests as well. --> - <testsuite> - <directory prefix="testX-" suffix=".php">./tests/</directory> - </testsuite> - </testsuites> -</phpunit> diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 3e11de2..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,7 +0,0 @@ -// postcssCustomProperties only needed for IE11 - remove if unnecessary for your project. -const postcssCustomProperties = require('postcss-custom-properties'); -const autoPrefixer = require('autoprefixer'); - -module.exports = { - plugins: [postcssCustomProperties(), autoPrefixer()], -}; diff --git a/prettier.config.js b/prettier.config.js deleted file mode 100644 index de2f53c..0000000 --- a/prettier.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - singleQuote: true, - trailingComma: 'all', -}; diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index dce03e0b9dbabc7054524c879dacd3ed964f9882..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386624 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 LNkvXXu0mjfORFW5 diff --git a/scripts/a11y.js b/scripts/a11y.js deleted file mode 100755 index 5af737b..0000000 --- a/scripts/a11y.js +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env node -/** - * @file a11y.js - * Contains a script that, when executed, will execute a11y linting tools - * against the storybook build. - */ - -const R = require('ramda'); -const path = require('path'); -const pa11y = require('pa11y'); -const chalk = require('chalk'); -const { - storybookBuildDir, - pa11y: pa11yConfig, - ignore, - components, -} = require('../a11y.config.js'); - -const STORYBOOK_BUILD_DIR = path.resolve(__dirname, '../', storybookBuildDir); -const STORYBOOK_IFRAME = path.join(STORYBOOK_BUILD_DIR, 'iframe.html'); - -const severityToColor = R.cond([ - [R.equals('error'), R.always('red')], - [R.equals('warning'), R.always('yellow')], - [R.equals('notice'), R.always('blue')], -]); - -const issueIsValid = ({ code, runnerExtras: { description } }) => - ignore.codes.includes(code) || ignore.descriptions.includes(description) - ? false - : true; - -const logIssue = ({ type: severity, message, context, selector }) => { - console.log(` - severity: ${chalk[severityToColor(severity)](severity)} - message: ${message} - context: ${context} - selector: ${selector} - `); -}; - -const logReport = ({ issues, pageUrl }) => { - const validIssues = issues.filter(issueIsValid); - const hasIssues = validIssues.length > 0; - - if (hasIssues) { - console.log(chalk.red(`Issues found in component: ${pageUrl}`)); - validIssues.map(logIssue); - } else { - console.log(chalk.green(`No issues found in component: ${pageUrl}`)); - } - - return hasIssues; -}; - -const lintComponent = async (name) => - pa11y(`${STORYBOOK_IFRAME}?id=${name}`, { - includeNotices: true, - includeWarnings: true, - runners: ['axe'], - ...pa11yConfig, - }); - -const lintReportAndExit = R.pipe( - R.map(lintComponent), - (p) => Promise.all(p), - R.andThen( - R.pipe( - R.map(logReport), - R.reject(R.equals(false)), - R.unless(R.isEmpty, () => process.exit(1)), - ), - ), -); - -// Only perform linting/reporting when instructed. -/* istanbul ignore next */ -if (R.pathEq(['argv', 2], '-r')(process)) { - lintReportAndExit(components); -} - -module.exports = { - severityToColor, - issueIsValid, - logIssue, - logReport, - lintComponent, - lintReportAndExit, -}; diff --git a/scripts/a11y.test.js b/scripts/a11y.test.js deleted file mode 100644 index fa20520..0000000 --- a/scripts/a11y.test.js +++ /dev/null @@ -1,157 +0,0 @@ -const mockExit = jest - .spyOn(global.process, 'exit') - .mockImplementation(() => {}); -jest.mock('pa11y', () => jest.fn()); -jest.spyOn(global.console, 'log').mockImplementation(() => {}); -const pa11y = require('pa11y'); -const path = require('path'); -const { - severityToColor, - issueIsValid, - logIssue, - logReport, - lintComponent, - lintReportAndExit, -} = require('./a11y'); -const { - ignore, - storybookBuildDir, - pa11y: pa11yConfig, -} = require('../a11y.config.js'); - -const STORYBOOK_BUILD_DIR = path.resolve(__dirname, '../', storybookBuildDir); -const STORYBOOK_IFRAME = path.join(STORYBOOK_BUILD_DIR, 'iframe.html'); - -pa11y.mockResolvedValue('very official report'); - -describe('a11y', () => { - beforeEach(() => { - global.console.log.mockClear(); - global.process.exit.mockClear(); - }); - it('can map axe issue severity to the correct chalk color', () => { - expect.assertions(3); - expect(severityToColor('error')).toBe('red'); - expect(severityToColor('warning')).toBe('yellow'); - expect(severityToColor('notice')).toBe('blue'); - }); - - it('identifies invalid issues based on the code or the description', () => { - expect.assertions(3); - expect( - issueIsValid({ - code: ignore.codes[0], - runnerExtras: {}, - }), - ).toBe(false); - expect( - issueIsValid({ - runnerExtras: { - description: ignore.descriptions[0], - }, - }), - ).toBe(false); - expect(issueIsValid({ code: 'chicken', runnerExtras: {} })).toBe(true); - }); - - it('can use an axe issue to generate a single log message about the issue', () => { - expect.assertions(1); - logIssue({ - type: 'error', - message: 'this chicken is not fried enough.', - context: 'https://example.com', - selector: 'kfc > popeyes > .chicken', - }); - expect(global.console.log.mock.calls[0][0]).toMatchInlineSnapshot(` - " - severity: error - message: this chicken is not fried enough. - context: https://example.com - selector: kfc > popeyes > .chicken - " - `); - }); - - it('can log a whole axe report', () => { - const report = { - issues: [ - { - type: 'error', - message: 'this pizza is too soggy', - context: 'https://example.com', - selector: 'pizza > .hut', - runnerExtras: {}, - }, - { - type: 'error', - message: 'this pasta is undercooked', - context: 'https://example.com', - selector: 'olive > .garden', - runnerExtras: {}, - }, - ], - pageUrl: 'https://example/component.html', - }; - expect(logReport(report)).toBe(true); - expect(global.console.log.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "Issues found in component: https://example/component.html", - ], - Array [ - " - severity: error - message: this pizza is too soggy - context: https://example.com - selector: pizza > .hut - ", - ], - Array [ - " - severity: error - message: this pasta is undercooked - context: https://example.com - selector: olive > .garden - ", - ], - ] - `); - }); - - it('logs about a component having no issue if a report comes back empty', () => { - expect(logReport({ issues: [], pageUrl: 'papa-johns' })).toBe(false); - expect(global.console.log.mock.calls[0][0]).toMatchInlineSnapshot( - `"No issues found in component: papa-johns"`, - ); - }); - - it('can call pa11y with the full path to a component', async () => { - expect.assertions(2); - await expect(lintComponent('chicken-strips')).resolves.toBe( - 'very official report', - ); - expect(pa11y).toHaveBeenCalledWith( - `${STORYBOOK_IFRAME}?id=chicken-strips`, - pa11yConfig, - ); - }); - - it('runs linter, reports on issues, and exits with code "1" if valid issues are found', async () => { - expect.assertions(1); - pa11y.mockResolvedValueOnce({ - issues: [ - { - type: 'error', - message: 'these 7 layer supreme burritos do not taste that good', - context: 'https://example.com', - selector: 'taco > bell > .burrito', - runnerExtras: {}, - }, - ], - pageUrl: '/path/to/taco-bell', - }); - - await lintReportAndExit(['taco-bell']); - expect(global.process.exit).toHaveBeenCalledWith(1); - }); -}); diff --git a/search.php b/search.php deleted file mode 100755 index 600a6ab..0000000 --- a/search.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/** - * Search results page - * - * Methods for TimberHelper can be found in the /lib sub-directory - * - * @package WordPress - * @subpackage Timber - * @since Timber 0.1 - */ - -$templates = array( 'search.twig', 'archive.twig', 'index.twig' ); - -$context = Timber::context(); -$context['title'] = 'Search results for ' . get_search_query(); -$context['posts'] = new Timber\PostQuery(); - -Timber::render( $templates, $context ); diff --git a/sidebar.php b/sidebar.php deleted file mode 100644 index 63a3492..0000000 --- a/sidebar.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php -/** - * The Template for the sidebar containing the main widget area - * - * @package WordPress - * @subpackage Timber - */ - -Timber::render( array( 'sidebar.twig' ), $data ); diff --git a/single.php b/single.php deleted file mode 100755 index 95d1d47..0000000 --- a/single.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -/** - * The Template for displaying all single posts - * - * Methods for TimberHelper can be found in the /lib sub-directory - * - * @package WordPress - * @subpackage Timber - * @since Timber 0.1 - */ - -$context = Timber::context(); -$timber_post = Timber::query_post(); -$context['post'] = $timber_post; - -if ( post_password_required( $timber_post->ID ) ) { - Timber::render( 'single-password.twig', $context ); -} else { - Timber::render( array( 'single-' . $timber_post->ID . '.twig', 'single-' . $timber_post->post_type . '.twig', 'single-' . $timber_post->slug . '.twig', 'single.twig' ), $context ); -} diff --git a/static/no-timber.html b/static/no-timber.html deleted file mode 100644 index b99c04b..0000000 --- a/static/no-timber.html +++ /dev/null @@ -1,10 +0,0 @@ -<!doctype html> -<html lang="en"> - <head> - <title>Timber not active - - - -

    Timber not activated

    - - diff --git a/static/site.js b/static/site.js deleted file mode 100755 index 6655bd0..0000000 --- a/static/site.js +++ /dev/null @@ -1,5 +0,0 @@ -jQuery( document ).ready( function( $ ) { - - // Your JavaScript goes here - -}); \ No newline at end of file diff --git a/style.css b/style.css deleted file mode 100644 index 35ae8b9..0000000 --- a/style.css +++ /dev/null @@ -1,15 +0,0 @@ -/* -Theme Name: Emulsify WordPress Theme -Theme URI: https://github.com/emulsify-ds/emulsify-wordpress-theme -Author: Four Kitchens -Author URI: https://www.emulsify.info/ -Description: Starter theme for Emulsify in WordPress. -Version: 1.0 -License: GNU General Public License v2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html -Tags: emulsify, design system, components -Text Domain: emulsify - -This theme, like WordPress, is licensed under the GPL. -Use it to make something cool, have fun, and share what you've learned with others. -*/ diff --git a/templates/404.twig b/templates/404.twig deleted file mode 100644 index 9105453..0000000 --- a/templates/404.twig +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} - Sorry, we couldn't find what you're looking for. -{% endblock %} diff --git a/templates/archive.twig b/templates/archive.twig deleted file mode 100644 index 7e79671..0000000 --- a/templates/archive.twig +++ /dev/null @@ -1,9 +0,0 @@ -{# This file demonstrates using most of the index.twig template and modifying - just a small part. See `search.twig` for an example of another approach #} - -{% extends "index.twig" %} - -{% block content %} -

    This is my archive

    - {{ parent() }} -{% endblock %} diff --git a/templates/author.twig b/templates/author.twig deleted file mode 100644 index 9b35dc9..0000000 --- a/templates/author.twig +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} - {% for post in posts %} - {% include ["tease-"~post.post_type~".twig", "tease.twig"] %} - {% endfor %} -{% endblock %} diff --git a/templates/comment-form.twig b/templates/comment-form.twig deleted file mode 100644 index b2b0a2e..0000000 --- a/templates/comment-form.twig +++ /dev/null @@ -1,28 +0,0 @@ -
    -

    Add comment

    -
    - {% if user %} - - - - {% else %} - - - - {% endif %} - - - - - -

    Your comment will be revised by the site if needed.

    -
    -
    diff --git a/templates/comment.twig b/templates/comment.twig deleted file mode 100644 index 40ce11c..0000000 --- a/templates/comment.twig +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    {{comment.author.name}} says
    -
    {{comment.comment_content|wpautop}}
    - -
    - - - {% include "comment-form.twig" %} - - - {% if post.comments %} -

    replies

    -
    - {% for cmt in comment.children %} - {% include "comment.twig" with {comment:cmt} %} - {% endfor %} -
    - {% endif %} - -
    -
    \ No newline at end of file diff --git a/templates/index.twig b/templates/index.twig deleted file mode 100644 index 568041e..0000000 --- a/templates/index.twig +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} - {% for post in posts %} - {% include ['tease-'~post.post_type~'.twig', 'tease.twig'] %} - {% endfor %} - - {% include 'partial/pagination.twig' with { pagination: posts.pagination({show_all: false, mid_size: 3, end_size: 2}) } %} -{% endblock %} diff --git a/templates/menu.twig b/templates/menu.twig deleted file mode 100644 index 0650f61..0000000 --- a/templates/menu.twig +++ /dev/null @@ -1,10 +0,0 @@ -{% if menu %} -
      - {% for item in items %} -
    • - {{ item.title }} - {% include "menu.twig" with {'items': item.children} %} -
    • - {% endfor %} -
    -{% endif %} diff --git a/templates/page-plugin.twig b/templates/page-plugin.twig deleted file mode 100644 index 913b0f5..0000000 --- a/templates/page-plugin.twig +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} -
    - {{content}} -
    -{% endblock %} diff --git a/templates/page.twig b/templates/page.twig deleted file mode 100644 index 3278c21..0000000 --- a/templates/page.twig +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} -
    -
    -
    -

    {{post.title}}

    -
    - {{post.content}} -
    -
    -
    -
    -{% endblock %} diff --git a/templates/partial/pagination.twig b/templates/partial/pagination.twig deleted file mode 100644 index fb7ccf8..0000000 --- a/templates/partial/pagination.twig +++ /dev/null @@ -1 +0,0 @@ -{% include "@molecules/pager/pager.twig" %} diff --git a/templates/search.twig b/templates/search.twig deleted file mode 100644 index 220f905..0000000 --- a/templates/search.twig +++ /dev/null @@ -1,13 +0,0 @@ -{# see `archive.twig` for an alternative strategy of extending templates #} -{% extends "@templates/_default.twig" %} - -{% block content %} - {# see `base.twig:27` for where this block's content will be inserted #} -
    - {% for post in posts %} - {% include ['tease-'~post.post_type~'.twig', 'tease.twig'] %} - {% endfor %} - - {% include 'partial/pagination.twig' with { pagination: posts.pagination({show_all: false, mid_size: 3, end_size: 2}) } %} -
    -{% endblock %} diff --git a/templates/sidebar.twig b/templates/sidebar.twig deleted file mode 100644 index ae3e17b..0000000 --- a/templates/sidebar.twig +++ /dev/null @@ -1 +0,0 @@ -Sidebar in Timber. Add HTML to your hearts content. \ No newline at end of file diff --git a/templates/single-password.twig b/templates/single-password.twig deleted file mode 100644 index 1a87e71..0000000 --- a/templates/single-password.twig +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} -
    - - - -
    -{% endblock %} diff --git a/templates/single.twig b/templates/single.twig deleted file mode 100644 index 6dcdc41..0000000 --- a/templates/single.twig +++ /dev/null @@ -1,45 +0,0 @@ -{% extends "@templates/_default.twig" %} - -{% block content %} -
    -
    - -
    -

    {{ post.title }}

    - {% if user.can('edit_posts') %} - {% include "@atoms/links/link/link.twig" with { - link_base_class: 'button', - link_url: site.url ~ '/wp-admin/post.php?post=' ~ post.id ~ '&action=edit', - link_content: "Edit Post", - } %} - {% endif %} -

    - By {{ post.author.name }} -

    -
    - {{post.content}} -
    -
    - - -
    - -
    - {% if post.comments %} -

    comments

    - {% for cmt in post.comments %} - {% include "comment.twig" with {comment:cmt} %} - {% endfor %} - {% endif %} -
    - - {% if post.comment_status == "closed" %} -

    comments for this post are closed

    - {% else %} - - {% include "comment-form.twig" %} - {% endif %} -
    -
    -
    -{% endblock %} diff --git a/templates/tease-post.twig b/templates/tease-post.twig deleted file mode 100644 index 420566c..0000000 --- a/templates/tease-post.twig +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "tease.twig" %} - -{% block content %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_level: 2, - heading: post.title, - heading_url: post.link, - } %} -

    {{post.preview.length(25)}}

    - {% if post.thumbnail.src %} - - {% endif %} -{% endblock %} diff --git a/templates/tease.twig b/templates/tease.twig deleted file mode 100644 index 2e2fe15..0000000 --- a/templates/tease.twig +++ /dev/null @@ -1,13 +0,0 @@ -
    - {% block content %} - {% include "@atoms/text/headings/_heading.twig" with { - heading_level: 2, - heading: post.title, - heading_url: post.link, - } %} -

    {{post.preview}}

    - {% if post.get_thumbnail %} - - {% endif %} - {% endblock %} -
    diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 8eaa39f..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,22 +0,0 @@ -assertTrue(is_array($context)); - } - - function testFunctionsPHP() { - $context = Timber::context(); - $this->assertEquals('StarterSite', get_class($context['site'])); - $this->assertTrue(current_theme_supports('post-thumbnails')); - $this->assertEquals('bar', $context['foo']); - } - - function testLoading() { - $str = Timber::compile('tease.twig'); - $this->assertStringStartsWith('
    ', $str); - $this->assertStringEndsWith('
    ', $str); - } - - /** - * Helper test to output current twig version - */ - function testTwigVersion() { - $str = Timber::compile_string("{{ constant('Twig_Environment::VERSION') }}"); - //error_log('Twig version = '.$str); - } - - function testTwigFilter() { - $str = Timber::compile_string('{{ "foo" | myfoo }}'); - $this->assertEquals('foo bar!', $str); - } - - static function _setupStarterTheme(){ - $dest = WP_CONTENT_DIR . '/themes/' . basename( dirname( dirname( __FILE__ ) ) ); - $src = realpath( __DIR__ . '/../../' . basename( dirname( dirname( __FILE__ ) ) ) ); - if ( is_dir($src) && !file_exists($dest) ) { - symlink($src, $dest); - } - } - - - } diff --git a/webpack/.stylelintrc b/webpack/.stylelintrc deleted file mode 100644 index 055dcfd..0000000 --- a/webpack/.stylelintrc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": [ - "stylelint-config-standard", - "stylelint-config-prettier" - ], - "rules": { - "at-rule-no-unknown": [ true, { - "ignoreAtRules": [ - "include", - "mixin", - "extend", - "if", - "else", - "each", - "content", - "import", - "function", - "return" - ] - }], - "no-descending-specificity": null, - "function-calc-no-invalid": null - } -} diff --git a/webpack/app.js b/webpack/app.js deleted file mode 100644 index 824fcb3..0000000 --- a/webpack/app.js +++ /dev/null @@ -1 +0,0 @@ -// Empty (needed for webpack) diff --git a/webpack/css.js b/webpack/css.js deleted file mode 100644 index f470d23..0000000 --- a/webpack/css.js +++ /dev/null @@ -1 +0,0 @@ -import '../components/style.scss'; diff --git a/webpack/loaders.js b/webpack/loaders.js deleted file mode 100644 index c5d50c9..0000000 --- a/webpack/loaders.js +++ /dev/null @@ -1,61 +0,0 @@ -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const globImporter = require('node-sass-glob-importer'); - -const JSLoader = { - test: /^(?!.*\.(stories|component)\.js$).*\.js$/, - exclude: /node_modules/, - loader: 'babel-loader', -}; - -const ImageLoader = { - test: /\.(svg)$/i, - exclude: /icons\/.*\.svg$/, - loader: 'file-loader', -}; - -const CSSLoader = { - test: /\.s[ac]ss$/i, - exclude: /node_modules/, - use: [ - MiniCssExtractPlugin.loader, - { - loader: 'css-loader', - options: { - sourceMap: true, - url: false, - }, - }, - { - loader: 'postcss-loader', - options: { - sourceMap: true, - }, - }, - { - loader: 'sass-loader', - options: { - sourceMap: true, - sassOptions: { - importer: globImporter(), - outputStyle: 'compressed', - }, - }, - }, - ], -}; - -const SVGSpriteLoader = { - test: /icons\/.*\.svg$/, // your icons directory - loader: 'svg-sprite-loader', - options: { - extract: true, - spriteFilename: '../dist/icons.svg', - }, -}; - -module.exports = { - JSLoader, - CSSLoader, - SVGSpriteLoader, - ImageLoader, -}; diff --git a/webpack/plugins.js b/webpack/plugins.js deleted file mode 100644 index d391d4d..0000000 --- a/webpack/plugins.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -const path = require('path'); -const webpack = require('webpack'); -const { CleanWebpackPlugin } = require('clean-webpack-plugin'); -const _MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const _ImageminPlugin = require('imagemin-webpack-plugin').default; -const _SpriteLoaderPlugin = require('svg-sprite-loader/plugin'); -const glob = require('glob'); - -const imagePath = path.resolve(__dirname, '../images'); - -const MiniCssExtractPlugin = new _MiniCssExtractPlugin({ - filename: 'style.css', - chunkFilename: '[id].css', -}); - -const ImageminPlugin = new _ImageminPlugin({ - disable: process.env.NODE_ENV !== 'production', - externalImages: { - context: imagePath, - sources: glob.sync(path.resolve(imagePath, '**/*.{png,jpg,gif,svg}')), - destination: imagePath, - }, -}); - -const SpriteLoaderPlugin = new _SpriteLoaderPlugin({ - plainSprite: true, -}); - -const ProgressPlugin = new webpack.ProgressPlugin(); - -module.exports = { - ProgressPlugin, - MiniCssExtractPlugin, - ImageminPlugin, - SpriteLoaderPlugin, - CleanWebpackPlugin: new CleanWebpackPlugin({ - cleanOnceBeforeBuildPatterns: ['!*.{png,jpg,gif,svg}'], - cleanAfterEveryBuildPatterns: ['remove/**', '!js', '!*.{png,jpg,gif,svg}'], - }), -}; diff --git a/webpack/svgSprite.js b/webpack/svgSprite.js deleted file mode 100644 index b5873bf..0000000 --- a/webpack/svgSprite.js +++ /dev/null @@ -1,5 +0,0 @@ -function requireAll(r) { - r.keys().forEach(r); -} - -requireAll(require.context('../images/', true, /\.svg$/)); diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js deleted file mode 100644 index ddd8960..0000000 --- a/webpack/webpack.common.js +++ /dev/null @@ -1,54 +0,0 @@ -const path = require('path'); -const glob = require('glob'); -const loaders = require('./loaders'); -const plugins = require('./plugins'); - -const webpackDir = path.resolve(__dirname); -const rootDir = path.resolve(__dirname, '..'); -const distDir = path.resolve(rootDir, 'dist'); - -function getEntries(pattern) { - const entries = {}; - - glob.sync(pattern).forEach((file) => { - const filePath = file.split('components/')[1]; - const newfilePath = `js/${filePath.replace('.js', '')}`; - entries[newfilePath] = file; - }); - - entries.svgSprite = path.resolve(webpackDir, 'svgSprite.js'); - entries.css = path.resolve(webpackDir, 'css.js'); - - return entries; -} - -module.exports = { - stats: { - errorDetails: true, - }, - entry: getEntries( - path.resolve( - rootDir, - 'components/**/!(*.stories|*.component|*.min|*.test).js', - ), - ), - module: { - rules: [ - loaders.CSSLoader, - loaders.SVGSpriteLoader, - loaders.ImageLoader, - loaders.JSLoader, - ], - }, - plugins: [ - plugins.MiniCssExtractPlugin, - plugins.ImageminPlugin, - plugins.SpriteLoaderPlugin, - plugins.ProgressPlugin, - plugins.CleanWebpackPlugin, - ], - output: { - path: distDir, - filename: '[name].js', - }, -}; diff --git a/webpack/webpack.dev.js b/webpack/webpack.dev.js deleted file mode 100644 index ee7ea0c..0000000 --- a/webpack/webpack.dev.js +++ /dev/null @@ -1,7 +0,0 @@ -const { merge } = require('webpack-merge'); -const common = require('./webpack.common.js'); - -module.exports = merge(common, { - mode: 'development', - devtool: 'source-map', -}); diff --git a/webpack/webpack.prod.js b/webpack/webpack.prod.js deleted file mode 100644 index ed0670e..0000000 --- a/webpack/webpack.prod.js +++ /dev/null @@ -1,6 +0,0 @@ -const { merge } = require('webpack-merge'); -const common = require('./webpack.common.js'); - -module.exports = merge(common, { - mode: 'production', -}); From 80c26459ea960282bff1107fb336279239f79eb3 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Wed, 7 May 2025 16:55:32 -0500 Subject: [PATCH 02/55] feat: init commit of rebuilt theme that is inline with other base themes --- .browserslistrc | 8 + .editorconfig | 5 + .github/ISSUE_TEMPLATE.md | 11 + .github/PULL_REQUEST_TEMPLATE.md | 14 + .github/stale.yml | 16 + .github/workflows/addtoprojects.yml | 22 + .github/workflows/contributors.yml | 24 + .github/workflows/dev-sync.yml | 29 + .github/workflows/semantic-release.yml | 30 + .gitignore | 20 + .husky/pre-commit | 4 + .npmrc | 1 + .nvmrc | 1 + 404.php | 11 + CODE_OF_CONDUCT.md | 56 + LICENSE | 339 + LICENSE.txt | 339 + README.md | 68 + archive.php | 39 + author.php | 18 + commitlint.config.js | 3 + composer.json | 29 + functions.php | 124 + husky.config.js | 5 + index.php | 27 + package-lock.json | 7896 +++++++++++++++++ package.json | 50 + page.php | 17 + release.config.js | 11 + screenshot.png | Bin 0 -> 11781 bytes search.php | 18 + single.php | 20 + src/StarterSite.php | 186 + style.css | 10 + templates/404.twig | 5 + templates/archive.twig | 15 + templates/author.twig | 7 + templates/index.twig | 16 + templates/layouts/base.twig | 43 + templates/page.twig | 12 + templates/partials/comment-form.twig | 32 + templates/partials/comment.twig | 19 + templates/partials/footer.twig | 1 + templates/partials/head.twig | 9 + templates/partials/menu.twig | 12 + templates/partials/pagination.twig | 44 + templates/partials/tease-post.twig | 16 + templates/partials/tease.twig | 9 + templates/search.twig | 18 + templates/single-password.twig | 17 + templates/single.twig | 37 + whisk/assets/audio/.gitkeep | 0 whisk/assets/fonts/.gitkeep | 0 whisk/assets/icons/.gitkeep | 0 whisk/assets/icons/angle-down.svg | 6 + whisk/assets/icons/arrowLeft.svg | 1 + whisk/assets/icons/arrowRight.svg | 16 + whisk/assets/icons/chevron-down.svg | 4 + whisk/assets/icons/chevronLeft.svg | 1 + whisk/assets/icons/chevronRight.svg | 1 + whisk/assets/icons/circle-info.svg | 4 + whisk/assets/icons/close-alt.svg | 1 + whisk/assets/icons/close.svg | 3 + whisk/assets/icons/crisisAlert.svg | 8 + whisk/assets/icons/download.svg | 27 + whisk/assets/icons/expand.svg | 1 + whisk/assets/icons/facebook.svg | 3 + whisk/assets/icons/home.svg | 4 + whisk/assets/icons/instagram.svg | 4 + whisk/assets/icons/linkedin.svg | 5 + whisk/assets/icons/list.svg | 3 + whisk/assets/icons/menu-close.svg | 5 + whisk/assets/icons/menu.svg | 3 + whisk/assets/icons/pause.svg | 1 + whisk/assets/icons/play.svg | 10 + whisk/assets/icons/programs-all.svg | 8 + whisk/assets/icons/programs-by-category.svg | 12 + whisk/assets/icons/programs-switch-view.svg | 4 + whisk/assets/icons/search.svg | 3 + whisk/assets/icons/starburst.svg | 23 + whisk/assets/icons/submenu.svg | 3 + whisk/assets/icons/triangle-exclamation.svg | 4 + whisk/assets/icons/twitter.svg | 3 + whisk/assets/icons/xmark.svg | 4 + whisk/assets/icons/youtube.svg | 3 + whisk/assets/images/.gitkeep | 0 whisk/assets/video/.gitkeep | 0 whisk/config/.env.example | 4 + whisk/config/configma.json | 13 + whisk/config/emulsify-core/.prettierignore | 4 + .../config/emulsify-core/eslintrc.config.json | 17 + .../emulsify-core/prettierrc.config.json | 4 + .../emulsify-core/storybook/example.theme.js | 37 + whisk/config/emulsify-core/storybook/main.js | 37 + .../config/emulsify-core/storybook/preview.js | 23 + .../emulsify-core/stylelintrc.config.json | 8 + .../webpack/example.babel.config.js | 18 + .../webpack/example.postcss.config.js | 5 + whisk/config/sd.config.json | 26 + whisk/functions.php | 18 + whisk/includes/site.php | 133 + whisk/package.json | 45 + whisk/project.emulsify.json | 10 + whisk/screenshot.png | Bin 0 -> 11781 bytes whisk/style.css | 7 + 105 files changed, 10350 insertions(+) create mode 100644 .browserslistrc create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/stale.yml create mode 100644 .github/workflows/addtoprojects.yml create mode 100644 .github/workflows/contributors.yml create mode 100644 .github/workflows/dev-sync.yml create mode 100644 .github/workflows/semantic-release.yml create mode 100644 .gitignore create mode 100755 .husky/pre-commit create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 404.php create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 archive.php create mode 100644 author.php create mode 100644 commitlint.config.js create mode 100644 composer.json create mode 100644 functions.php create mode 100644 husky.config.js create mode 100644 index.php create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 page.php create mode 100644 release.config.js create mode 100644 screenshot.png create mode 100644 search.php create mode 100644 single.php create mode 100644 src/StarterSite.php create mode 100644 style.css create mode 100644 templates/404.twig create mode 100644 templates/archive.twig create mode 100644 templates/author.twig create mode 100644 templates/index.twig create mode 100644 templates/layouts/base.twig create mode 100644 templates/page.twig create mode 100644 templates/partials/comment-form.twig create mode 100644 templates/partials/comment.twig create mode 100644 templates/partials/footer.twig create mode 100644 templates/partials/head.twig create mode 100644 templates/partials/menu.twig create mode 100644 templates/partials/pagination.twig create mode 100644 templates/partials/tease-post.twig create mode 100644 templates/partials/tease.twig create mode 100644 templates/search.twig create mode 100644 templates/single-password.twig create mode 100644 templates/single.twig create mode 100644 whisk/assets/audio/.gitkeep create mode 100644 whisk/assets/fonts/.gitkeep create mode 100644 whisk/assets/icons/.gitkeep create mode 100644 whisk/assets/icons/angle-down.svg create mode 100644 whisk/assets/icons/arrowLeft.svg create mode 100644 whisk/assets/icons/arrowRight.svg create mode 100644 whisk/assets/icons/chevron-down.svg create mode 100644 whisk/assets/icons/chevronLeft.svg create mode 100644 whisk/assets/icons/chevronRight.svg create mode 100644 whisk/assets/icons/circle-info.svg create mode 100644 whisk/assets/icons/close-alt.svg create mode 100644 whisk/assets/icons/close.svg create mode 100644 whisk/assets/icons/crisisAlert.svg create mode 100644 whisk/assets/icons/download.svg create mode 100644 whisk/assets/icons/expand.svg create mode 100644 whisk/assets/icons/facebook.svg create mode 100644 whisk/assets/icons/home.svg create mode 100644 whisk/assets/icons/instagram.svg create mode 100644 whisk/assets/icons/linkedin.svg create mode 100644 whisk/assets/icons/list.svg create mode 100644 whisk/assets/icons/menu-close.svg create mode 100644 whisk/assets/icons/menu.svg create mode 100644 whisk/assets/icons/pause.svg create mode 100644 whisk/assets/icons/play.svg create mode 100644 whisk/assets/icons/programs-all.svg create mode 100644 whisk/assets/icons/programs-by-category.svg create mode 100644 whisk/assets/icons/programs-switch-view.svg create mode 100644 whisk/assets/icons/search.svg create mode 100644 whisk/assets/icons/starburst.svg create mode 100644 whisk/assets/icons/submenu.svg create mode 100644 whisk/assets/icons/triangle-exclamation.svg create mode 100644 whisk/assets/icons/twitter.svg create mode 100644 whisk/assets/icons/xmark.svg create mode 100644 whisk/assets/icons/youtube.svg create mode 100644 whisk/assets/images/.gitkeep create mode 100644 whisk/assets/video/.gitkeep create mode 100644 whisk/config/.env.example create mode 100644 whisk/config/configma.json create mode 100644 whisk/config/emulsify-core/.prettierignore create mode 100644 whisk/config/emulsify-core/eslintrc.config.json create mode 100644 whisk/config/emulsify-core/prettierrc.config.json create mode 100644 whisk/config/emulsify-core/storybook/example.theme.js create mode 100644 whisk/config/emulsify-core/storybook/main.js create mode 100644 whisk/config/emulsify-core/storybook/preview.js create mode 100644 whisk/config/emulsify-core/stylelintrc.config.json create mode 100644 whisk/config/emulsify-core/webpack/example.babel.config.js create mode 100644 whisk/config/emulsify-core/webpack/example.postcss.config.js create mode 100644 whisk/config/sd.config.json create mode 100644 whisk/functions.php create mode 100644 whisk/includes/site.php create mode 100644 whisk/package.json create mode 100644 whisk/project.emulsify.json create mode 100644 whisk/screenshot.png create mode 100644 whisk/style.css diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..4abb119 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,8 @@ +# Browsers that we support + +> 1% +last 2 versions +Firefox ESR +Chrome 41 # Support for Googlebot +not dead +not IE 9-11 # For IE 9-11 support, remove 'not'. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cf0dff6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +# Unix-style newlines with a newline ending every file +[*] +indent_style = space +indent_size = 2 +insert_final_newline = true diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..ee8cdc6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +emulsify version (see [releases](https://github.com/emulsify-ds/emulsify-drupal/releases)): + +**What you did:** + +**What happened:** + +**Reproduction repository (if necessary):** + +**Problem description:** + +**Suggested solution:** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..fc5ef3d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +**This PR does the following:** +- Adds functionality bullet item +- Fixes this or that bullet item + +### Related Issue(s) +- [Title of the issue](https://github.com/emulsify-ds/emulsify-drupal/issues/1) (if applicable) + +### Notes: +- (optional) Document any intentionally unfinished parts or known issues within this PR + +### Functional Testing: +- [ ] Document steps that allow someone to fully test your code changes. Include screenshot and links when appropriate. + + diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..a620800 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,16 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - 'Priority: Critical' +# Label to use when marking an issue as stale +staleLabel: 'Automatically Closed' +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/addtoprojects.yml b/.github/workflows/addtoprojects.yml new file mode 100644 index 0000000..9fb9df2 --- /dev/null +++ b/.github/workflows/addtoprojects.yml @@ -0,0 +1,22 @@ +name: Add to projects + +on: + issues: + types: + - opened + pull_request: + types: + - opened + workflow_dispatch: + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.3.0 + with: + # You can target a repository in a different organization + # to the issue + project-url: https://github.com/orgs/emulsify-ds/projects/6 + token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 0000000..07b7454 --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,24 @@ +name: Add contributors +on: + schedule: + - cron: '20 20 * * *' + push: + branches: + - main + workflow_dispatch: + +jobs: + add-contributors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: BobAnkh/add-contributors@master + with: + CONTRIBUTOR: '### Contributors' + COLUMN_PER_ROW: '6' + ACCESS_TOKEN: ${{secrets.ADD_TO_PROJECT_PAT}} + IMG_WIDTH: '100' + FONT_SIZE: '14' + PATH: '/README.md' + COMMIT_MESSAGE: 'docs(README): update contributors' + AVATAR_SHAPE: 'round' diff --git a/.github/workflows/dev-sync.yml b/.github/workflows/dev-sync.yml new file mode 100644 index 0000000..cb33d71 --- /dev/null +++ b/.github/workflows/dev-sync.yml @@ -0,0 +1,29 @@ +name: Sync Drupal Dev Branch +on: + push: + branches: + - '*.x' + workflow_dispatch: +jobs: + sync: + name: Sync Dev Branch + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Extract branch name + shell: bash + run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" + id: extract_branch + - name: Add remote SSH Key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.DRUPAL_ORG_SSH_KEY }} + config: ${{ secrets.SSH_CONFIG }} + known_hosts: ${{ secrets.KNOWN_HOSTS }} + - name: Sync dev branch to Drupal.org Project + run: | + git remote add drupal-org '${{ secrets.DRUPAL_REPO_URL }}' + git push drupal-org ${{ steps.extract_branch.outputs.branch }} diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml new file mode 100644 index 0000000..4a24fcf --- /dev/null +++ b/.github/workflows/semantic-release.yml @@ -0,0 +1,30 @@ +name: Semantic Release on Merge +on: + push: + branches: + - "main" + workflow_dispatch: +jobs: + release: + name: Attempt Semantic Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Add remote SSH Key + uses: shimataro/ssh-key-action@v2 + with: + key: ${{ secrets.DRUPAL_ORG_SSH_KEY }} + config: ${{ secrets.SSH_CONFIG }} + known_hosts: ${{ secrets.KNOWN_HOSTS }} + - name: Sync release tags to Drupal.org Project + if: steps.semantic.outputs.new_release_published == 'true' + run: | + git remote add drupal-org '${{ secrets.DRUPAL_REPO_URL }}' + git push drupal-org --tags + git checkout main + git push drupal-org main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..569c0d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Ignore compiled files. +dist + +# npm +node_modules + +# System +.DS_Store +.idea +.vscode + +# Git deploy +.publish + +# Composer +composer.lock +vendor + +# Jest +.coverage diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..20d0d06 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run lint diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..5fca0d5 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +scripts-prepend-node-path=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/404.php b/404.php new file mode 100644 index 0000000..a51168d --- /dev/null +++ b/404.php @@ -0,0 +1,11 @@ + +Copyright (C) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. +This is free software, and you are welcome to redistribute it +under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program +`Gnomovision' (which makes passes at compilers) written by James Hacker. + +, 1 April 1989 +Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..50d9617 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +![Emulsify Design System](https://github.com/emulsify-ds/.github/blob/6bd435be881bd820bddfa05d88905efe29176a0a/assets/images/header.png) + +# Emulsify wordpress + +## Emulsify is an open-source toolset for creating and implementing design systems on your website + +### Storybook development, Webpack build, and wordpress 11 theme + +**Emulsify wordpress** provides a [Storybook](https://storybook.js.org/) component library, a [Webpack](https://webpack.js.org/) development environment, and a wordpress 11 starter kit theme. + +## Documentation + +[docs.emulsify.info](https://emulsify.info/docs) + +### Quick Links + +1. [Installation](https://www.emulsify.info/docs/emulsify-wordpress) +2. [Usage](https://www.emulsify.info/docs/emulsify-wordpress/basic-usage/commands) + +## Demo + +1. [Storybook](http://storybook.emulsify.info/) + +## Contributing + +### [Code of Conduct](https://github.com/emulsify-ds/emulsify-wordpress/blob/main/CODE_OF_CONDUCT.md) + +The project maintainers have adopted a Code of Conduct that we expect project participants to adhere to. Please read the full text so that you can understand what actions will and will not be tolerated. + +### Contribution Guide + +Please also follow the issue template and pull request templates provided. See below for the correct places to post issues: + +1. [Emulsify wordpress](https://github.com/emulsify-ds/emulsify-wordpress) +2. [Emulsify Twig Extensions](https://github.com/emulsify-ds/emulsify-twig-extensions/issues) + +### Committing Changes + +To facilitate automatic semantic release versioning, we utilize the [Conventional Changelog](https://github.com/conventional-changelog/conventional-changelog) standard through Commitizen. Follow these steps when commiting your work to ensure semantic release can version correctly. + +1. Stage your changes, ensuring they encompass exactly what you wish to change, no more. +2. Run the `commit` script via `npm run commit` and follow the prompts to craft the perfect commit message. +3. Your commit message will be used to create the changelog for the next version that includes that commit. + +## Author + +Emulsify® is a product of [Four Kitchens — We make BIG websites](https://fourkitchens.com). + +### Contributors + + + + + + +
    + + Callin Mullaney +
    + Callin Mullaney +
    +
    + + Mike Goulding +
    + Mike Goulding +
    +
    diff --git a/archive.php b/archive.php new file mode 100644 index 0000000..c364f58 --- /dev/null +++ b/archive.php @@ -0,0 +1,39 @@ + $title, + ] +); + +Timber::render( $templates, $context ); diff --git a/author.php b/author.php new file mode 100644 index 0000000..dae48bc --- /dev/null +++ b/author.php @@ -0,0 +1,18 @@ +name() ); +} + +Timber::render( [ 'templates/author.twig', 'templates/archive.twig' ], $context ); diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..84dcb12 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], +}; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a4ab436 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "emulsify-ds/emulsify-wordpress", + "description": "The official Wordpress base theme for Emulsify that generates custom themes with Storybook development + Webpack Build", + "type": "wordpress-theme", + "homepage": "http://emulsify.info", + "license": "GPL-2.0-only", + "authors": [ + { + "name": "Callin Mullaney", + "email": "callin@fourkitchens.com" + }, + { + "name": "Mike Goulding", + "email": "mike.goulding@fourkitchens.com" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "timber/timber": "^2.3" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true, + "allow-plugins": { + "composer/installers": true + } + } +} \ No newline at end of file diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..bea8653 --- /dev/null +++ b/functions.php @@ -0,0 +1,124 @@ + + * : The name of the new theme. + * + * ## EXAMPLES + * + * wp emulsify new-theme + * + * @when after_wp_load + */ + public function __invoke($args, $assoc_args) + { + list($theme_name) = $args; + $machine_name = $this->convertLabelToMachineName($theme_name); + + // Define source (base theme) and destination (new theme) paths. + $source = get_theme_root() . '/emulsify/whisk'; + $destination = get_theme_root() . '/' . $machine_name; + + // Validate source directory existence. + if (!is_dir($source)) { + WP_CLI::error("The source directory ($source) does not exist."); + } + + // Prevent overwriting an existing theme. + if (is_dir($destination)) { + WP_CLI::error("The destination directory ($destination) already exists."); + } + + // Recursively copy theme files and directories. + $this->copyTheme($source, $destination, 'whisk', $machine_name); + + // Replace instances of "whisk" with the new theme name inside file contents. + $this->renameInstances($destination, 'whisk', $machine_name); + + WP_CLI::success("Theme '$theme_name' has been created successfully."); + } + + /** + * Recursively copies a theme while renaming instances of the base theme name in filenames. + * + * @param string $src Source directory (original theme). + * @param string $dst Destination directory (new theme). + * @param string $search The original theme name to search for. + * @param string $replace The new theme name to replace with. + */ + private function copyTheme(string $src, string $dst, string $search, string $replace): void + { + $dir = opendir($src); + @mkdir($dst, 0755, true); // Ensure directory is created with correct permissions. + + while (($file = readdir($dir)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + + $src_file = $src . '/' . $file; + $dst_file = $dst . '/' . str_replace($search, $replace, $file); + + // If it's a directory, recursively copy its contents. + if (is_dir($src_file)) { + $this->copyTheme($src_file, $dst_file, $search, $replace); + } else { + copy($src_file, $dst_file); + } + } + + closedir($dir); + } + + /** + * Recursively replaces all instances of a string inside files within a directory. + * + * @param string $dir The directory to search within. + * @param string $search The string to search for. + * @param string $replace The string to replace it with. + */ + private function renameInstances(string $dir, string $search, string $replace): void + { + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); + + foreach ($files as $file) { + if ($file->isFile()) { + $contents = file_get_contents($file->getRealPath()); + $updated_contents = str_replace($search, $replace, $contents); + file_put_contents($file->getRealPath(), $updated_contents); + } + } + } + + /** + * Converts a given theme name into a machine-friendly format. + * + * @param string $label The human-readable theme name. + * @return string The machine-friendly version of the theme name. + */ + private function convertLabelToMachineName(string $label): string + { + return strtolower(preg_replace('/[^a-z0-9_]+/ui', '_', $label)); + } +} + +// Register the WP-CLI command. +WP_CLI::add_command('emulsify', 'Emulsify_CLI'); diff --git a/husky.config.js b/husky.config.js new file mode 100644 index 0000000..4901c7c --- /dev/null +++ b/husky.config.js @@ -0,0 +1,5 @@ +module.exports = { + hooks: { + 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', + }, +}; diff --git a/index.php b/index.php new file mode 100644 index 0000000..619f59b --- /dev/null +++ b/index.php @@ -0,0 +1,27 @@ + 'bar', + ] +); + +Timber::render( $templates, $context ); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..398b5f1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7896 @@ +{ + "name": "emulsify-wordpress", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "emulsify-wordpress", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.2.11" + }, + "devDependencies": { + "@commitlint/cli": "^19.4.1", + "@commitlint/config-conventional": "^19.4.1", + "@semantic-release/changelog": "^6.0.2", + "@semantic-release/commit-analyzer": "^13.0.0", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^11.0.2", + "@semantic-release/release-notes-generator": "^14.0.0", + "husky": "^9.1.5", + "lint-staged": "^15.2.9", + "semantic-release": "^24.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@commitlint/cli": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.0.tgz", + "integrity": "sha512-t/fCrLVu+Ru01h0DtlgHZXbHV2Y8gKocTR5elDOqIRUzQd0/6hpt2VIWOj9b3NDo7y4/gfxeR2zRtXq/qO6iUg==", + "dev": true, + "dependencies": { + "@commitlint/format": "^19.8.0", + "@commitlint/lint": "^19.8.0", + "@commitlint/load": "^19.8.0", + "@commitlint/read": "^19.8.0", + "@commitlint/types": "^19.8.0", + "tinyexec": "^0.3.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.0.tgz", + "integrity": "sha512-9I2kKJwcAPwMoAj38hwqFXG0CzS2Kj+SAByPUQ0SlHTfb7VUhYVmo7G2w2tBrqmOf7PFd6MpZ/a1GQJo8na8kw==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.0", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.0.tgz", + "integrity": "sha512-+r5ZvD/0hQC3w5VOHJhGcCooiAVdynFlCe2d6I9dU+PvXdV3O+fU4vipVg+6hyLbQUuCH82mz3HnT/cBQTYYuA==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.0", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.0.tgz", + "integrity": "sha512-kNiNU4/bhEQ/wutI1tp1pVW1mQ0QbAjfPRo5v8SaxoVV+ARhkB8Wjg3BSseNYECPzWWfg/WDqQGIfV1RaBFQZg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.0", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.0.tgz", + "integrity": "sha512-fuLeI+EZ9x2v/+TXKAjplBJWI9CNrHnyi5nvUQGQt4WRkww/d95oVRsc9ajpt4xFrFmqMZkd/xBQHZDvALIY7A==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.0.tgz", + "integrity": "sha512-EOpA8IERpQstxwp/WGnDArA7S+wlZDeTeKi98WMOvaDLKbjptuHWdOYYr790iO7kTCif/z971PKPI2PkWMfOxg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.0.tgz", + "integrity": "sha512-L2Jv9yUg/I+jF3zikOV0rdiHUul9X3a/oU5HIXhAJLE2+TXTnEBfqYP9G5yMw/Yb40SnR764g4fyDK6WR2xtpw==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.0", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.0.tgz", + "integrity": "sha512-+/NZKyWKSf39FeNpqhfMebmaLa1P90i1Nrb1SrA7oSU5GNN/lksA4z6+ZTnsft01YfhRZSYMbgGsARXvkr/VLQ==", + "dev": true, + "dependencies": { + "@commitlint/is-ignored": "^19.8.0", + "@commitlint/parse": "^19.8.0", + "@commitlint/rules": "^19.8.0", + "@commitlint/types": "^19.8.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.0.tgz", + "integrity": "sha512-4rvmm3ff81Sfb+mcWT5WKlyOa+Hd33WSbirTVUer0wjS1Hv/Hzr07Uv1ULIV9DkimZKNyOwXn593c+h8lsDQPQ==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^19.8.0", + "@commitlint/execute-rule": "^19.8.0", + "@commitlint/resolve-extends": "^19.8.0", + "@commitlint/types": "^19.8.0", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.0.tgz", + "integrity": "sha512-qs/5Vi9bYjf+ZV40bvdCyBn5DvbuelhR6qewLE8Bh476F7KnNyLfdM/ETJ4cp96WgeeHo6tesA2TMXS0sh5X4A==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.0.tgz", + "integrity": "sha512-YNIKAc4EXvNeAvyeEnzgvm1VyAe0/b3Wax7pjJSwXuhqIQ1/t2hD3OYRXb6D5/GffIvaX82RbjD+nWtMZCLL7Q==", + "dev": true, + "dependencies": { + "@commitlint/types": "^19.8.0", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.0.tgz", + "integrity": "sha512-6ywxOGYajcxK1y1MfzrOnwsXO6nnErna88gRWEl3qqOOP8MDu/DTeRkGLXBFIZuRZ7mm5yyxU5BmeUvMpNte5w==", + "dev": true, + "dependencies": { + "@commitlint/top-level": "^19.8.0", + "@commitlint/types": "^19.8.0", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^0.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.0.tgz", + "integrity": "sha512-CLanRQwuG2LPfFVvrkTrBR/L/DMy3+ETsgBqW1OvRxmzp/bbVJW0Xw23LnnExgYcsaFtos967lul1CsbsnJlzQ==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^19.8.0", + "@commitlint/types": "^19.8.0", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.0.tgz", + "integrity": "sha512-IZ5IE90h6DSWNuNK/cwjABLAKdy8tP8OgGVGbXe1noBEX5hSsu00uRlLu6JuruiXjWJz2dZc+YSw3H0UZyl/mA==", + "dev": true, + "dependencies": { + "@commitlint/ensure": "^19.8.0", + "@commitlint/message": "^19.8.0", + "@commitlint/to-lines": "^19.8.0", + "@commitlint/types": "^19.8.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.0.tgz", + "integrity": "sha512-3CKLUw41Cur8VMjh16y8LcsOaKbmQjAKCWlXx6B0vOUREplp6em9uIVhI8Cv934qiwkbi2+uv+mVZPnXJi1o9A==", + "dev": true, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.0.tgz", + "integrity": "sha512-Rphgoc/omYZisoNkcfaBRPQr4myZEHhLPx2/vTXNLjiCw4RgfPR1wEgUpJ9OOmDCiv5ZyIExhprNLhteqH4FuQ==", + "dev": true, + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "19.8.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.0.tgz", + "integrity": "sha512-LRjP623jPyf3Poyfb0ohMj8I3ORyBDOwXAgxxVPbSD0unJuW2mJWeiRfaQinjtccMqC5Wy1HOMfa4btKjbNxbg==", + "dev": true, + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", + "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", + "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.2.2", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "dev": true, + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", + "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", + "dev": true, + "dependencies": { + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz", + "integrity": "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-12.0.0.tgz", + "integrity": "sha512-MPd6WK1VtZ52lFrgZ0R2FlaoiWllzgqFHaSZxvp72NmoDeZ0m8GeJdg4oB6ctqMTYyrnDYp592Xma21mrgiyDA==", + "dev": true, + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.2.1.tgz", + "integrity": "sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==", + "dev": true, + "dependencies": { + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-10.0.0.tgz", + "integrity": "sha512-Kuq5/qs0DVYTHZuBAzCZStCzo2nKvVRo/TDNhCcpC2TKiOGz/DisXMCvjt3/b5kr6SCI1Y8eeeJTHBxxpFvZEg==", + "dev": true, + "dependencies": { + "@octokit/types": "^14.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.1.3" + } + }, + "node_modules/@octokit/request": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz", + "integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.0.0.tgz", + "integrity": "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^25.0.0" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, + "node_modules/@semantic-release/changelog": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", + "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", + "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "dev": true, + "dependencies": { + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "dev": true, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/github": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.2.tgz", + "integrity": "sha512-EhHimj3/eOSPu0OflgDzwgrawoGJIn8XLOkNS6WzwuTr8ebxyX976Y4mCqJ8MlkdQpV5+8T+49sy8xXlcm6uCg==", + "dev": true, + "dependencies": { + "@octokit/core": "^6.0.0", + "@octokit/plugin-paginate-rest": "^12.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^10.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=24.1.0" + } + }, + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/github/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/github/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", + "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.5.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/npm/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.3.tgz", + "integrity": "sha512-XxAZRPWGwO5JwJtS83bRdoIhCiYIx8Vhr+u231pQAsdFIAbm19rSVJLdnBN+Avvk7CKvNQE/nJ4y7uqKH6WTiw==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^2.0.0", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", + "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "dev": true, + "dependencies": { + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", + "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.15.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.15.tgz", + "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "dev": true + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cli-highlight/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", + "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", + "dev": true, + "dependencies": { + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser/node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz", + "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==", + "dev": true, + "dependencies": { + "jiti": "^2.4.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, + "node_modules/env-ci": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", + "integrity": "sha512-Z8dnwSDbV1XYM9SBF2J0GcNVvmfmfh3a49qddGIROhBoVro6MZVTji15z/sJbQ2ko2ei8n988EU1wzoLU/tF+g==", + "dev": true, + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/env-ci/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/env-ci/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/git-log-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", + "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", + "dev": true, + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "0.6.8" + } + }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/git-raw-commits/node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/hook-std": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", + "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from-esm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", + "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=18.20" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "dev": true, + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "dev": true, + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", + "integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", + "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.0", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.1.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "@sigstore/tuf": "^3.0.0", + "abbrev": "^3.0.0", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.3.0", + "ci-info": "^4.1.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.0.2", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.0", + "libnpmexec": "^9.0.0", + "libnpmfund": "^6.0.0", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.0", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.0.0", + "nopt": "^8.0.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^4.0.0", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.0.0", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.0", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "normalize-package-data": "^7.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.1.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/minizlib": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/p-map": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.1.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/nopt/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "5.0.10", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.6.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^3.0.0", + "@sigstore/tuf": "^3.0.0", + "@sigstore/verify": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^14.0.1", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.20", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", + "dev": true, + "dependencies": { + "p-map": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", + "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/semantic-release": { + "version": "24.2.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.3.tgz", + "integrity": "sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A==", + "dev": true, + "dependencies": { + "@semantic-release/commit-analyzer": "^13.0.0-beta.1", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^11.0.0", + "@semantic-release/npm": "^12.0.0", + "@semantic-release/release-notes-generator": "^14.0.0-beta.1", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^9.0.0", + "figures": "^6.0.0", + "find-versions": "^6.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^3.0.0", + "hosted-git-info": "^8.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "marked": "^12.0.0", + "marked-terminal": "^7.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-package-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^4.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": ">=20.8.1" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/execa": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "dev": true, + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/hosted-git-info": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/semantic-release/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/semantic-release/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/semantic-release/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/signale/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/signale/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/super-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", + "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "dev": true, + "dependencies": { + "function-timeout": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f451773 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "emulsify-wordpress", + "version": "1.0.0", + "description": "Storybook development + Webpack Build + Wordpress theme", + "keywords": [ + "component library", + "design system", + "wordpress", + "pattern library", + "storybook", + "styleguide" + ], + "author": "Four Kitchens ", + "license": "ISC", + "repository": { + "type": "git", + "url": "git+https://github.com/emulsify-ds/emulsify-wordpress.git" + }, + "bugs": { + "url": "https://github.com/emulsify-ds/emulsify-wordpress/issues" + }, + "homepage": "https://www.emulsify.info", + "scripts": { + "prepare": "husky install", + "husky:commit-msg": "commitlint --edit $1", + "husky:pre-commit": "npm run lint", + "lint": "npm run lint:php", + "lint:php": "find ./ -name '*.php' -exec php -l {} \\;", + "lint-staged": "lint-staged", + "prepare": "[ -d '.git' ] && (husky install) || true", + "publish": "semantic-release --tag-format '${version}'", + "publish-test": "semantic-release --tag-format '${version}' --dry-run --debug", + "semantic-release": "semantic-release" + }, + "dependencies": { + "graceful-fs": "^4.2.11" + }, + "devDependencies": { + "@commitlint/cli": "^19.4.1", + "@commitlint/config-conventional": "^19.4.1", + "@semantic-release/changelog": "^6.0.2", + "@semantic-release/commit-analyzer": "^13.0.0", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^11.0.2", + "@semantic-release/release-notes-generator": "^14.0.0", + "husky": "^9.1.5", + "lint-staged": "^15.2.9", + "semantic-release": "^24.1.0" + } +} diff --git a/page.php b/page.php new file mode 100644 index 0000000..34c142d --- /dev/null +++ b/page.php @@ -0,0 +1,17 @@ +5`J}rJJQYUY_q? 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 literal 0 HcmV?d00001 diff --git a/search.php b/search.php new file mode 100644 index 0000000..9e1ac5f --- /dev/null +++ b/search.php @@ -0,0 +1,18 @@ + 'Search results for ' . get_search_query(), + ] +); + +Timber::render( $templates, $context ); diff --git a/single.php b/single.php new file mode 100644 index 0000000..df070cf --- /dev/null +++ b/single.php @@ -0,0 +1,20 @@ +post_type . '.twig', 'templates/single.twig' ]; + +if ( post_password_required( $post->ID ) ) { + $templates = 'templates/single-password.twig'; +} + +Timber::render( $templates, $context ); diff --git a/src/StarterSite.php b/src/StarterSite.php new file mode 100644 index 0000000..834eaad --- /dev/null +++ b/src/StarterSite.php @@ -0,0 +1,186 @@ + _x( 'Main menu', 'Backend - menu name', 'timber-starter' ), + ] + ); + + // Add default posts and comments RSS feed links to head. + add_theme_support( 'automatic-feed-links' ); + + /* + * Let WordPress manage the document title. + * By adding theme support, we declare that this theme does not use a + * hard-coded tag in the document head, and expect WordPress to + * provide it for us. + */ + add_theme_support( 'title-tag' ); + + /* + * Enable support for Post Thumbnails on posts and pages. + * + * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ + */ + add_theme_support( 'post-thumbnails' ); + + /* + * Switch default core markup for search form, comment form, and comments + * to output valid HTML5. + */ + add_theme_support( + 'html5', + [ + 'comment-form', + 'comment-list', + 'gallery', + 'caption', + ] + ); + + /* + * Enable support for Post Formats. + * + * See: https://codex.wordpress.org/Post_Formats + */ + add_theme_support( + 'post-formats', + [ + 'aside', + 'image', + 'video', + 'quote', + 'link', + 'gallery', + 'audio', + ] + ); + + add_theme_support( 'menus' ); + } + + /** + * This would return 'foo bar!'. + * + * @param string $text being 'foo', then returned 'foo bar!' + */ + public function myfoo( $text ) { + $text .= ' bar!'; + + return $text; + } + + /** + * This is where you can add your own functions to twig. + * + * @link https://timber.github.io/docs/v2/hooks/filters/#timber/twig/filters + * @param array $filters an array of Twig filters. + */ + public function add_filters_to_twig( $filters ) { + + $additional_filters = [ + 'myfoo' => [ + 'callable' => [ $this, 'myfoo' ], + ], + ]; + + return array_merge( $filters, $additional_filters ); + } + + + /** + * This is where you can add your own functions to twig. + * + * @link https://timber.github.io/docs/v2/hooks/filters/#timber/twig/functions + * @param array $functions an array of existing Twig functions. + */ + public function add_functions_to_twig( $functions ) { + $additional_functions = [ + 'get_theme_mod' => [ + 'callable' => 'get_theme_mod', + ], + ]; + + return array_merge( $functions, $additional_functions ); + } + + /** + * Updates Twig environment options. + * + * @see https://twig.symfony.com/doc/2.x/api.html#environment-options + * + * @param array $options an array of environment options + * + * @return array + */ + public function update_twig_environment_options( $options ) { + // $options['autoescape'] = true; + + return $options; + } +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..4ec9ec3 --- /dev/null +++ b/style.css @@ -0,0 +1,10 @@ +/* + * Theme Name: Emulsify + * Author: Callin Mullaney & Mike Goulding + * Description: The official base theme for Emulsify and it's flexible component driven development. + * Text Domain: emulsify + * Requires at least: 6.7 + * Tested up to: 6.7 + * Requires PHP: 8.3 + * Version: 0.1 +*/ diff --git a/templates/404.twig b/templates/404.twig new file mode 100644 index 0000000..1b2073c --- /dev/null +++ b/templates/404.twig @@ -0,0 +1,5 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + Sorry, we couldn't find what you're looking for. +{% endblock %} diff --git a/templates/archive.twig b/templates/archive.twig new file mode 100644 index 0000000..ae021ea --- /dev/null +++ b/templates/archive.twig @@ -0,0 +1,15 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} + + {% include 'partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} +{% endblock %} diff --git a/templates/author.twig b/templates/author.twig new file mode 100644 index 0000000..6d19761 --- /dev/null +++ b/templates/author.twig @@ -0,0 +1,7 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} +{% endblock %} diff --git a/templates/index.twig b/templates/index.twig new file mode 100644 index 0000000..b68fbd7 --- /dev/null +++ b/templates/index.twig @@ -0,0 +1,16 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <h2>{{ foo }}</h2> + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} + + {% include 'partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} +{% endblock %} diff --git a/templates/layouts/base.twig b/templates/layouts/base.twig new file mode 100644 index 0000000..537c08d --- /dev/null +++ b/templates/layouts/base.twig @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html {{ site.language_attributes }}> + {% block head %} + {% include 'partials/head.twig' %} + {% endblock %} + + <body class="{{ body_class }}"> + {{ function('wp_body_open') }} + <a class="skip-link screen-reader-text" href="#content">{{ _e('Skip to content') }}</a> + <header class="header"> + {% block header %} + <div class="wrapper"> + <h1 class="hdr-logo"> + <a class="hdr-logo-link" href="{{ site.url }}">{{ site.name }}</a> + </h1> + <nav id="nav-main" class="nav-main"> + {% include 'partials/menu.twig' with { + items: menu.get_items + } %} + </nav> + <!-- #nav --> + </div> + {% endblock %} + </header> + + <section id="content" class="content-wrapper"> + {% if title %} + <h1>{{ title }}</h1> + {% endif %} + <div class="wrapper"> + {% block content %} + Sorry, no content + {% endblock %} + </div> + </section> + + {% block footer %} + {% include 'partials/footer.twig' %} + {% endblock %} + {{ function('wp_footer') }} + {% do action('get_footer') %} + </body> +</html> diff --git a/templates/page.twig b/templates/page.twig new file mode 100644 index 0000000..7709c52 --- /dev/null +++ b/templates/page.twig @@ -0,0 +1,12 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + <article class="post-type-{{ post.type }}" id="post-{{ post.id }}"> + <section class="article-content"> + <h1 class="article-h1">{{ post.title }}</h1> + <div class="article-body">{{ post.content }}</div> + </section> + </article> + </div> +{% endblock %} diff --git a/templates/partials/comment-form.twig b/templates/partials/comment-form.twig new file mode 100644 index 0000000..565f4b6 --- /dev/null +++ b/templates/partials/comment-form.twig @@ -0,0 +1,32 @@ +<div class="comment-form"> + <h3>Add comment</h3> + <form class="comment-form" method="post" action="{{ site.link ~ '/wp-comments-post.php' }}"> + {% if user %} + <input type="hidden" name="email" value="{{ user.email }}" /> + <input type="hidden" name="author" value="{{ user.name }}" /> + <input type="hidden" name="url" value="{{ user.link }}" /> + {% else %} + <label> + Email<br /> + <input required name="email" type="email" id="email" /> + </label> + <label> + Name<br /> + <input required name="author" type="text" /> + </label> + <label> + Website<br /> + <input name="url" type="url" /> + </label> + {% endif %} + <label> + Comment<br /> + <textarea placeholder="Leave a comment..." name="comment" cols="60" rows="3"></textarea> + </label> + <input name="comment_post_ID" value="{{ post.id }}" type="hidden" /> + <input name="comment_parent" value="{{ comment.id|default('0') }}" type="hidden" /> + <button type="submit" name="Submit" class="btn">Send</button> + <button type="reset">Cancel</button> + <p>Your comment will be revised by the site if needed.</p> + </form> +</div> diff --git a/templates/partials/comment.twig b/templates/partials/comment.twig new file mode 100644 index 0000000..710f979 --- /dev/null +++ b/templates/partials/comment.twig @@ -0,0 +1,19 @@ +<div class="blog-comment" id="blog-comment-{{ comment.id }}"> + <h5 class="comment-author">{{ comment.author.name }} says</h5> + <div class="comment-content">{{ comment.content|wpautop }}</div> + + <section class="comment-box"> + {% include 'partials/comment-form.twig' %} + + {% if post.comments %} + <h4>replies</h4> + <div class="comments"> + {% for cmt in comment.children %} + {% include 'partials/comment.twig' with { + comment: cmt + } %} + {% endfor %} + </div> + {% endif %} + </section> +</div> diff --git a/templates/partials/footer.twig b/templates/partials/footer.twig new file mode 100644 index 0000000..606165c --- /dev/null +++ b/templates/partials/footer.twig @@ -0,0 +1 @@ +<footer id="footer">Copyright {{ 'now'|date('Y') }}</footer> diff --git a/templates/partials/head.twig b/templates/partials/head.twig new file mode 100644 index 0000000..ea68c2a --- /dev/null +++ b/templates/partials/head.twig @@ -0,0 +1,9 @@ +<head> + <meta charset="{{ site.charset }}" /> + <link rel="stylesheet" href="{{ site.theme.link }}/style.css" type="text/css" media="screen" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="author" href="{{ site.theme.link }}/humans.txt" /> + <link rel="profile" href="http://gmpg.org/xfn/11" /> + {% do action('get_header') %} + {{ function('wp_head') }} +</head> diff --git a/templates/partials/menu.twig b/templates/partials/menu.twig new file mode 100644 index 0000000..377dc98 --- /dev/null +++ b/templates/partials/menu.twig @@ -0,0 +1,12 @@ +{% if menu %} + <ul> + {% for item in items %} + <li class="{{ item.classes|join(' ') }}"> + <a target="{{ item.target }}" href="{{ item.link }}">{{ item.title }}</a> + {% include 'partials/menu.twig' with { + items: item.children + } %} + </li> + {% endfor %} + </ul> +{% endif %} diff --git a/templates/partials/pagination.twig b/templates/partials/pagination.twig new file mode 100644 index 0000000..e4d737b --- /dev/null +++ b/templates/partials/pagination.twig @@ -0,0 +1,44 @@ +{% if posts.pagination.pages is not empty %} + <nav class="pagination-block"> + <ul class="pagination"> + {# First #} + {% if (posts.pagination.pages|first) and posts.pagination.pages|first.current != true %} + <li class="first btn"> + <a href="{{ posts.pagination.pages|first.link }}">First</a> + </li> + {% else %} + <li class="first btn disabled"><button disabled>First</button></li> + {% endif %} + + {# Previous #} + {% if posts.pagination.prev %} + <li class="prev btn"><a href="{{ posts.pagination.prev.link }}">Previous</a></li> + {% else %} + <li class="prev btn disabled"><button disabled>Previous</button></li> + {% endif %} + + {# Pages #} + {% for page in posts.pagination.pages %} + {% if page.link %} + <li><a href="{{ page.link }}" class="{{ page.class }}">{{ page.title }}</a></li> + {% else %} + <li class="current"><span class="{{ page.class }}">{{ page.title }}</span></li> + {% endif %} + {% endfor %} + + {# Next #} + {% if posts.pagination.next %} + <li class="next btn"><a href="{{ posts.pagination.next.link }}">Next</a></li> + {% else %} + <li class="next btn disabled"><button disabled>Next</button></li> + {% endif %} + + {# Last #} + {% if (posts.pagination.pages|last) and posts.pagination.pages|last.current != true %} + <li class="last btn"><a href="{{ posts.pagination.pages|last.link }}">Last</a></li> + {% else %} + <li class="last btn disabled"><button disabled>Last</button></li> + {% endif %} + </ul> + </nav> +{% endif %} diff --git a/templates/partials/tease-post.twig b/templates/partials/tease-post.twig new file mode 100644 index 0000000..5a08767 --- /dev/null +++ b/templates/partials/tease-post.twig @@ -0,0 +1,16 @@ +{% extends 'partials/tease.twig' %} + +{% block content %} + <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> + <p> + {{ + post.excerpt({ + words: 5, + read_more: 'Keep reading' + }) + }} + </p> + {% if post.thumbnail.src %} + <img src="{{ post.thumbnail.src }}" /> + {% endif %} +{% endblock %} diff --git a/templates/partials/tease.twig b/templates/partials/tease.twig new file mode 100644 index 0000000..1a0f7b0 --- /dev/null +++ b/templates/partials/tease.twig @@ -0,0 +1,9 @@ +<article class="tease tease-{{ post.type }}" id="tease-{{ post.id }}"> + {% block content %} + <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> + <p>{{ post.excerpt }}</p> + {% if post.thumbnail %} + <img src="{{ post.thumbnail.src }}" /> + {% endif %} + {% endblock %} +</article> \ No newline at end of file diff --git a/templates/search.twig b/templates/search.twig new file mode 100644 index 0000000..f8853a6 --- /dev/null +++ b/templates/search.twig @@ -0,0 +1,18 @@ +{# see `templates/archive.twig` for an alternative strategy of extending templates #} +{% extends 'layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} + + {% include 'partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} + </div> +{% endblock %} diff --git a/templates/single-password.twig b/templates/single-password.twig new file mode 100644 index 0000000..fc62761 --- /dev/null +++ b/templates/single-password.twig @@ -0,0 +1,17 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <form class="password-form" + action="{{ site.link }}/wp-login.php?action=postpass" + method="post"> + <label for="pwbox-{{ post.id }}">Password:</label> + <input class="password-box" + name="post_password" + id="pwbox-{{ post.id }}" + type="password" + placeholder="Password" + size="20" + maxlength="20" /> + <input class="password-btn" type="submit" name="Submit" value="Submit" /> + </form> +{% endblock %} diff --git a/templates/single.twig b/templates/single.twig new file mode 100644 index 0000000..584450d --- /dev/null +++ b/templates/single.twig @@ -0,0 +1,37 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + <article class="post-type-{{ post.type }}" id="post-{{ post.id }}"> + <img src="{{ post.thumbnail.src|resize(1200, 300) }}" /> + <section class="article-content"> + <h1 class="article-h1">{{ post.title }}</h1> + <p class="blog-author"> + <span>By</span> + <a href="{{ post.author.path }}">{{ post.author.name }}</a> + <span>•</span> + <time datetime="{{ post.date|date('Y-m-d H:i:s') }}">{{ post.date }}</time> + </p> + <div class="article-body">{{ post.content }}</div> + </section> + <section class="comment-box"> + <div class="comments"> + {% if post.comments %} + <h3>comments</h3> + {% for cmt in post.comments %} + {% include 'partials/comment.twig' with { + comment: cmt + } %} + {% endfor %} + {% endif %} + </div> + + {% if post.comment_status == 'closed' %} + <p>comments for this post are closed</p> + {% else %} + {% include 'partials/comment-form.twig' %} + {% endif %} + </section> + </article> + </div> +{% endblock %} diff --git a/whisk/assets/audio/.gitkeep b/whisk/assets/audio/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/assets/fonts/.gitkeep b/whisk/assets/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/assets/icons/.gitkeep b/whisk/assets/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/assets/icons/angle-down.svg b/whisk/assets/icons/angle-down.svg new file mode 100644 index 0000000..d3fc045 --- /dev/null +++ b/whisk/assets/icons/angle-down.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! + Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - + https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> + <path + d="M360.5 217.5l-152 143.1C203.9 365.8 197.9 368 192 368s-11.88-2.188-16.5-6.562L23.5 217.5C13.87 208.3 13.47 193.1 22.56 183.5C31.69 173.8 46.94 173.5 56.5 182.6L192 310.9l135.5-128.4c9.562-9.094 24.75-8.75 33.94 .9375C370.5 193.1 370.1 208.3 360.5 217.5z" /> +</svg> \ No newline at end of file diff --git a/whisk/assets/icons/arrowLeft.svg b/whisk/assets/icons/arrowLeft.svg new file mode 100644 index 0000000..0c9252c --- /dev/null +++ b/whisk/assets/icons/arrowLeft.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3334 3334" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="icons"><g id="ui-gambling-website-lined-icnos-casinoshunter"><g id="_1" serif:id="1"><path id="right-arrow" d="M981.391,2712.26l-922.2,-914.391l-7.71,-7.155c-30.857,-30.598 -48.809,-70.952 -51.481,-118.63l0.154,-16.028c2.238,-37.768 16.671,-73.849 45.114,-106.181l10.762,-11.107l925.361,-917.699c68.435,-67.861 179.085,-67.861 247.519,0c68.844,68.267 68.844,179.257 0.006,247.519l-638.939,633.699l2567.96,-0.063c96.702,-0 175.392,78.03 175.392,174.657c0,96.627 -78.69,174.658 -175.39,174.658l-2547.35,0.063l618.314,613.134c68.844,68.267 68.844,179.257 0,247.524c-68.434,67.861 -179.084,67.861 -247.519,0Z" style="fill-rule:nonzero;"/></g></g></g></svg> \ No newline at end of file diff --git a/whisk/assets/icons/arrowRight.svg b/whisk/assets/icons/arrowRight.svg new file mode 100644 index 0000000..1e578a3 --- /dev/null +++ b/whisk/assets/icons/arrowRight.svg @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg width="800px" height="800px" viewBox="0 -6.5 38 38" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + + <title>right-arrow + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/whisk/assets/icons/chevron-down.svg b/whisk/assets/icons/chevron-down.svg new file mode 100644 index 0000000..9b2dcc1 --- /dev/null +++ b/whisk/assets/icons/chevron-down.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/chevronLeft.svg b/whisk/assets/icons/chevronLeft.svg new file mode 100644 index 0000000..045a093 --- /dev/null +++ b/whisk/assets/icons/chevronLeft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/whisk/assets/icons/chevronRight.svg b/whisk/assets/icons/chevronRight.svg new file mode 100644 index 0000000..f2a6bc2 --- /dev/null +++ b/whisk/assets/icons/chevronRight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/whisk/assets/icons/circle-info.svg b/whisk/assets/icons/circle-info.svg new file mode 100644 index 0000000..8828c26 --- /dev/null +++ b/whisk/assets/icons/circle-info.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/close-alt.svg b/whisk/assets/icons/close-alt.svg new file mode 100644 index 0000000..7440a67 --- /dev/null +++ b/whisk/assets/icons/close-alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/whisk/assets/icons/close.svg b/whisk/assets/icons/close.svg new file mode 100644 index 0000000..421d698 --- /dev/null +++ b/whisk/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/crisisAlert.svg b/whisk/assets/icons/crisisAlert.svg new file mode 100644 index 0000000..4d47037 --- /dev/null +++ b/whisk/assets/icons/crisisAlert.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/whisk/assets/icons/download.svg b/whisk/assets/icons/download.svg new file mode 100644 index 0000000..df6233f --- /dev/null +++ b/whisk/assets/icons/download.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/whisk/assets/icons/expand.svg b/whisk/assets/icons/expand.svg new file mode 100644 index 0000000..c0c66e5 --- /dev/null +++ b/whisk/assets/icons/expand.svg @@ -0,0 +1 @@ + diff --git a/whisk/assets/icons/facebook.svg b/whisk/assets/icons/facebook.svg new file mode 100644 index 0000000..94ea2c1 --- /dev/null +++ b/whisk/assets/icons/facebook.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/home.svg b/whisk/assets/icons/home.svg new file mode 100644 index 0000000..657b7af --- /dev/null +++ b/whisk/assets/icons/home.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/whisk/assets/icons/instagram.svg b/whisk/assets/icons/instagram.svg new file mode 100644 index 0000000..e4318d5 --- /dev/null +++ b/whisk/assets/icons/instagram.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/linkedin.svg b/whisk/assets/icons/linkedin.svg new file mode 100644 index 0000000..6bed756 --- /dev/null +++ b/whisk/assets/icons/linkedin.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/list.svg b/whisk/assets/icons/list.svg new file mode 100644 index 0000000..d2085ee --- /dev/null +++ b/whisk/assets/icons/list.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/menu-close.svg b/whisk/assets/icons/menu-close.svg new file mode 100644 index 0000000..3201bc6 --- /dev/null +++ b/whisk/assets/icons/menu-close.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/menu.svg b/whisk/assets/icons/menu.svg new file mode 100644 index 0000000..69f2c93 --- /dev/null +++ b/whisk/assets/icons/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/pause.svg b/whisk/assets/icons/pause.svg new file mode 100644 index 0000000..641a58e --- /dev/null +++ b/whisk/assets/icons/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/whisk/assets/icons/play.svg b/whisk/assets/icons/play.svg new file mode 100644 index 0000000..495fd0f --- /dev/null +++ b/whisk/assets/icons/play.svg @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/whisk/assets/icons/programs-all.svg b/whisk/assets/icons/programs-all.svg new file mode 100644 index 0000000..c230a22 --- /dev/null +++ b/whisk/assets/icons/programs-all.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/whisk/assets/icons/programs-by-category.svg b/whisk/assets/icons/programs-by-category.svg new file mode 100644 index 0000000..f57f57d --- /dev/null +++ b/whisk/assets/icons/programs-by-category.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/whisk/assets/icons/programs-switch-view.svg b/whisk/assets/icons/programs-switch-view.svg new file mode 100644 index 0000000..3f8c3a5 --- /dev/null +++ b/whisk/assets/icons/programs-switch-view.svg @@ -0,0 +1,4 @@ + + + + diff --git a/whisk/assets/icons/search.svg b/whisk/assets/icons/search.svg new file mode 100644 index 0000000..7f88f07 --- /dev/null +++ b/whisk/assets/icons/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/starburst.svg b/whisk/assets/icons/starburst.svg new file mode 100644 index 0000000..1ebb1b5 --- /dev/null +++ b/whisk/assets/icons/starburst.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/whisk/assets/icons/submenu.svg b/whisk/assets/icons/submenu.svg new file mode 100644 index 0000000..767de0b --- /dev/null +++ b/whisk/assets/icons/submenu.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/triangle-exclamation.svg b/whisk/assets/icons/triangle-exclamation.svg new file mode 100644 index 0000000..43ef0d9 --- /dev/null +++ b/whisk/assets/icons/triangle-exclamation.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/twitter.svg b/whisk/assets/icons/twitter.svg new file mode 100644 index 0000000..151a319 --- /dev/null +++ b/whisk/assets/icons/twitter.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/icons/xmark.svg b/whisk/assets/icons/xmark.svg new file mode 100644 index 0000000..1a2f3b6 --- /dev/null +++ b/whisk/assets/icons/xmark.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/whisk/assets/icons/youtube.svg b/whisk/assets/icons/youtube.svg new file mode 100644 index 0000000..992d54a --- /dev/null +++ b/whisk/assets/icons/youtube.svg @@ -0,0 +1,3 @@ + + + diff --git a/whisk/assets/images/.gitkeep b/whisk/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/assets/video/.gitkeep b/whisk/assets/video/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/config/.env.example b/whisk/config/.env.example new file mode 100644 index 0000000..f9a1e67 --- /dev/null +++ b/whisk/config/.env.example @@ -0,0 +1,4 @@ + +# Added by Figma Token Engine +FIGMA_PERSONAL_ACCESS_TOKEN="" # Your personal Figma Personal Access token https://www.figma.com/developers/api#access-tokens +FIGMA_FILE_URL="" # URL of the Figma file with the tokens diff --git a/whisk/config/configma.json b/whisk/config/configma.json new file mode 100644 index 0000000..25dd6db --- /dev/null +++ b/whisk/config/configma.json @@ -0,0 +1,13 @@ +{ + "url": "https://www.figma.com/file/eo87m50yAw3P8iXTypxw4I/Four-Kitchens-UI-Kit?node-id=", + "audioPlayer": "22343%3A575", + "button": "22077%3A27", + "borderRadius": "21931%3A684", + "border": "22092%3A24", + "breakpoint": "21930%3A594", + "lineHeight": "22135%3A97", + "opacity": "22039%3A329", + "size": "3131%3A24", + "spacing": "22039%3A177", + "typeScale": "22135%3A31" +} diff --git a/whisk/config/emulsify-core/.prettierignore b/whisk/config/emulsify-core/.prettierignore new file mode 100644 index 0000000..c45aa50 --- /dev/null +++ b/whisk/config/emulsify-core/.prettierignore @@ -0,0 +1,4 @@ +dist +.out +.coverage +*.min.js \ No newline at end of file diff --git a/whisk/config/emulsify-core/eslintrc.config.json b/whisk/config/emulsify-core/eslintrc.config.json new file mode 100644 index 0000000..8c43050 --- /dev/null +++ b/whisk/config/emulsify-core/eslintrc.config.json @@ -0,0 +1,17 @@ +{ + "extends": [ + "../../node_modules/@emulsify/core/config/eslintrc.config.json" + ], + "globals": { + "Drupal": true, + "drupalSettings": true, + "drupalTranslations": true, + "domready": true, + "jQuery": true, + "_": true, + "matchMedia": true, + "Backbone": true, + "Modernizr": true, + "CKEDITOR": true + } +} diff --git a/whisk/config/emulsify-core/prettierrc.config.json b/whisk/config/emulsify-core/prettierrc.config.json new file mode 100644 index 0000000..a20502b --- /dev/null +++ b/whisk/config/emulsify-core/prettierrc.config.json @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/whisk/config/emulsify-core/storybook/example.theme.js b/whisk/config/emulsify-core/storybook/example.theme.js new file mode 100644 index 0000000..2df2913 --- /dev/null +++ b/whisk/config/emulsify-core/storybook/example.theme.js @@ -0,0 +1,37 @@ +// Documentation on theming Storybook: https://storybook.js.org/docs/configurations/theming/ +import { create } from '@storybook/theming'; + +export default create({ + base: 'dark', + + // UI + appBg: '#0000ff', + appContentBg: '#eee', + appBorderColor: '#ff0000', + appBorderRadius: 4, + + // Typography + fontBase: '"Mona Sans", sans-serif', + fontCode: 'monospace', + + // Text colors + textColor: 'white', + textInverseColor: 'rgba(255,255,255,0.9)', + textMutedColor: '#E6F5FC', + + // Toolbar default and active colors + barTextColor: '#0000ff', + barSelectedColor: '#eeff00', + barBg: '#eeff00', + + // Form colors + inputBg: 'red', + inputBorder: 'silver', + inputTextColor: '#dddddd', + inputBorderRadius: 4, + // Branding + brandTitle: 'Emulsify', + brandUrl: 'https://emulsify.info', + brandImage: + 'https://raw.githubusercontent.com/fourkitchens/emulsify-core/main/assets/images/emulsify-logo-sb.svg?token=GHSAT0AAAAAACIEXLVC5R3KBCX6HGKGTBBSZNYFWMA', +}); diff --git a/whisk/config/emulsify-core/storybook/main.js b/whisk/config/emulsify-core/storybook/main.js new file mode 100644 index 0000000..d3f3709 --- /dev/null +++ b/whisk/config/emulsify-core/storybook/main.js @@ -0,0 +1,37 @@ +// Pass an empty configOverrides by default. +const configOverrides = { + staticDirs: [ + '../../../../assets/images', + '../../../../assets/icons', + '../../../../assets/textures', + '../../../../dist', + ], +}; + +// Uncomment the following section to override the default Emulsify Core configuration. +// Doing so is a complete override so no configuration from Emulsify Core's main.js will be inherited. +// See https://storybook.js.org/docs/7/configure for details. +// const configOverrides = { +// stories: [ +// '../../../../src/**/*.stories.@(js|jsx|ts|tsx)', +// ], +// addons: [ +// '../../../@storybook/addon-a11y', +// '../../../@storybook/addon-links', +// '../../../@storybook/addon-essentials', +// '../../../@storybook/addon-themes', +// '../../../@storybook/addon-styling-webpack' +// ], +// core: { +// builder: 'webpack5', +// }, +// framework: { +// name: '@storybook/html-webpack5', +// options: {}, +// }, +// docs: { +// autodocs: true, +// }, +// }; + +module.exports = {configOverrides}; diff --git a/whisk/config/emulsify-core/storybook/preview.js b/whisk/config/emulsify-core/storybook/preview.js new file mode 100644 index 0000000..8b927e3 --- /dev/null +++ b/whisk/config/emulsify-core/storybook/preview.js @@ -0,0 +1,23 @@ +// export global parameters as config overrides. +// This is useful for reorganizing your stories. +// See https://storybook.js.org/docs/writing-stories/parameters#story-parameters. +export const overrideParams = { + actions: { argTypesRegex: '^on[A-Z].*' }, + layout: 'fullscreen', + options: { + storySort: { + method: 'alphabetical', + order: [ + 'Docs', + ['Intro', '*'], + 'Tokens', + ['Readme', '*'], + 'Components', + ['Readme', '*'], + 'Layout', + ['Readme', '*'], + ], + locales: 'en-US', + }, + }, +}; diff --git a/whisk/config/emulsify-core/stylelintrc.config.json b/whisk/config/emulsify-core/stylelintrc.config.json new file mode 100644 index 0000000..7bad9d6 --- /dev/null +++ b/whisk/config/emulsify-core/stylelintrc.config.json @@ -0,0 +1,8 @@ +{ + "extends": [ + "../../node_modules/@emulsify/core/config/.stylelintrc.json" + ], + "rules": { + "no-duplicate-selectors": null + } +} diff --git a/whisk/config/emulsify-core/webpack/example.babel.config.js b/whisk/config/emulsify-core/webpack/example.babel.config.js new file mode 100644 index 0000000..495063e --- /dev/null +++ b/whisk/config/emulsify-core/webpack/example.babel.config.js @@ -0,0 +1,18 @@ +module.exports = (api) => { + api.cache(true); + + const presets = [ + [ + '@babel/preset-env', + { + useBuiltIns: 'entry', + corejs: 3, + }, + ], + ['minify', { builtIns: false }], + ]; + + const comments = false; + + return { presets, comments }; +}; diff --git a/whisk/config/emulsify-core/webpack/example.postcss.config.js b/whisk/config/emulsify-core/webpack/example.postcss.config.js new file mode 100644 index 0000000..6e64681 --- /dev/null +++ b/whisk/config/emulsify-core/webpack/example.postcss.config.js @@ -0,0 +1,5 @@ +const autoPrefixer = require('autoprefixer'); + +module.exports = { + plugins: [autoPrefixer()], +}; diff --git a/whisk/config/sd.config.json b/whisk/config/sd.config.json new file mode 100644 index 0000000..2387529 --- /dev/null +++ b/whisk/config/sd.config.json @@ -0,0 +1,26 @@ +{ + "source": [ + "dist/tokens/transformed.tokens.json" + ], + "log": { + "verbosity": "verbose" + }, + "platforms": { + "scss": { + "transforms": [ + "attribute/cti", + "name/kebab", + "time/seconds", + "html/icon", + "color/css" + ], + "files": [ + { + "destination": "src/tokens/_tokens.scss", + "format": "scss/variables", + "mapName": "my-tokens" + } + ] + } + } +} \ No newline at end of file diff --git a/whisk/functions.php b/whisk/functions.php new file mode 100644 index 0000000..bd21126 --- /dev/null +++ b/whisk/functions.php @@ -0,0 +1,18 @@ + tag in the document head, and expect WordPress to + * provide it for us. + */ + add_theme_support( 'title-tag' ); + + /* + * Enable support for Post Thumbnails on posts and pages. + * + * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ + */ + add_theme_support( 'post-thumbnails' ); + + /* + * Switch default core markup for search form, comment form, and comments + * to output valid HTML5. + */ + add_theme_support( + 'html5', + array( + 'comment-form', + 'comment-list', + 'gallery', + 'caption', + ) + ); + + /* + * Enable support for Post Formats. + * + * See: https://codex.wordpress.org/Post_Formats + */ + add_theme_support( + 'post-formats', + array( + 'aside', + 'image', + 'video', + 'quote', + 'link', + 'gallery', + 'audio', + ) + ); + + add_theme_support( 'menus' ); + } + + /** + * This is where you can add your own functions to twig. + * + * @param Twig\Environment $twig get extension. + */ + public function add_to_twig( $twig ) { + /** + * Required when you want to use Twig’s template_from_string. + * @link https://twig.symfony.com/doc/3.x/functions/template_from_string.html + */ + // $twig->addExtension( new Twig\Extension\StringLoaderExtension() ); + + $twig->addFilter( new Twig\TwigFilter( 'myfoo', [ $this, 'myfoo' ] ) ); + + return $twig; + } + + /** + * Updates Twig environment options. + * + * @link https://twig.symfony.com/doc/2.x/api.html#environment-options + * + * \@param array $options An array of environment options. + * + * @return array + */ + function update_twig_environment_options( $options ) { + // $options['autoescape'] = true; + + return $options; + } +} diff --git a/whisk/package.json b/whisk/package.json new file mode 100644 index 0000000..b29dc80 --- /dev/null +++ b/whisk/package.json @@ -0,0 +1,45 @@ +{ + "name": "whisk", + "version": "1.0.0", + "description": "Storybook development + Webpack Build", + "keywords": [ + "component library", + "design system", + "wordpress", + "emulsify", + "storybook", + "styleguide" + ], + "author": "Four Kitchens ", + "license": "ISC", + "type": "module", + "scripts": { + "a11y": "npm run storybook-build && node_modules/@emulsify/core/scripts/a11y.js -r", + "build": "webpack --config node_modules/@emulsify/core/config/webpack/webpack.prod.js", + "build-dev": "webpack --config node_modules/@emulsify/core/config/webpack/webpack.dev.js", + "coverage": "npm run test && open-cli .coverage/lcov-report/index.html", + "develop": "concurrently --raw \"npm run webpack\" \"npm run storybook\"", + "format": "npm run lint-fix; npm run prettier-fix", + "husky:commit-msg": "commitlint --edit $1", + "husky:pre-commit": "lint-staged", + "lint": "npm run lint-js; npm run lint-styles", + "lint-fix": "npm run lint-js -- --fix; npm run lint-styles -- --fix", + "lint-js": "eslint --config ./config/emulsify-core/eslintrc.config.json --no-eslintrc --no-error-on-unmatched-pattern ./src", + "lint-staged": "lint-staged", + "lint-styles": "stylelint --config ./config/emulsify-core/stylelintrc.config.json './src/**/*.scss'", + "prettier": "prettier --config ./config/emulsify-core/prettierrc.config.json --ignore-path ./config/emulsify-core/.prettierignore --ignore-unknown \"./src/**/*.{js,yml,scss,md}\"", + "prettier-fix": "prettier --config ./config/emulsify-core/prettierrc.config.json --ignore-path ./config/emulsify-core/.prettierignore --write --ignore-unknown \"./src/**/*.{js,yml,scss,md}\"", + "storybook": "storybook dev -c node_modules/@emulsify/core/.storybook --ci -p 6006", + "storybook-build": "npm run build && storybook build -c node_modules/@emulsify/core/.storybook -o .out", + "storybook-deploy": "storybook-to-ghpages -o .out", + "style-dictionary:build": "style-dictionary build --config ./config/sd.config.json", + "test": "jest --coverage --config ./config/jest.config.js", + "tokens:build": "npm run tokens:transform && npm run style-dictionary:build", + "tokens:transform": "node ./src/util/tokensTransform.js -- './src/tokens/figma-export/**/*.json' './dist/tokens'", + "twatch": "jest --no-coverage --watch --verbose", + "webpack": "webpack --watch --config node_modules/@emulsify/core/config/webpack/webpack.dev.js" + }, + "dependencies": { + "@emulsify/core": "^2.7.1", + } +} diff --git a/whisk/project.emulsify.json b/whisk/project.emulsify.json new file mode 100644 index 0000000..b1f47c9 --- /dev/null +++ b/whisk/project.emulsify.json @@ -0,0 +1,10 @@ +{ + "project": { + "platform": "wordpress", + "name": "whisk", + "machineName": "whisk" + }, + "starter": { + "repository": "https://github.com/emulsify-ds/emulsify-wordpress-theme/" + } +} diff --git a/whisk/screenshot.png b/whisk/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..43e603ec4f0549c6ace26705bebf79b2a5d5c5be GIT binary patch literal 11781 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 literal 0 HcmV?d00001 diff --git a/whisk/style.css b/whisk/style.css new file mode 100644 index 0000000..3a211f4 --- /dev/null +++ b/whisk/style.css @@ -0,0 +1,7 @@ +/* + * Theme Name: whisk + * Author: you + * Template: emulsify + * Description: A subtheme build upon the Emulsify Design System. + * Version: 0.1 +*/ From 84b340e2fdfd590d73fe1d3ed864dda19c08047e Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Thu, 5 Jun 2025 12:21:20 -0500 Subject: [PATCH 03/55] feat: re-organize default templates, auto-register blocks, and twig namespacing --- 404.php | 4 +- README.md | 6 +- archive.php | 29 +- author.php | 10 +- components/.gitkeep | 0 functions.php | 130 +--- includes/class-emulsify-cli.php | 174 +++++ includes/class-emulsify-theme.php | 218 ++++++ includes/global-assets.php | 61 ++ includes/register-blocks.php | 100 +++ includes/twig-namespaces.php | 15 + index.php | 8 +- page.php | 5 +- search.php | 10 +- single.php | 10 +- src/StarterSite.php | 186 ----- src/components/.gitkeep | 0 templates/404.twig | 2 +- templates/archive.twig | 6 +- templates/author.twig | 4 +- templates/index.twig | 6 +- templates/layouts/base.twig | 12 +- templates/page.twig | 2 +- templates/partials/comment.twig | 4 +- templates/partials/menu.twig | 2 +- templates/partials/tease-post.twig | 2 +- templates/search.twig | 6 +- templates/single-password.twig | 2 +- templates/single.twig | 6 +- theme.json | 663 ++++++++++++++++++ whisk/.nvm | 1 + whisk/components/.gitkeep | 0 .../emulsify-core/storybook/example.theme.js | 37 - whisk/config/emulsify-core/storybook/main.js | 13 +- .../emulsify-core/storybook/manager-head.html | 0 .../emulsify-core/storybook/preview-head.html | 0 .../config/emulsify-core/storybook/preview.js | 26 +- whisk/config/emulsify-core/storybook/theme.js | 40 ++ whisk/functions.php | 24 +- whisk/includes/site.php | 133 ---- whisk/includes/twig-namespaces.php | 17 + whisk/package.json | 25 +- whisk/src/components/.gitkeep | 0 whisk/src/foundation/.gitkeep | 0 whisk/src/layout/.gitkeep | 0 whisk/templates/404.twig | 5 + whisk/templates/archive.twig | 15 + whisk/templates/author.twig | 7 + whisk/templates/index.twig | 16 + whisk/templates/layouts/base.twig | 43 ++ whisk/templates/page.twig | 12 + whisk/templates/partials/comment-form.twig | 32 + whisk/templates/partials/comment.twig | 19 + whisk/templates/partials/footer.twig | 1 + whisk/templates/partials/head.twig | 9 + whisk/templates/partials/menu.twig | 12 + whisk/templates/partials/pagination.twig | 44 ++ whisk/templates/partials/tease-post.twig | 16 + whisk/templates/partials/tease.twig | 9 + whisk/templates/search.twig | 18 + whisk/templates/single-password.twig | 17 + whisk/templates/single.twig | 37 + 62 files changed, 1711 insertions(+), 600 deletions(-) create mode 100644 components/.gitkeep create mode 100644 includes/class-emulsify-cli.php create mode 100644 includes/class-emulsify-theme.php create mode 100644 includes/global-assets.php create mode 100644 includes/register-blocks.php create mode 100644 includes/twig-namespaces.php delete mode 100644 src/StarterSite.php create mode 100644 src/components/.gitkeep create mode 100644 theme.json create mode 100644 whisk/.nvm create mode 100644 whisk/components/.gitkeep delete mode 100644 whisk/config/emulsify-core/storybook/example.theme.js create mode 100644 whisk/config/emulsify-core/storybook/manager-head.html create mode 100644 whisk/config/emulsify-core/storybook/preview-head.html create mode 100644 whisk/config/emulsify-core/storybook/theme.js delete mode 100644 whisk/includes/site.php create mode 100644 whisk/includes/twig-namespaces.php create mode 100644 whisk/src/components/.gitkeep create mode 100644 whisk/src/foundation/.gitkeep create mode 100644 whisk/src/layout/.gitkeep create mode 100644 whisk/templates/404.twig create mode 100644 whisk/templates/archive.twig create mode 100644 whisk/templates/author.twig create mode 100644 whisk/templates/index.twig create mode 100644 whisk/templates/layouts/base.twig create mode 100644 whisk/templates/page.twig create mode 100644 whisk/templates/partials/comment-form.twig create mode 100644 whisk/templates/partials/comment.twig create mode 100644 whisk/templates/partials/footer.twig create mode 100644 whisk/templates/partials/head.twig create mode 100644 whisk/templates/partials/menu.twig create mode 100644 whisk/templates/partials/pagination.twig create mode 100644 whisk/templates/partials/tease-post.twig create mode 100644 whisk/templates/partials/tease.twig create mode 100644 whisk/templates/search.twig create mode 100644 whisk/templates/single-password.twig create mode 100644 whisk/templates/single.twig diff --git a/404.php b/404.php index a51168d..91d13bd 100644 --- a/404.php +++ b/404.php @@ -1,6 +1,8 @@ $title, - ] + array( + 'title' => $page_title, + ) ); Timber::render( $templates, $context ); diff --git a/author.php b/author.php index dae48bc..a829c20 100644 --- a/author.php +++ b/author.php @@ -1,8 +1,8 @@ name() ); } -Timber::render( [ 'templates/author.twig', 'templates/archive.twig' ], $context ); +Timber::render( + array( 'templates/author.twig', 'templates/archive.twig' ), + $context +); diff --git a/components/.gitkeep b/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/functions.php b/functions.php index bea8653..657fe2e 100644 --- a/functions.php +++ b/functions.php @@ -1,124 +1,18 @@ + * @since 1.0.0 */ -class Emulsify_CLI -{ - /** - * Creates an Emulsify sub-theme. - * - * ## OPTIONS - * - * - * : The name of the new theme. - * - * ## EXAMPLES - * - * wp emulsify new-theme - * - * @when after_wp_load - */ - public function __invoke($args, $assoc_args) - { - list($theme_name) = $args; - $machine_name = $this->convertLabelToMachineName($theme_name); - - // Define source (base theme) and destination (new theme) paths. - $source = get_theme_root() . '/emulsify/whisk'; - $destination = get_theme_root() . '/' . $machine_name; - - // Validate source directory existence. - if (!is_dir($source)) { - WP_CLI::error("The source directory ($source) does not exist."); - } - - // Prevent overwriting an existing theme. - if (is_dir($destination)) { - WP_CLI::error("The destination directory ($destination) already exists."); - } - - // Recursively copy theme files and directories. - $this->copyTheme($source, $destination, 'whisk', $machine_name); - - // Replace instances of "whisk" with the new theme name inside file contents. - $this->renameInstances($destination, 'whisk', $machine_name); - - WP_CLI::success("Theme '$theme_name' has been created successfully."); - } - - /** - * Recursively copies a theme while renaming instances of the base theme name in filenames. - * - * @param string $src Source directory (original theme). - * @param string $dst Destination directory (new theme). - * @param string $search The original theme name to search for. - * @param string $replace The new theme name to replace with. - */ - private function copyTheme(string $src, string $dst, string $search, string $replace): void - { - $dir = opendir($src); - @mkdir($dst, 0755, true); // Ensure directory is created with correct permissions. - - while (($file = readdir($dir)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - - $src_file = $src . '/' . $file; - $dst_file = $dst . '/' . str_replace($search, $replace, $file); - - // If it's a directory, recursively copy its contents. - if (is_dir($src_file)) { - $this->copyTheme($src_file, $dst_file, $search, $replace); - } else { - copy($src_file, $dst_file); - } - } - - closedir($dir); - } - - /** - * Recursively replaces all instances of a string inside files within a directory. - * - * @param string $dir The directory to search within. - * @param string $search The string to search for. - * @param string $replace The string to replace it with. - */ - private function renameInstances(string $dir, string $search, string $replace): void - { - $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); - - foreach ($files as $file) { - if ($file->isFile()) { - $contents = file_get_contents($file->getRealPath()); - $updated_contents = str_replace($search, $replace, $contents); - file_put_contents($file->getRealPath(), $updated_contents); - } - } - } - /** - * Converts a given theme name into a machine-friendly format. - * - * @param string $label The human-readable theme name. - * @return string The machine-friendly version of the theme name. - */ - private function convertLabelToMachineName(string $label): string - { - return strtolower(preg_replace('/[^a-z0-9_]+/ui', '_', $label)); - } -} -// Register the WP-CLI command. -WP_CLI::add_command('emulsify', 'Emulsify_CLI'); +require 'includes/class-emulsify-cli.php'; +require 'includes/class-emulsify-theme.php'; +require 'includes/twig-namespaces.php'; +require 'includes/register-blocks.php'; +require 'includes/global-assets.php'; diff --git a/includes/class-emulsify-cli.php b/includes/class-emulsify-cli.php new file mode 100644 index 0000000..f336cff --- /dev/null +++ b/includes/class-emulsify-cli.php @@ -0,0 +1,174 @@ + + * + * @param array $args Positional arguments passed to the command. + * @param array $assoc_args Named (associative) arguments passed to the command. + */ + public function __invoke( $args, $assoc_args ) { + return $this->generate( $args, $assoc_args ); + } + + /** + * Generates a new Emulsify sub-theme. + * + * @param array $args Positional arguments passed to the command. + * @param array $assoc_args Named (associative) arguments passed to the command. + */ + public function generate( $args, $assoc_args ) { + // 1) Validate & sanitize the human label. + $label = ( isset( $args[0] ) && is_string( $args[0] ) && trim( $args[0] ) !== '' ) + ? sanitize_text_field( $args[0] ) + : 'New Theme'; + + // 2) Build a machine-friendly slug. + $machine_name = self::convert_label_to_machine_name( $label ); + + WP_CLI::log( sprintf( 'Cooking up a sub-theme: "%s" from Emulsify', $label, $machine_name ) ); + + // 3) Point at *only* the `whisk` folder inside the Emulsify theme. + $parent = ( isset( $assoc_args['parent'] ) && is_string( $assoc_args['parent'] ) ) + ? sanitize_key( $assoc_args['parent'] ) + : 'emulsify'; + // Adjust this path if your whisk theme lives somewhere else inside emulsify. + $sub_dir = 'whisk'; + $src = get_theme_root() . "/{$parent}/{$sub_dir}"; + + if ( ! is_dir( $src ) ) { + WP_CLI::error( sprintf( 'Source directory not found: %s', $src ) ); + } + + // 4) Copy it into a new folder named after your slug. + $dst = get_theme_root() . "/{$machine_name}"; + if ( ! $this->copy_theme( $src, $dst ) ) { + WP_CLI::error( sprintf( 'Failed generating sub-theme "%s"', $src, $dst ) ); + } + + // 5) Rename all occurrences of “whisk” inside file/folder names and file contents. + $old_slug = basename( $sub_dir ); // “whisk” + $this->rename_instances( $dst, $old_slug, $machine_name ); + + WP_CLI::success( sprintf( "Ding! Sub-theme '%s' created at '%s'.", $label, $dst ) ); + } + + /** + * Copies the parent theme into the sub-theme directory. + * + * @param string $src Source directory path. + * @param string $dst Destination directory path. + * @return bool True on success, false on failure. + */ + private function copy_theme( string $src, string $dst ): bool { + // Ensure destination exists. + if ( ! wp_mkdir_p( $dst ) ) { + return false; + } + + // Load WP Filesystem API if not already. + if ( ! function_exists( 'copy_dir' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + // Initialize the filesystem, so $wp_filesystem is not null. + WP_Filesystem(); + + global $wp_filesystem; + // Now safely call copy_dir(). + $copied = copy_dir( $src, $dst ); + if ( is_wp_error( $copied ) ) { + return false; + } + return true; + } + + /** + * Renames files, directories, and file contents from old slug to new slug. + * + * @param string $dir Directory to process recursively. + * @param string $old_slug Original theme slug. + * @param string $new_slug New theme slug. + */ + private function rename_instances( string $dir, string $old_slug, string $new_slug ): void { + // Bail if there’s nothing here. + if ( ! is_dir( $dir ) ) { + return; + } + + // Initialize fs if you haven’t already... + global $wp_filesystem; + if ( empty( $wp_filesystem ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + WP_Filesystem(); + } + + // Safely build iterator. + try { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::SELF_FIRST + ); + } catch ( \UnexpectedValueException $e ) { + // Directory unreadable or missing – just skip. + return; + } + + foreach ( $iterator as $item ) { + $path = $item->getPathname(); + + // Rename directories and files that contain the slug. + if ( false !== strpos( $path, $old_slug ) ) { + $new_path = str_replace( $old_slug, $new_slug, $path ); + if ( ! $wp_filesystem->move( $path, $new_path ) ) { + WP_CLI::warning( sprintf( 'Could not move %s to %s.', $path, $new_path ) ); + continue; + } + $path = $new_path; + } + + // If this is a file, replace slug inside its contents. + if ( $item->isFile() ) { + $contents = $wp_filesystem->get_contents( $path ); + $updated = str_replace( $old_slug, $new_slug, $contents ); + if ( $updated !== $contents ) { + $wp_filesystem->put_contents( $path, $updated, FS_CHMOD_FILE ); + } + } + } + } + + /** + * Converts a human-readable theme name into a machine-friendly slug. + * + * @param string $label The human-readable theme name. + * @return string The machine-friendly version. + */ + private static function convert_label_to_machine_name( string $label ): string { + // Lowercase and replace non-alphanumerics with underscores. + $slug = preg_replace( '/[^a-z0-9]+/ui', '_', strtolower( $label ) ); + // Collapse consecutive underscores. + $slug = preg_replace( '/_{2,}/', '_', $slug ); + // Trim leading or trailing underscores. + $slug = preg_replace( '/^_+|_+$/', '', $slug ); + return $slug; + } +} + +// Register the `emulsify` WP-CLI command. +WP_CLI::add_command( 'emulsify', 'Emulsify_CLI' ); diff --git a/includes/class-emulsify-theme.php b/includes/class-emulsify-theme.php new file mode 100644 index 0000000..a712da1 --- /dev/null +++ b/includes/class-emulsify-theme.php @@ -0,0 +1,218 @@ + [ + // 'callable' => [ $this, 'my_filter_callback' ], + // ],. + ); + return array_merge( $filters, $additional ); + } + + /** + * Registers custom Twig functions. + * + * @param array $functions Existing Twig functions. + * @return array Modified functions. + */ + public function add_functions_to_twig( $functions ) { + $additional = array( + 'bem' => array( + 'callable' => array( $this, 'bem' ), + 'needs_context' => true, + 'is_safe' => array( 'html' ), + ), + 'add_attributes' => array( + 'callable' => array( $this, 'add_attributes' ), + 'needs_context' => true, + 'is_safe' => array( 'html' ), + ), + ); + return array_merge( $functions, $additional ); + } + + /** + * BEM helper for Timber/Twig. + * + * Usage in Twig: + *
    + * + * Or via an options array/object: + * {{ bem({ + * block: 'button', + * element: 'icon', + * modifiers:['small'], + * extra: ['js-clickable'] + * }) }} + * + * @param string|array|object $base_class Either the base class name (string) or an options array/object. + * @param array|string $modifiers Modifier suffixes. + * @param string $blockname Optional block name for elements (makes block__element). + * @param array|string $extra Any extra classes to append. + * + * @return string Safe HTML attribute string, e.g. class="block block--mod extra". + */ + public function bem( $base_class, $modifiers = array(), $blockname = '', $extra = array() ) { + // Allow passing an options array/object as single arg. + if ( is_object( $base_class ) || ( is_array( $base_class ) && isset( $base_class['block'] ) ) ) { + $opts = (array) $base_class; + $base_class = $opts['block'] ?? ''; + $blockname = $opts['element'] ?? ''; + $modifiers = $opts['modifiers'] ?? array(); + $extra = $opts['extra'] ?? array(); + } + + // 1) Normalize to flat arrays + $modifiers = is_array( $modifiers ) ? $modifiers : array( $modifiers ); + $extra = is_array( $extra ) ? $extra : array( $extra ); + + // 2) Filter out non‐scalar values so we never get an array element inside $classes + $modifiers = array_filter( + $modifiers, + function ( $m ) { + return is_scalar( $m ) && '' !== (string) $m; + } + ); + $extra = array_filter( + $extra, + function ( $e ) { + return is_scalar( $e ) && '' !== (string) $e; + } + ); + + // 3) Build the BEM base and modifier classes + $classes = array(); + $base = $blockname ? "{$blockname}__{$base_class}" : $base_class; + $classes[] = $base; + foreach ( $modifiers as $mod ) { + $classes[] = "{$base}--{$mod}"; + } + // 4) Any extra non‐BEM classes + foreach ( $extra as $e ) { + $classes[] = (string) $e; + } + + // 5) Return a single, escaped class="…" string + $class_string = implode( ' ', $classes ); + return sprintf( 'class="%s"', esc_attr( $class_string ) ); + } + + /** + * Build a string of HTML attributes from an associative array. + * + * Usage in Twig: + * + * + * @param array $context Timber context (unused here). + * @param array $additional_attributes ['attr' => 'value' or [ 'a','b' ], 'custom'=>'x=y' ]. + * + * @return string A string like: id="email" class="input input--large" data-foo="bar" + */ + public function add_attributes( $context, $additional_attributes = array() ) { + $attributes = array(); + + foreach ( $additional_attributes as $key => $value ) { + switch ( gettype( $value ) ) { + case 'array': + foreach ( $value as $index => $item ) { + // Handle bem() output. + if ( $item instanceof Attribute ) { + // Remove the item. + unset( $value[ $index ] ); + $value = array_merge( $value, $item->toArray()[ $key ] ); + } + } + break; + + default: + // Set value to an empty string. + $value = ''; + break; + } + // Merge additional attribute values with existing ones. + if ( $context['attributes']->offsetExists( $key ) ) { + $existing_attribute = $context['attributes']->offsetGet( $key )->value(); + $value = array_merge( $existing_attribute, $value ); + } + $context['attributes']->setAttribute( $key, $value ); + } + + // Set all attributes. + foreach ( $context['attributes'] as $key => $value ) { + $attributes->setAttribute( $key, $value ); + // Remove this attribute from context so it doesn't filter down to child + // elements. + $context['attributes']->removeAttribute( $key ); + } + + return $attributes; + } +} + +Timber::init(); + +// Lets get cooking. +new Emulsify_Theme(); diff --git a/includes/global-assets.php b/includes/global-assets.php new file mode 100644 index 0000000..4904aac --- /dev/null +++ b/includes/global-assets.php @@ -0,0 +1,61 @@ +isFile() && 'css' === $file->getExtension() ) { + $filename = $file->getBasename(); + $handle = $context . '-' . basename( $filename, '.css' ); + + $relative_path = ltrim( str_replace( $base_path, '', $file->getPathname() ), '/' ); + $relative_path = str_replace( '\\', '/', $relative_path ); // Normalize for Windows. + $file_uri = trailingslashit( $base_uri ) . $relative_path; + + wp_enqueue_style( + $handle, + $file_uri, + array(), + filemtime( $file->getPathname() ) + ); + } + } +} + +/** + * Enqueue all global styles on the frontend. + */ +function emulsify_enqueue_frontend_styles() { + emulsify_enqueue_global_styles( 'emulsify' ); +} +add_action( 'wp_enqueue_scripts', 'emulsify_enqueue_frontend_styles' ); + +/** + * Enqueue all global styles in the block editor. + */ +function emulsify_enqueue_block_editor_styles() { + emulsify_enqueue_global_styles( 'emulsify-editor' ); +} +add_action( 'enqueue_block_assets', 'emulsify_enqueue_block_editor_styles' ); diff --git a/includes/register-blocks.php b/includes/register-blocks.php new file mode 100644 index 0000000..21a3490 --- /dev/null +++ b/includes/register-blocks.php @@ -0,0 +1,100 @@ +isDir() && ! $item->isDot() ) { + $slug = $item->getFilename(); + $json_path = $item->getPathname() . '/' . $slug . '.component.json'; + $twig_path = 'dist/components/' . $slug . '/' . $slug . '.twig'; + + // Skip if either the JSON config or Twig template file is missing. + if ( ! file_exists( $json_path ) || ! file_exists( get_stylesheet_directory() . '/' . $twig_path ) ) { + continue; + } + + // Load and decode the component's JSON configuration file. + $config = json_decode( file_get_contents( $json_path ), true ); + + // Skip invalid or incomplete JSON configs. + if ( ! is_array( $config ) || empty( $config['name'] ) ) { + continue; + } + + // Define base/default arguments for the block registration. + $block_args = array_merge( + array( + 'name' => 'emulsify/' . strtolower( ucwords( str_replace( '-', ' ', $slug ) ) ), + // Prefix the block name for uniqueness within Gutenberg. + 'title' => strtolower( ucwords( str_replace( '-', ' ', $slug ) ) ), + 'mode' => 'preview', + // Timber-compatible render callback. + 'render_callback' => 'render_twig_block', + // Custom key for locating the Twig template. + 'twig_template' => $twig_path, + ), + $config + ); + + // Register the block with ACF. + acf_register_block_type( $block_args ); + } + } +} +add_action( 'acf/init', 'register_emulsify_blocks' ); + +/** + * Timber render callback for ACF Gutenberg blocks. + * + * Passes block data and ACF fields into the specified Twig template for rendering. + * + * @param array $block The block settings and attributes. + * @param string $content The block's inner content (empty for dynamic blocks). + * @param bool $is_preview True during editor preview render. + * @param int $post_id The ID of the current post being edited or viewed. + * + * @return void + */ +function render_twig_block( $block, $content = '', $is_preview = false, $post_id = 0 ) { + // Get standard Timber context. + $context = Timber::context(); + $context['block'] = $block; + $context['fields'] = get_fields(); + + // Add preview mode flag to context. + if ( $is_preview ) { + $context['is_preview'] = true; + } + + // Try to get the template path from either block data or custom property. + if ( ! empty( $block['data']['twig_template'] ) ) { + Timber::render( $block['data']['twig_template'], $context ); + } elseif ( ! empty( $block['twig_template'] ) ) { + Timber::render( $block['twig_template'], $context ); + } else { + // Fallback message if no template is defined. + echo '

    Block rendering error: No template defined.

    '; + } +} diff --git a/includes/twig-namespaces.php b/includes/twig-namespaces.php new file mode 100644 index 0000000..27c93b3 --- /dev/null +++ b/includes/twig-namespaces.php @@ -0,0 +1,15 @@ +addPath( __DIR__ . '/../templates', 'emulsify-tpl' ); + $loader->addPath( __DIR__ . '/../components', 'components' ); + return $loader; + } +); diff --git a/index.php b/index.php index 619f59b..97f94fd 100644 --- a/index.php +++ b/index.php @@ -2,6 +2,8 @@ /** * The main template file * + * @package Emulsify + * * This is the most generic template file in a WordPress theme * and one of the two required files for a theme (the other being style.css). * It is used to display a page when nothing more specific matches a query. @@ -12,16 +14,16 @@ use Timber\Timber; -$templates = [ 'templates/index.twig' ]; +$templates = array( 'templates/index.twig' ); if ( is_home() ) { array_unshift( $templates, 'templates/front-page.twig', 'templates/home.twig' ); } $context = Timber::context( - [ + array( 'foo' => 'bar', - ] + ) ); Timber::render( $templates, $context ); diff --git a/page.php b/page.php index 34c142d..3f57f3f 100644 --- a/page.php +++ b/page.php @@ -2,10 +2,7 @@ /** * The template for displaying all pages. * - * This is the template that displays all pages by default. - * Please note that this is the WordPress construct of pages - * and that other 'pages' on your WordPress site will use a - * different template. + * @package Emulsify */ namespace App; diff --git a/search.php b/search.php index 9e1ac5f..43c9487 100644 --- a/search.php +++ b/search.php @@ -1,18 +1,18 @@ 'Search results for ' . get_search_query(), - ] + ) ); Timber::render( $templates, $context ); diff --git a/single.php b/single.php index df070cf..885d7c2 100644 --- a/single.php +++ b/single.php @@ -1,8 +1,8 @@ post_type . '.twig', 'templates/single.twig' ]; +$wp_post = $context['post']; +$templates = array( 'templates/single-' . $wp_post->post_type . '.twig', 'templates/single.twig' ); -if ( post_password_required( $post->ID ) ) { +if ( post_password_required( $wp_post->ID ) ) { $templates = 'templates/single-password.twig'; } diff --git a/src/StarterSite.php b/src/StarterSite.php deleted file mode 100644 index 834eaad..0000000 --- a/src/StarterSite.php +++ /dev/null @@ -1,186 +0,0 @@ - _x( 'Main menu', 'Backend - menu name', 'timber-starter' ), - ] - ); - - // Add default posts and comments RSS feed links to head. - add_theme_support( 'automatic-feed-links' ); - - /* - * Let WordPress manage the document title. - * By adding theme support, we declare that this theme does not use a - * hard-coded tag in the document head, and expect WordPress to - * provide it for us. - */ - add_theme_support( 'title-tag' ); - - /* - * Enable support for Post Thumbnails on posts and pages. - * - * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ - */ - add_theme_support( 'post-thumbnails' ); - - /* - * Switch default core markup for search form, comment form, and comments - * to output valid HTML5. - */ - add_theme_support( - 'html5', - [ - 'comment-form', - 'comment-list', - 'gallery', - 'caption', - ] - ); - - /* - * Enable support for Post Formats. - * - * See: https://codex.wordpress.org/Post_Formats - */ - add_theme_support( - 'post-formats', - [ - 'aside', - 'image', - 'video', - 'quote', - 'link', - 'gallery', - 'audio', - ] - ); - - add_theme_support( 'menus' ); - } - - /** - * This would return 'foo bar!'. - * - * @param string $text being 'foo', then returned 'foo bar!' - */ - public function myfoo( $text ) { - $text .= ' bar!'; - - return $text; - } - - /** - * This is where you can add your own functions to twig. - * - * @link https://timber.github.io/docs/v2/hooks/filters/#timber/twig/filters - * @param array $filters an array of Twig filters. - */ - public function add_filters_to_twig( $filters ) { - - $additional_filters = [ - 'myfoo' => [ - 'callable' => [ $this, 'myfoo' ], - ], - ]; - - return array_merge( $filters, $additional_filters ); - } - - - /** - * This is where you can add your own functions to twig. - * - * @link https://timber.github.io/docs/v2/hooks/filters/#timber/twig/functions - * @param array $functions an array of existing Twig functions. - */ - public function add_functions_to_twig( $functions ) { - $additional_functions = [ - 'get_theme_mod' => [ - 'callable' => 'get_theme_mod', - ], - ]; - - return array_merge( $functions, $additional_functions ); - } - - /** - * Updates Twig environment options. - * - * @see https://twig.symfony.com/doc/2.x/api.html#environment-options - * - * @param array $options an array of environment options - * - * @return array - */ - public function update_twig_environment_options( $options ) { - // $options['autoescape'] = true; - - return $options; - } -} diff --git a/src/components/.gitkeep b/src/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/templates/404.twig b/templates/404.twig index 1b2073c..fa4e23c 100644 --- a/templates/404.twig +++ b/templates/404.twig @@ -1,4 +1,4 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} Sorry, we couldn't find what you're looking for. diff --git a/templates/archive.twig b/templates/archive.twig index ae021ea..6f3c0f3 100644 --- a/templates/archive.twig +++ b/templates/archive.twig @@ -1,11 +1,11 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} {% for post in posts %} - {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} {% endfor %} - {% include 'partials/pagination.twig' with { + {% include 'templates/partials/pagination.twig' with { pagination: posts.pagination({ show_all: false, mid_size: 3, diff --git a/templates/author.twig b/templates/author.twig index 6d19761..0249fea 100644 --- a/templates/author.twig +++ b/templates/author.twig @@ -1,7 +1,7 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} {% for post in posts %} - {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} {% endfor %} {% endblock %} diff --git a/templates/index.twig b/templates/index.twig index b68fbd7..e610d48 100644 --- a/templates/index.twig +++ b/templates/index.twig @@ -1,12 +1,12 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} <h2>{{ foo }}</h2> {% for post in posts %} - {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} {% endfor %} - {% include 'partials/pagination.twig' with { + {% include 'templates/partials/pagination.twig' with { pagination: posts.pagination({ show_all: false, mid_size: 3, diff --git a/templates/layouts/base.twig b/templates/layouts/base.twig index 537c08d..20175ec 100644 --- a/templates/layouts/base.twig +++ b/templates/layouts/base.twig @@ -1,20 +1,20 @@ <!DOCTYPE html> <html {{ site.language_attributes }}> {% block head %} - {% include 'partials/head.twig' %} + {% include 'templates/partials/head.twig' %} {% endblock %} <body class="{{ body_class }}"> {{ function('wp_body_open') }} <a class="skip-link screen-reader-text" href="#content">{{ _e('Skip to content') }}</a> - <header class="header"> + <header> {% block header %} <div class="wrapper"> <h1 class="hdr-logo"> <a class="hdr-logo-link" href="{{ site.url }}">{{ site.name }}</a> </h1> <nav id="nav-main" class="nav-main"> - {% include 'partials/menu.twig' with { + {% include '@emulsify-tpl/partials/menu.twig' with { items: menu.get_items } %} </nav> @@ -23,7 +23,7 @@ {% endblock %} </header> - <section id="content" class="content-wrapper"> + <main id="content" class="content-wrapper"> {% if title %} <h1>{{ title }}</h1> {% endif %} @@ -32,10 +32,10 @@ Sorry, no content {% endblock %} </div> - </section> + </main> {% block footer %} - {% include 'partials/footer.twig' %} + {% include 'templates/partials/footer.twig' %} {% endblock %} {{ function('wp_footer') }} {% do action('get_footer') %} diff --git a/templates/page.twig b/templates/page.twig index 7709c52..d2c0571 100644 --- a/templates/page.twig +++ b/templates/page.twig @@ -1,4 +1,4 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} <div class="content-wrapper"> diff --git a/templates/partials/comment.twig b/templates/partials/comment.twig index 710f979..3bac23c 100644 --- a/templates/partials/comment.twig +++ b/templates/partials/comment.twig @@ -3,13 +3,13 @@ <div class="comment-content">{{ comment.content|wpautop }}</div> <section class="comment-box"> - {% include 'partials/comment-form.twig' %} + {% include 'templates/partials/comment-form.twig' %} {% if post.comments %} <h4>replies</h4> <div class="comments"> {% for cmt in comment.children %} - {% include 'partials/comment.twig' with { + {% include 'templates/partials/comment.twig' with { comment: cmt } %} {% endfor %} diff --git a/templates/partials/menu.twig b/templates/partials/menu.twig index 377dc98..74a6bd6 100644 --- a/templates/partials/menu.twig +++ b/templates/partials/menu.twig @@ -3,7 +3,7 @@ {% for item in items %} <li class="{{ item.classes|join(' ') }}"> <a target="{{ item.target }}" href="{{ item.link }}">{{ item.title }}</a> - {% include 'partials/menu.twig' with { + {% include 'templates/partials/menu.twig' with { items: item.children } %} </li> diff --git a/templates/partials/tease-post.twig b/templates/partials/tease-post.twig index 5a08767..6e63324 100644 --- a/templates/partials/tease-post.twig +++ b/templates/partials/tease-post.twig @@ -1,4 +1,4 @@ -{% extends 'partials/tease.twig' %} +{% extends 'templates/partials/tease.twig' %} {% block content %} <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> diff --git a/templates/search.twig b/templates/search.twig index f8853a6..dce2dc7 100644 --- a/templates/search.twig +++ b/templates/search.twig @@ -1,13 +1,13 @@ {# see `templates/archive.twig` for an alternative strategy of extending templates #} -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} <div class="content-wrapper"> {% for post in posts %} - {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} {% endfor %} - {% include 'partials/pagination.twig' with { + {% include 'templates/partials/pagination.twig' with { pagination: posts.pagination({ show_all: false, mid_size: 3, diff --git a/templates/single-password.twig b/templates/single-password.twig index fc62761..c59927c 100644 --- a/templates/single-password.twig +++ b/templates/single-password.twig @@ -1,4 +1,4 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} <form class="password-form" diff --git a/templates/single.twig b/templates/single.twig index 584450d..701ebf2 100644 --- a/templates/single.twig +++ b/templates/single.twig @@ -1,4 +1,4 @@ -{% extends 'layouts/base.twig' %} +{% extends 'templates/layouts/base.twig' %} {% block content %} <div class="content-wrapper"> @@ -19,7 +19,7 @@ {% if post.comments %} <h3>comments</h3> {% for cmt in post.comments %} - {% include 'partials/comment.twig' with { + {% include 'templates/partials/comment.twig' with { comment: cmt } %} {% endfor %} @@ -29,7 +29,7 @@ {% if post.comment_status == 'closed' %} <p>comments for this post are closed</p> {% else %} - {% include 'partials/comment-form.twig' %} + {% include 'templates/partials/comment-form.twig' %} {% endif %} </section> </article> diff --git a/theme.json b/theme.json new file mode 100644 index 0000000..97101d9 --- /dev/null +++ b/theme.json @@ -0,0 +1,663 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2, + "settings": { + "color": { + "defaultPalette": false, + "defaultGradients": false, + "custom": false, + "duotone": [], + "gradients": [], + "customDuotone": false, + "defaultDuotone": false, + "customGradient": false, + "palette": [ + { + "slug": "brand-primary-base", + "name": "brand primary base", + "color": "#005F89" + }, + { + "slug": "brand-primary-light", + "name": "brand primary light", + "color": "#CCECFA" + }, + { + "slug": "brand-primary-light-1", + "name": "brand primary light-1", + "color": "#E6F5FC" + }, + { + "slug": "brand-primary-light-2", + "name": "brand primary light-2", + "color": "#fff" + }, + { + "slug": "brand-primary-dark", + "name": "brand primary dark", + "color": "#00202E" + }, + { + "slug": "brand-primary-dark-1", + "name": "brand primary dark-1", + "color": "#00405B" + }, + { + "slug": "brand-secondary-base", + "name": "brand secondary base", + "color": "#8B1E7E" + }, + { + "slug": "brand-secondary-light", + "name": "brand secondary light", + "color": "#d8aad5" + }, + { + "slug": "brand-secondary-light-1", + "name": "brand secondary light-1", + "color": "#7efbe1" + }, + { + "slug": "brand-secondary-dark", + "name": "brand secondary dark", + "color": "#8b1e7e" + }, + { + "slug": "brand-secondary-dark-1", + "name": "brand secondary dark-1", + "color": "#0b4b3f" + }, + { + "slug": "grayscale-white", + "name": "grayscale-white", + "color": "#ffffff" + }, + { + "slug": "grayscale-gray-1", + "name": "grayscale-gray-1", + "color": "#f1f1f1" + }, + { + "slug": "grayscale-gray-2", + "name": "grayscale-gray-2", + "color": "#e6e6e6" + }, + { + "slug": "grayscale-gray-3", + "name": "grayscale-gray-3", + "color": "#adadad" + }, + { + "slug": "grayscale-gray-4", + "name": "grayscale-gray-4", + "color": "#757575" + }, + { + "slug": "grayscale-gray-5", + "name": "grayscale-gray-5", + "color": "#5c5c5c" + }, + { + "slug": "grayscale-gray-6", + "name": "grayscale-gray-6", + "color": "#454545" + }, + { + "slug": "grayscale-gray-7", + "name": "grayscale-gray-7", + "color": "#1b1b1b" + }, + { + "slug": "grayscale-black", + "name": "grayscale-black", + "color": "#000000" + }, + { + "slug": "other-yellow-base", + "name": "other yellow base", + "color": "#fad980" + }, + { + "slug": "other-yellow-light", + "name": "other yellow light", + "color": "#fff1d2" + }, + { + "slug": "other-yellow-neon-base", + "name": "other yellow-neon base", + "color": "#ff0" + }, + { + "slug": "other-green-base", + "name": "other green base", + "color": "#94bfa2" + }, + { + "slug": "other-green-light", + "name": "other green light", + "color": "#e7f4e4" + }, + { + "slug": "other-red-base", + "name": "other red base", + "color": "#e31c3d" + }, + { + "slug": "other-red-light", + "name": "other red light", + "color": "#e59393" + }, + { + "slug": "other-red-light-1", + "name": "other red light-1", + "color": "#f9dede" + }, + { + "slug": "other-red-dark", + "name": "other red dark", + "color": "#cd2026" + }, + { + "slug": "other-red-dark-1", + "name": "other red dark-1", + "color": "#981b1e" + } + ] + }, + "shadow": { + "defaultPresets": false + }, + "typography": { + "customFontSize": false, + "fontFamilies": [ + { + "name": "Primary", + "slug": "primary", + "fontFamily": "\"Source Sans Pro\", Arial, sans-serif" + }, + { + "name": "System", + "slug": "system", + "fontFamily": "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Oxygen-Sans\", Ubuntu, Cantarell, \"Fira Sans\", Droid Sans, sans-serif" + }, + { + "name": "Monospace", + "slug": "monospace", + "fontFamily": "Menlo, Consolas, \"Lucida Console\", \"Liberation Mono\", \"Courier New\", monospace, sans-serif" + } + ], + "fontSizes": [ + { + "name": "1", + "slug": "1", + "size": "12px" + }, + { + "name": "2", + "slug": "2", + "size": "14px" + }, + { + "name": "3", + "slug": "3", + "size": "16px" + }, + { + "name": "4", + "slug": "4", + "size": "17px", + "fluid": { + "min": "17px", + "max": "18px" + } + }, + { + "name": "5", + "slug": "5", + "size": "19px", + "fluid": { + "min": "19px", + "max": "21px" + } + }, + { + "name": "6", + "slug": "6", + "size": "21px", + "fluid": { + "min": "21px", + "max": "23px" + } + }, + { + "name": "7", + "slug": "7", + "size": "23px", + "fluid": { + "min": "23px", + "max": "26px" + } + }, + { + "name": "8", + "slug": "8", + "size": "26px", + "fluid": { + "min": "26px", + "max": "30px" + } + }, + { + "name": "9", + "slug": "9", + "size": "28px", + "fluid": { + "min": "28px", + "max": "34px" + } + }, + { + "name": "10", + "slug": "10", + "size": "31px", + "fluid": { + "min": "31px", + "max": "39px" + } + }, + { + "name": "11", + "slug": "11", + "size": "34px", + "fluid": { + "min": "34px", + "max": "44px" + } + }, + { + "name": "12", + "slug": "12", + "size": "38px", + "fluid": { + "min": "38px", + "max": "50px" + } + }, + { + "name": "13", + "slug": "13", + "size": "41px", + "fluid": { + "min": "41px", + "max": "56px" + } + }, + { + "name": "14", + "slug": "14", + "size": "46px", + "fluid": { + "min": "46px", + "max": "64px" + } + }, + { + "name": "15", + "slug": "15", + "size": "50px", + "fluid": { + "min": "50px", + "max": "72px" + } + } + ] + }, + "spacing": { + "spacingScale": { + "operator": "*", + "increment": 1.5, + "steps": 7, + "mediumStep": 1.5, + "unit": "rem" + }, + "spacingSizes": [ + { + "slug": "0", + "size": "0", + "name": "0" + }, + { + "slug": "1", + "size": "0.25rem", + "name": "0.25rem" + }, + { + "slug": "2", + "size": "0.5rem", + "name": "0.5rem" + }, + { + "slug": "3", + "size": "0.75rem", + "name": "0.75rem" + }, + { + "slug": "4", + "size": "1rem", + "name": "1rem" + }, + { + "slug": "5", + "size": "1.25rem", + "name": "1.25rem" + }, + { + "slug": "6", + "size": "1.5rem", + "name": "1.5rem" + }, + { + "slug": "7", + "size": "2rem", + "name": "2rem" + }, + { + "slug": "8", + "size": "2.5rem", + "name": "2.5rem" + }, + { + "slug": "9", + "size": "3rem", + "name": "3rem" + }, + { + "slug": "10", + "size": "3.5rem", + "name": "3.5rem" + }, + { + "slug": "11", + "size": "4rem", + "name": "4rem" + }, + { + "slug": "12", + "size": "4.5rem", + "name": "4.5rem" + }, + { + "slug": "13", + "size": "5rem", + "name": "5rem - Default" + }, + { + "slug": "14", + "size": "5.5rem", + "name": "5.5rem" + }, + { + "slug": "15", + "size": "6rem", + "name": "6rem" + }, + { + "slug": "16", + "size": "6.5rem", + "name": "6.5rem" + }, + { + "slug": "17", + "size": "7rem", + "name": "7rem" + }, + { + "slug": "18", + "size": "7.5rem", + "name": "7.5rem" + } + ] + }, + "layout": { + "contentSize": "1440px", + "wideSize": "2200px" + }, + "useRootPaddingAwareAlignments": true, + "custom": { + "box-shadow": { + "0": "none", + "1": "0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)", + "2": "0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23)", + "3": "0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23)", + "4": "0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22)", + "5": "0 19px 38px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22)" + }, + "constrain": { + "sm": "800px", + "md": "1440px", + "lg": "2200px" + }, + "line-height": { + "short": 1.1, + "base": 1.45, + "loose": 1.7 + }, + "transitions": { + "ease-in-out": "cubic-bezier(0.4, 0, 0.2, 1)", + "ease-out": "cubic-bezier(0.0, 0, 0.2, 1)", + "ease-in": "cubic-bezier(0.4, 0, 1, 1)", + "sharp": "cubic-bezier(0.4, 0, 0.6, 1)", + "duration-shortest": "150ms", + "duration-short": "200ms", + "duration-standard": "375ms", + "duration-long": "400ms", + "duration-intro": "270ms", + "duration-outro": "195ms" + }, + "z-index": { + "nav": 1000, + "drawer": 1200, + "modal": 1300, + "skiplinks": 9999 + } + }, + "blocks": { + "core/group": { + "typography": { + "fontSizes": [], + "fontFamilies": [], + "letterSpacing": false, + "lineHeight": false, + "dropCap": false, + "textDecoration": false, + "fontWeight": false, + "fontStyle": false, + "textTransform": false + } + }, + "core/heading": { + "typography": { + "fontSizes": [], + "fontFamilies": [], + "letterSpacing": false, + "lineHeight": false, + "dropCap": false, + "textDecoration": false, + "fontWeight": false, + "fontStyle": false, + "textTransform": false + } + }, + "core/quote": { + "typography": { + "fontSizes": [], + "fontFamilies": [], + "letterSpacing": false, + "lineHeight": false, + "dropCap": false, + "textDecoration": false, + "fontWeight": false, + "fontStyle": false, + "textTransform": false + } + }, + "core/paragraph": { + "typography": { + "fontFamilies": [], + "lineHeight": false, + "textDecoration": false, + "fontWeight": false, + "fontStyle": false, + "textTransform": false + } + } + } + }, + "styles": { + "spacing": { + "padding": { + "left": "1rem", + "right": "1rem" + } + }, + "elements": { + "link": { + "border": {}, + "color": { + "text": "var(--wp--preset--color--brand-primary-light)" + }, + ":hover": { + "color": { + "text": "var(---wp--preset--color--brand-primary)" + } + }, + ":focus": { + "color": { + "text": "var(---wp--preset--color--brand-primary)" + } + }, + ":visited": { + "color": { + "text": "var(--wp--preset--color--brand-primary-dark)" + } + }, + ":active": { + "color": { + "text": "var(---wp--preset--color--brand-primary)" + } + }, + "spacing": {}, + "typography": {} + }, + "h1": { + "color": { + "text": "var(--wp--preset--color--grayscale-black)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--12)" + } + }, + "h2": { + "color": { + "text": "var(--wp--preset--color--grayscale-black)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--10)" + } + }, + "h3": { + "color": { + "text": "var(--wp--preset--color--grayscale-black)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--8)" + } + }, + "h4": { + "color": { + "text": "var(--wp--preset--color--grayscale-black)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--7)" + } + }, + "h5": { + "color": { + "text": "var(--wp--preset--color--grayscale-black)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--3)" + } + }, + "h6": { + "color": { + "text": "var(--wp--preset--color--grayscale-black)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--2)" + } + } + }, + "blocks": { + "core/button": { + "border": { + "radius": "0", + "width": "0", + "color": { + "background": "var(--wp--preset--color--grayscale-black)", + "text": "var(--wp--preset--color--grayscale-white)" + } + }, + "spacing": { + "padding": { + "bottom": "var(--wp--preset--spacing--2)", + "left": "var(--wp--preset--spacing--4)", + "right": "var(--wp--preset--spacing--4)", + "top": "var(--wp--preset--spacing--2)" + } + } + }, + "core/quote": { + "color": { + "text": "var(--wp--preset--color--brand-primary-light)" + }, + "spacing": { + "margin": { + "top": "0", + "right": "0", + "bottom": "var(--wp--preset--spacing--medium)", + "left": "0" + } + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--7)", + "fontWeight": "400" + }, + "elements": { + "cite": { + "color": { + "text": "var(--wp--preset--color--brand-primary-dark)" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontSize": "var(--wp--preset--font-size--3)", + "fontWeight": "400" + }, + "spacing": { + "margin": { + "top": "var(--wp--preset--spacing--medium)" + } + } + } + } + } + } + } +} diff --git a/whisk/.nvm b/whisk/.nvm new file mode 100644 index 0000000..cabf43b --- /dev/null +++ b/whisk/.nvm @@ -0,0 +1 @@ +24 \ No newline at end of file diff --git a/whisk/components/.gitkeep b/whisk/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/config/emulsify-core/storybook/example.theme.js b/whisk/config/emulsify-core/storybook/example.theme.js deleted file mode 100644 index 2df2913..0000000 --- a/whisk/config/emulsify-core/storybook/example.theme.js +++ /dev/null @@ -1,37 +0,0 @@ -// Documentation on theming Storybook: https://storybook.js.org/docs/configurations/theming/ -import { create } from '@storybook/theming'; - -export default create({ - base: 'dark', - - // UI - appBg: '#0000ff', - appContentBg: '#eee', - appBorderColor: '#ff0000', - appBorderRadius: 4, - - // Typography - fontBase: '"Mona Sans", sans-serif', - fontCode: 'monospace', - - // Text colors - textColor: 'white', - textInverseColor: 'rgba(255,255,255,0.9)', - textMutedColor: '#E6F5FC', - - // Toolbar default and active colors - barTextColor: '#0000ff', - barSelectedColor: '#eeff00', - barBg: '#eeff00', - - // Form colors - inputBg: 'red', - inputBorder: 'silver', - inputTextColor: '#dddddd', - inputBorderRadius: 4, - // Branding - brandTitle: 'Emulsify', - brandUrl: 'https://emulsify.info', - brandImage: - 'https://raw.githubusercontent.com/fourkitchens/emulsify-core/main/assets/images/emulsify-logo-sb.svg?token=GHSAT0AAAAAACIEXLVC5R3KBCX6HGKGTBBSZNYFWMA', -}); diff --git a/whisk/config/emulsify-core/storybook/main.js b/whisk/config/emulsify-core/storybook/main.js index d3f3709..f608f7e 100644 --- a/whisk/config/emulsify-core/storybook/main.js +++ b/whisk/config/emulsify-core/storybook/main.js @@ -1,19 +1,12 @@ // Pass an empty configOverrides by default. -const configOverrides = { - staticDirs: [ - '../../../../assets/images', - '../../../../assets/icons', - '../../../../assets/textures', - '../../../../dist', - ], -}; +const configOverrides = {}; // Uncomment the following section to override the default Emulsify Core configuration. // Doing so is a complete override so no configuration from Emulsify Core's main.js will be inherited. // See https://storybook.js.org/docs/7/configure for details. // const configOverrides = { // stories: [ -// '../../../../src/**/*.stories.@(js|jsx|ts|tsx)', +// '../../../../components/**/*.stories.@(js|jsx|ts|tsx)', // ], // addons: [ // '../../../@storybook/addon-a11y', @@ -34,4 +27,4 @@ const configOverrides = { // }, // }; -module.exports = {configOverrides}; +export default configOverrides; \ No newline at end of file diff --git a/whisk/config/emulsify-core/storybook/manager-head.html b/whisk/config/emulsify-core/storybook/manager-head.html new file mode 100644 index 0000000..e69de29 diff --git a/whisk/config/emulsify-core/storybook/preview-head.html b/whisk/config/emulsify-core/storybook/preview-head.html new file mode 100644 index 0000000..e69de29 diff --git a/whisk/config/emulsify-core/storybook/preview.js b/whisk/config/emulsify-core/storybook/preview.js index 8b927e3..ad7c4bf 100644 --- a/whisk/config/emulsify-core/storybook/preview.js +++ b/whisk/config/emulsify-core/storybook/preview.js @@ -1,23 +1,5 @@ -// export global parameters as config overrides. -// This is useful for reorganizing your stories. +// export project specific parameters as config overrides. // See https://storybook.js.org/docs/writing-stories/parameters#story-parameters. -export const overrideParams = { - actions: { argTypesRegex: '^on[A-Z].*' }, - layout: 'fullscreen', - options: { - storySort: { - method: 'alphabetical', - order: [ - 'Docs', - ['Intro', '*'], - 'Tokens', - ['Readme', '*'], - 'Components', - ['Readme', '*'], - 'Layout', - ['Readme', '*'], - ], - locales: 'en-US', - }, - }, -}; +const overrideParams = {}; + +export default overrideParams; \ No newline at end of file diff --git a/whisk/config/emulsify-core/storybook/theme.js b/whisk/config/emulsify-core/storybook/theme.js new file mode 100644 index 0000000..f7b22db --- /dev/null +++ b/whisk/config/emulsify-core/storybook/theme.js @@ -0,0 +1,40 @@ +// To customize your Storybook theme, remove the following line and uncomment the code below. +const storybookTheme = {}; + +// import { create } from '@storybook/theming'; +// const storybookTheme = create({ +// base: 'dark', + +// // UI +// appBg: '#0000ff', +// appContentBg: '#eee', +// appBorderColor: '#ff0000', +// appBorderRadius: 4, + +// // Typography +// fontBase: '"Mona Sans", sans-serif', +// fontCode: 'monospace', + +// // Text colors +// textColor: 'white', +// textInverseColor: 'rgba(255,255,255,0.9)', +// textMutedColor: '#E6F5FC', + +// // Toolbar default and active colors +// barTextColor: '#0000ff', +// barSelectedColor: '#eeff00', +// barBg: '#eeff00', + +// // Form colors +// inputBg: 'red', +// inputBorder: 'silver', +// inputTextColor: '#dddddd', +// inputBorderRadius: 4, +// // Branding +// brandTitle: 'Emulsify', +// brandUrl: 'https://emulsify.info', +// brandImage: +// 'https://raw.githubusercontent.com/fourkitchens/emulsify-core/main/assets/images/emulsify-logo-sb.svg?token=GHSAT0AAAAAACIEXLVC5R3KBCX6HGKGTBBSZNYFWMA', +// }); + +export default storybookTheme; diff --git a/whisk/functions.php b/whisk/functions.php index bd21126..59eb14e 100644 --- a/whisk/functions.php +++ b/whisk/functions.php @@ -1,18 +1,14 @@ <?php /** - * whisk - * Emulsify subtheme - * https://github.com/emulsify-ds/emulsify-wordpress-theme/ + * Functions + * + * Main functions file for the theme. + * + * @category Theme + * @package Emulsify + * @subpackage whisk + * @author Callin Mullaney <callin@fourkitchens.com> + * @since 1.0.0 */ -// Load Composer dependencies. -require_once dirname(__DIR__, 4) . '/vendor/autoload.php'; - -require_once __DIR__ . '/includes/site.php'; - -Timber\Timber::init(); - -// Sets the directories (inside your theme) to find .twig files. -Timber::$dirname = [ 'templates' ]; - -new whiskSite(); +require 'includes/twig-namespaces.php'; diff --git a/whisk/includes/site.php b/whisk/includes/site.php deleted file mode 100644 index ccf8bd7..0000000 --- a/whisk/includes/site.php +++ /dev/null @@ -1,133 +0,0 @@ -<?php - -use Timber\Site; - -/** - * Class whiskSite - */ -class whiskSite extends Site { - public function __construct() { - add_action( 'after_setup_theme', array( $this, 'theme_supports' ) ); - add_action( 'init', array( $this, 'register_post_types' ) ); - add_action( 'init', array( $this, 'register_taxonomies' ) ); - - add_filter( 'timber/context', array( $this, 'add_to_context' ) ); - add_filter( 'timber/twig', array( $this, 'add_to_twig' ) ); - add_filter( 'timber/twig/environment/options', [ $this, 'update_twig_environment_options' ] ); - - parent::__construct(); - } - - /** - * This is where you can register custom post types. - */ - public function register_post_types() { - - } - - /** - * This is where you can register custom taxonomies. - */ - public function register_taxonomies() { - - } - - /** - * This is where you add some context - * - * @param string $context context['this'] Being the Twig's {{ this }}. - */ - public function add_to_context( $context ) { - $context['notes'] = 'These values are available everytime you call Timber::context();'; - $context['menu'] = Timber::get_menu(); - $context['site'] = $this; - - return $context; - } - - public function theme_supports() { - // Add default posts and comments RSS feed links to head. - add_theme_support( 'automatic-feed-links' ); - - /* - * Let WordPress manage the document title. - * By adding theme support, we declare that this theme does not use a - * hard-coded <title> tag in the document head, and expect WordPress to - * provide it for us. - */ - add_theme_support( 'title-tag' ); - - /* - * Enable support for Post Thumbnails on posts and pages. - * - * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ - */ - add_theme_support( 'post-thumbnails' ); - - /* - * Switch default core markup for search form, comment form, and comments - * to output valid HTML5. - */ - add_theme_support( - 'html5', - array( - 'comment-form', - 'comment-list', - 'gallery', - 'caption', - ) - ); - - /* - * Enable support for Post Formats. - * - * See: https://codex.wordpress.org/Post_Formats - */ - add_theme_support( - 'post-formats', - array( - 'aside', - 'image', - 'video', - 'quote', - 'link', - 'gallery', - 'audio', - ) - ); - - add_theme_support( 'menus' ); - } - - /** - * This is where you can add your own functions to twig. - * - * @param Twig\Environment $twig get extension. - */ - public function add_to_twig( $twig ) { - /** - * Required when you want to use Twig’s template_from_string. - * @link https://twig.symfony.com/doc/3.x/functions/template_from_string.html - */ - // $twig->addExtension( new Twig\Extension\StringLoaderExtension() ); - - $twig->addFilter( new Twig\TwigFilter( 'myfoo', [ $this, 'myfoo' ] ) ); - - return $twig; - } - - /** - * Updates Twig environment options. - * - * @link https://twig.symfony.com/doc/2.x/api.html#environment-options - * - * \@param array $options An array of environment options. - * - * @return array - */ - function update_twig_environment_options( $options ) { - // $options['autoescape'] = true; - - return $options; - } -} diff --git a/whisk/includes/twig-namespaces.php b/whisk/includes/twig-namespaces.php new file mode 100644 index 0000000..54ddb08 --- /dev/null +++ b/whisk/includes/twig-namespaces.php @@ -0,0 +1,17 @@ +<?php +/** + * Create namespaces for referencing twig files. + * + * @package Emulsify + */ + +add_filter( + 'timber/loader/loader', + function ( $loader ) { + $loader->addPath( __DIR__ . '/../src/components', 'components' ); + $loader->addPath( __DIR__ . '/../src/foundation', 'foundation' ); + $loader->addPath( __DIR__ . '/../src/layout', 'layout' ); + $loader->addPath( __DIR__ . '/../templates', 'templates' ); + return $loader; + } +); diff --git a/whisk/package.json b/whisk/package.json index b29dc80..7c4ca1a 100644 --- a/whisk/package.json +++ b/whisk/package.json @@ -15,31 +15,30 @@ "type": "module", "scripts": { "a11y": "npm run storybook-build && node_modules/@emulsify/core/scripts/a11y.js -r", - "build": "webpack --config node_modules/@emulsify/core/config/webpack/webpack.prod.js", - "build-dev": "webpack --config node_modules/@emulsify/core/config/webpack/webpack.dev.js", + "build": "npm run ensure-dist && webpack --config node_modules/@emulsify/core/config/webpack/webpack.prod.js", + "build-dev": "npm run ensure-dist && webpack --config node_modules/@emulsify/core/config/webpack/webpack.dev.js", "coverage": "npm run test && open-cli .coverage/lcov-report/index.html", - "develop": "concurrently --raw \"npm run webpack\" \"npm run storybook\"", + "develop": "npm run ensure-dist && concurrently --raw --no-shell npm:webpack npm:storybook", + "ensure-dist": "mkdir -p dist", "format": "npm run lint-fix; npm run prettier-fix", - "husky:commit-msg": "commitlint --edit $1", - "husky:pre-commit": "lint-staged", "lint": "npm run lint-js; npm run lint-styles", "lint-fix": "npm run lint-js -- --fix; npm run lint-styles -- --fix", - "lint-js": "eslint --config ./config/emulsify-core/eslintrc.config.json --no-eslintrc --no-error-on-unmatched-pattern ./src", + "lint-js": "eslint --config config/emulsify-core/eslintrc.config.json --no-eslintrc --no-error-on-unmatched-pattern ./components", "lint-staged": "lint-staged", - "lint-styles": "stylelint --config ./config/emulsify-core/stylelintrc.config.json './src/**/*.scss'", - "prettier": "prettier --config ./config/emulsify-core/prettierrc.config.json --ignore-path ./config/emulsify-core/.prettierignore --ignore-unknown \"./src/**/*.{js,yml,scss,md}\"", - "prettier-fix": "prettier --config ./config/emulsify-core/prettierrc.config.json --ignore-path ./config/emulsify-core/.prettierignore --write --ignore-unknown \"./src/**/*.{js,yml,scss,md}\"", - "storybook": "storybook dev -c node_modules/@emulsify/core/.storybook --ci -p 6006", + "lint-styles": "stylelint --config config/emulsify-core/stylelintrc.config.json './components/**/*.scss'", + "prettier": "prettier --config config/emulsify-core/prettierrc.config.json --ignore-path config/emulsify-core/.prettierignore --ignore-unknown \"./components/**/*.{js,yml,scss,md}\"", + "prettier-fix": "prettier --config config/emulsify-core/prettierrc.config.json --ignore-path config/emulsify-core/.prettierignore --write --ignore-unknown \"./components/**/*.{js,yml,scss,md}\"", + "storybook": "NODE_OPTIONS=--no-deprecation storybook dev -c node_modules/@emulsify/core/.storybook --ci -p 6006", "storybook-build": "npm run build && storybook build -c node_modules/@emulsify/core/.storybook -o .out", "storybook-deploy": "storybook-to-ghpages -o .out", - "style-dictionary:build": "style-dictionary build --config ./config/sd.config.json", + "style-dictionary:build": "node ./src/tokens/tokensTransform.mjs", "test": "jest --coverage --config ./config/jest.config.js", "tokens:build": "npm run tokens:transform && npm run style-dictionary:build", - "tokens:transform": "node ./src/util/tokensTransform.js -- './src/tokens/figma-export/**/*.json' './dist/tokens'", + "tokens:transform": "token-transformer ./src/tokens/figma.tokens.json ./src/tokens/transformed.tokens.json", "twatch": "jest --no-coverage --watch --verbose", "webpack": "webpack --watch --config node_modules/@emulsify/core/config/webpack/webpack.dev.js" }, "dependencies": { - "@emulsify/core": "^2.7.1", + "@emulsify/core": "^3.0.2" } } diff --git a/whisk/src/components/.gitkeep b/whisk/src/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/src/foundation/.gitkeep b/whisk/src/foundation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/src/layout/.gitkeep b/whisk/src/layout/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whisk/templates/404.twig b/whisk/templates/404.twig new file mode 100644 index 0000000..fa4e23c --- /dev/null +++ b/whisk/templates/404.twig @@ -0,0 +1,5 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + Sorry, we couldn't find what you're looking for. +{% endblock %} diff --git a/whisk/templates/archive.twig b/whisk/templates/archive.twig new file mode 100644 index 0000000..6f3c0f3 --- /dev/null +++ b/whisk/templates/archive.twig @@ -0,0 +1,15 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + {% for post in posts %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} + {% endfor %} + + {% include 'templates/partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} +{% endblock %} diff --git a/whisk/templates/author.twig b/whisk/templates/author.twig new file mode 100644 index 0000000..0249fea --- /dev/null +++ b/whisk/templates/author.twig @@ -0,0 +1,7 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + {% for post in posts %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} + {% endfor %} +{% endblock %} diff --git a/whisk/templates/index.twig b/whisk/templates/index.twig new file mode 100644 index 0000000..e610d48 --- /dev/null +++ b/whisk/templates/index.twig @@ -0,0 +1,16 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + <h2>{{ foo }}</h2> + {% for post in posts %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} + {% endfor %} + + {% include 'templates/partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} +{% endblock %} diff --git a/whisk/templates/layouts/base.twig b/whisk/templates/layouts/base.twig new file mode 100644 index 0000000..2e3fd6f --- /dev/null +++ b/whisk/templates/layouts/base.twig @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html {{ site.language_attributes }}> + {% block head %} + {% include 'templates/partials/head.twig' %} + {% endblock %} + + <body class="{{ body_class }}"> + {{ function('wp_body_open') }} + <a class="skip-link screen-reader-text" href="#content">{{ _e('Skip to content') }}</a> + <header class="header"> + {% block header %} + <div class="wrapper"> + <h1 class="hdr-logo"> + <a class="hdr-logo-link" href="{{ site.url }}">{{ site.name }}</a> + </h1> + <nav id="nav-main" class="nav-main"> + {% include 'templates/partials/menu.twig' with { + items: menu.get_items + } %} + </nav> + <!-- #nav --> + </div> + {% endblock %} + </header> + + <section id="content" class="content-wrapper"> + {% if title %} + <h1>{{ title }}</h1> + {% endif %} + <div class="wrapper"> + {% block content %} + Sorry, no content + {% endblock %} + </div> + </section> + + {% block footer %} + {% include 'templates/partials/footer.twig' %} + {% endblock %} + {{ function('wp_footer') }} + {% do action('get_footer') %} + </body> +</html> diff --git a/whisk/templates/page.twig b/whisk/templates/page.twig new file mode 100644 index 0000000..d2c0571 --- /dev/null +++ b/whisk/templates/page.twig @@ -0,0 +1,12 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + <article class="post-type-{{ post.type }}" id="post-{{ post.id }}"> + <section class="article-content"> + <h1 class="article-h1">{{ post.title }}</h1> + <div class="article-body">{{ post.content }}</div> + </section> + </article> + </div> +{% endblock %} diff --git a/whisk/templates/partials/comment-form.twig b/whisk/templates/partials/comment-form.twig new file mode 100644 index 0000000..565f4b6 --- /dev/null +++ b/whisk/templates/partials/comment-form.twig @@ -0,0 +1,32 @@ +<div class="comment-form"> + <h3>Add comment</h3> + <form class="comment-form" method="post" action="{{ site.link ~ '/wp-comments-post.php' }}"> + {% if user %} + <input type="hidden" name="email" value="{{ user.email }}" /> + <input type="hidden" name="author" value="{{ user.name }}" /> + <input type="hidden" name="url" value="{{ user.link }}" /> + {% else %} + <label> + Email<br /> + <input required name="email" type="email" id="email" /> + </label> + <label> + Name<br /> + <input required name="author" type="text" /> + </label> + <label> + Website<br /> + <input name="url" type="url" /> + </label> + {% endif %} + <label> + Comment<br /> + <textarea placeholder="Leave a comment..." name="comment" cols="60" rows="3"></textarea> + </label> + <input name="comment_post_ID" value="{{ post.id }}" type="hidden" /> + <input name="comment_parent" value="{{ comment.id|default('0') }}" type="hidden" /> + <button type="submit" name="Submit" class="btn">Send</button> + <button type="reset">Cancel</button> + <p>Your comment will be revised by the site if needed.</p> + </form> +</div> diff --git a/whisk/templates/partials/comment.twig b/whisk/templates/partials/comment.twig new file mode 100644 index 0000000..3bac23c --- /dev/null +++ b/whisk/templates/partials/comment.twig @@ -0,0 +1,19 @@ +<div class="blog-comment" id="blog-comment-{{ comment.id }}"> + <h5 class="comment-author">{{ comment.author.name }} says</h5> + <div class="comment-content">{{ comment.content|wpautop }}</div> + + <section class="comment-box"> + {% include 'templates/partials/comment-form.twig' %} + + {% if post.comments %} + <h4>replies</h4> + <div class="comments"> + {% for cmt in comment.children %} + {% include 'templates/partials/comment.twig' with { + comment: cmt + } %} + {% endfor %} + </div> + {% endif %} + </section> +</div> diff --git a/whisk/templates/partials/footer.twig b/whisk/templates/partials/footer.twig new file mode 100644 index 0000000..606165c --- /dev/null +++ b/whisk/templates/partials/footer.twig @@ -0,0 +1 @@ +<footer id="footer">Copyright {{ 'now'|date('Y') }}</footer> diff --git a/whisk/templates/partials/head.twig b/whisk/templates/partials/head.twig new file mode 100644 index 0000000..ea68c2a --- /dev/null +++ b/whisk/templates/partials/head.twig @@ -0,0 +1,9 @@ +<head> + <meta charset="{{ site.charset }}" /> + <link rel="stylesheet" href="{{ site.theme.link }}/style.css" type="text/css" media="screen" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="author" href="{{ site.theme.link }}/humans.txt" /> + <link rel="profile" href="http://gmpg.org/xfn/11" /> + {% do action('get_header') %} + {{ function('wp_head') }} +</head> diff --git a/whisk/templates/partials/menu.twig b/whisk/templates/partials/menu.twig new file mode 100644 index 0000000..74a6bd6 --- /dev/null +++ b/whisk/templates/partials/menu.twig @@ -0,0 +1,12 @@ +{% if menu %} + <ul> + {% for item in items %} + <li class="{{ item.classes|join(' ') }}"> + <a target="{{ item.target }}" href="{{ item.link }}">{{ item.title }}</a> + {% include 'templates/partials/menu.twig' with { + items: item.children + } %} + </li> + {% endfor %} + </ul> +{% endif %} diff --git a/whisk/templates/partials/pagination.twig b/whisk/templates/partials/pagination.twig new file mode 100644 index 0000000..e4d737b --- /dev/null +++ b/whisk/templates/partials/pagination.twig @@ -0,0 +1,44 @@ +{% if posts.pagination.pages is not empty %} + <nav class="pagination-block"> + <ul class="pagination"> + {# First #} + {% if (posts.pagination.pages|first) and posts.pagination.pages|first.current != true %} + <li class="first btn"> + <a href="{{ posts.pagination.pages|first.link }}">First</a> + </li> + {% else %} + <li class="first btn disabled"><button disabled>First</button></li> + {% endif %} + + {# Previous #} + {% if posts.pagination.prev %} + <li class="prev btn"><a href="{{ posts.pagination.prev.link }}">Previous</a></li> + {% else %} + <li class="prev btn disabled"><button disabled>Previous</button></li> + {% endif %} + + {# Pages #} + {% for page in posts.pagination.pages %} + {% if page.link %} + <li><a href="{{ page.link }}" class="{{ page.class }}">{{ page.title }}</a></li> + {% else %} + <li class="current"><span class="{{ page.class }}">{{ page.title }}</span></li> + {% endif %} + {% endfor %} + + {# Next #} + {% if posts.pagination.next %} + <li class="next btn"><a href="{{ posts.pagination.next.link }}">Next</a></li> + {% else %} + <li class="next btn disabled"><button disabled>Next</button></li> + {% endif %} + + {# Last #} + {% if (posts.pagination.pages|last) and posts.pagination.pages|last.current != true %} + <li class="last btn"><a href="{{ posts.pagination.pages|last.link }}">Last</a></li> + {% else %} + <li class="last btn disabled"><button disabled>Last</button></li> + {% endif %} + </ul> + </nav> +{% endif %} diff --git a/whisk/templates/partials/tease-post.twig b/whisk/templates/partials/tease-post.twig new file mode 100644 index 0000000..6e63324 --- /dev/null +++ b/whisk/templates/partials/tease-post.twig @@ -0,0 +1,16 @@ +{% extends 'templates/partials/tease.twig' %} + +{% block content %} + <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> + <p> + {{ + post.excerpt({ + words: 5, + read_more: 'Keep reading' + }) + }} + </p> + {% if post.thumbnail.src %} + <img src="{{ post.thumbnail.src }}" /> + {% endif %} +{% endblock %} diff --git a/whisk/templates/partials/tease.twig b/whisk/templates/partials/tease.twig new file mode 100644 index 0000000..1a0f7b0 --- /dev/null +++ b/whisk/templates/partials/tease.twig @@ -0,0 +1,9 @@ +<article class="tease tease-{{ post.type }}" id="tease-{{ post.id }}"> + {% block content %} + <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> + <p>{{ post.excerpt }}</p> + {% if post.thumbnail %} + <img src="{{ post.thumbnail.src }}" /> + {% endif %} + {% endblock %} +</article> \ No newline at end of file diff --git a/whisk/templates/search.twig b/whisk/templates/search.twig new file mode 100644 index 0000000..dce2dc7 --- /dev/null +++ b/whisk/templates/search.twig @@ -0,0 +1,18 @@ +{# see `templates/archive.twig` for an alternative strategy of extending templates #} +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + {% for post in posts %} + {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} + {% endfor %} + + {% include 'templates/partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} + </div> +{% endblock %} diff --git a/whisk/templates/single-password.twig b/whisk/templates/single-password.twig new file mode 100644 index 0000000..c59927c --- /dev/null +++ b/whisk/templates/single-password.twig @@ -0,0 +1,17 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + <form class="password-form" + action="{{ site.link }}/wp-login.php?action=postpass" + method="post"> + <label for="pwbox-{{ post.id }}">Password:</label> + <input class="password-box" + name="post_password" + id="pwbox-{{ post.id }}" + type="password" + placeholder="Password" + size="20" + maxlength="20" /> + <input class="password-btn" type="submit" name="Submit" value="Submit" /> + </form> +{% endblock %} diff --git a/whisk/templates/single.twig b/whisk/templates/single.twig new file mode 100644 index 0000000..701ebf2 --- /dev/null +++ b/whisk/templates/single.twig @@ -0,0 +1,37 @@ +{% extends 'templates/layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + <article class="post-type-{{ post.type }}" id="post-{{ post.id }}"> + <img src="{{ post.thumbnail.src|resize(1200, 300) }}" /> + <section class="article-content"> + <h1 class="article-h1">{{ post.title }}</h1> + <p class="blog-author"> + <span>By</span> + <a href="{{ post.author.path }}">{{ post.author.name }}</a> + <span>•</span> + <time datetime="{{ post.date|date('Y-m-d H:i:s') }}">{{ post.date }}</time> + </p> + <div class="article-body">{{ post.content }}</div> + </section> + <section class="comment-box"> + <div class="comments"> + {% if post.comments %} + <h3>comments</h3> + {% for cmt in post.comments %} + {% include 'templates/partials/comment.twig' with { + comment: cmt + } %} + {% endfor %} + {% endif %} + </div> + + {% if post.comment_status == 'closed' %} + <p>comments for this post are closed</p> + {% else %} + {% include 'templates/partials/comment-form.twig' %} + {% endif %} + </section> + </article> + </div> +{% endblock %} From 6ee5d0031b82caee287cc1879bfd9fb38e4a48f1 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 03:53:18 -0500 Subject: [PATCH 04/55] chore: align release metadata for 2.0 --- .nvmrc | 2 +- README.md | 10 +- composer.json | 6 +- package-lock.json | 3201 ++++++++++++++++++--------------------------- package.json | 25 +- 5 files changed, 1282 insertions(+), 1962 deletions(-) diff --git a/.nvmrc b/.nvmrc index 209e3ef..a45fd52 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +24 diff --git a/README.md b/README.md index 2c878ad..dd2252d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ ![Emulsify Design System](https://github.com/emulsify-ds/.github/blob/6bd435be881bd820bddfa05d88905efe29176a0a/assets/images/header.png) -# Emulsify Wordpress +# Emulsify WordPress ## Emulsify is an open-source toolset for creating and implementing design systems on your website -### Storybook development, Webpack build, and Wordpress 11 theme +### Storybook development, Webpack build, and WordPress theme -**Emulsify Wordpress** provides a [Storybook](https://storybook.js.org/) component library, a [Webpack](https://webpack.js.org/) development environment, and a Wordpress 11 starter kit theme. +**Emulsify WordPress** provides a [Storybook](https://storybook.js.org/) component library, a [Webpack](https://webpack.js.org/) development environment, and a WordPress starter kit theme. ## Documentation @@ -23,7 +23,7 @@ ## Contributing -### [Code of Conduct](https://github.com/emulsify-ds/emulsify-wordpress/blob/main/CODE_OF_CONDUCT.md) +### [Code of Conduct](https://github.com/emulsify-ds/emulsify-wordpress-theme/blob/main/CODE_OF_CONDUCT.md) The project maintainers have adopted a Code of Conduct that we expect project participants to adhere to. Please read the full text so that you can understand what actions will and will not be tolerated. @@ -31,7 +31,7 @@ The project maintainers have adopted a Code of Conduct that we expect project pa Please also follow the issue template and pull request templates provided. See below for the correct places to post issues: -1. [Emulsify wordpress](https://github.com/emulsify-ds/emulsify-wordpress) +1. [Emulsify WordPress](https://github.com/emulsify-ds/emulsify-wordpress-theme) 2. [Emulsify Twig Extensions](https://github.com/emulsify-ds/emulsify-twig-extensions/issues) ### Committing Changes diff --git a/composer.json b/composer.json index a4ab436..8d8f7d9 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "emulsify-ds/emulsify-wordpress", - "description": "The official Wordpress base theme for Emulsify that generates custom themes with Storybook development + Webpack Build", + "name": "emulsify-ds/emulsify-wordpress-theme", + "description": "The official WordPress base theme for Emulsify that generates custom themes with Storybook development + Webpack Build", "type": "wordpress-theme", "homepage": "http://emulsify.info", "license": "GPL-2.0-only", @@ -26,4 +26,4 @@ "composer/installers": true } } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 398b5f1..7c2751b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,82 @@ { - "name": "emulsify-wordpress", + "name": "emulsify-wordpress-theme", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "emulsify-wordpress", + "name": "emulsify-wordpress-theme", "version": "1.0.0", - "license": "ISC", + "license": "GPL-2.0-only", "dependencies": { "graceful-fs": "^4.2.11" }, "devDependencies": { "@commitlint/cli": "^19.4.1", "@commitlint/config-conventional": "^19.4.1", - "@semantic-release/changelog": "^6.0.2", - "@semantic-release/commit-analyzer": "^13.0.0", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/git": "^10.0.1", - "@semantic-release/github": "^11.0.2", - "@semantic-release/release-notes-generator": "^14.0.0", + "@semantic-release/github": "^12.0.8", + "@semantic-release/npm": "^13.1.5", + "@semantic-release/release-notes-generator": "^14.1.1", "husky": "^9.1.5", "lint-staged": "^15.2.9", - "semantic-release": "^24.1.0" + "semantic-release": "^25.0.5" + }, + "engines": { + "node": ">=24.10" + } + }, + "node_modules/@actions/core": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz", + "integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" + } + }, + "node_modules/@actions/exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^3.0.2" + } + }, + "node_modules/@actions/http-client": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz", + "integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" } }, + "node_modules/@actions/io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -297,184 +350,161 @@ "node": ">=v18" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/core": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz", - "integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/openapi-types": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz", - "integrity": "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==", - "dev": true + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-12.0.0.tgz", - "integrity": "sha512-MPd6WK1VtZ52lFrgZ0R2FlaoiWllzgqFHaSZxvp72NmoDeZ0m8GeJdg4oB6ctqMTYyrnDYp592Xma21mrgiyDA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { "@octokit/core": ">=6" } }, "node_modules/@octokit/plugin-retry": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.2.1.tgz", - "integrity": "sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": ">=6" + "@octokit/core": ">=7" } }, "node_modules/@octokit/plugin-throttling": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-10.0.0.tgz", - "integrity": "sha512-Kuq5/qs0DVYTHZuBAzCZStCzo2nKvVRo/TDNhCcpC2TKiOGz/DisXMCvjt3/b5kr6SCI1Y8eeeJTHBxxpFvZEg==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.3.tgz", + "integrity": "sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0", + "@octokit/types": "^16.0.0", "bottleneck": "^2.15.3" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "^6.1.3" + "@octokit/core": "^7.0.0" } }, "node_modules/@octokit/request": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz", - "integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.10.tgz", + "integrity": "sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "content-type": "^2.0.0", + "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^14.0.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.0.0.tgz", - "integrity": "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^25.0.0" + "@octokit/openapi-types": "^27.0.0" } }, "node_modules/@pnpm/config.env-replace": { @@ -623,30 +653,32 @@ } }, "node_modules/@semantic-release/github": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.2.tgz", - "integrity": "sha512-EhHimj3/eOSPu0OflgDzwgrawoGJIn8XLOkNS6WzwuTr8ebxyX976Y4mCqJ8MlkdQpV5+8T+49sy8xXlcm6uCg==", + "version": "12.0.8", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.8.tgz", + "integrity": "sha512-tej5AAgK5X9wHRoDmYhecMXEHEkFeGOY1XsEblKxu8pIQwahzf1STYyr7iPU6Lpbg6C5I3N2w/ocXrBo+L7jhw==", "dev": true, + "license": "MIT", "dependencies": { - "@octokit/core": "^6.0.0", - "@octokit/plugin-paginate-rest": "^12.0.0", - "@octokit/plugin-retry": "^7.0.0", - "@octokit/plugin-throttling": "^10.0.0", + "@octokit/core": "^7.0.0", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-retry": "^8.0.0", + "@octokit/plugin-throttling": "^11.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "debug": "^4.3.4", "dir-glob": "^3.0.1", - "globby": "^14.0.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^9.0.0", + "https-proxy-agent": "^9.0.0", "issue-parser": "^7.0.0", "lodash-es": "^4.17.21", "mime": "^4.0.0", "p-filter": "^4.0.0", + "tinyglobby": "^0.2.14", + "undici": "^7.0.0", "url-join": "^5.0.0" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.10.0" }, "peerDependencies": { "semantic-release": ">=24.1.0" @@ -705,27 +737,30 @@ } }, "node_modules/@semantic-release/npm": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", - "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "version": "13.1.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.5.tgz", + "integrity": "sha512-Hq5UxzoatN3LHiq2rTsWS54nCdqJHlsssGERCo8WlvdfFA9LoN0vO+OuKVSjtNapIc/S8C2LBj206wKLHg62mg==", "dev": true, + "license": "MIT", "dependencies": { + "@actions/core": "^3.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", + "env-ci": "^11.2.0", "execa": "^9.0.0", "fs-extra": "^11.0.0", "lodash-es": "^4.17.21", "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", - "npm": "^10.5.0", + "normalize-url": "^9.0.0", + "npm": "^11.6.2", "rc": "^1.2.8", - "read-pkg": "^9.0.0", + "read-pkg": "^10.0.0", "registry-auth-token": "^5.0.0", "semver": "^7.1.2", "tempy": "^3.0.0" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.10.0" }, "peerDependencies": { "semantic-release": ">=20.1.0" @@ -825,6 +860,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/npm/node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@semantic-release/npm/node_modules/human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -858,6 +906,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/npm/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@semantic-release/npm/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@semantic-release/npm/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -874,6 +947,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/npm/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@semantic-release/npm/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -886,6 +977,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/npm/node_modules/read-pkg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/read-pkg/node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@semantic-release/npm/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -923,19 +1063,18 @@ } }, "node_modules/@semantic-release/release-notes-generator": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.3.tgz", - "integrity": "sha512-XxAZRPWGwO5JwJtS83bRdoIhCiYIx8Vhr+u231pQAsdFIAbm19rSVJLdnBN+Avvk7CKvNQE/nJ4y7uqKH6WTiw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.1.1.tgz", + "integrity": "sha512-Pbd2e2XRMUD0OxehHpgd5/YghsE76cddkRHSoDvKLK+OCy4Ewxn49rWR631MEUU01lgwF/uyVXvbnVuu6+Z6VA==", "dev": true, + "license": "MIT", "dependencies": { "conventional-changelog-angular": "^8.0.0", "conventional-changelog-writer": "^8.0.0", "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.0.0", "debug": "^4.0.0", - "get-stream": "^7.0.0", "import-from-esm": "^2.0.0", - "into-stream": "^7.0.0", "lodash-es": "^4.17.21", "read-package-up": "^11.0.0" }, @@ -985,18 +1124,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", @@ -1022,12 +1149,13 @@ "dev": true }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/aggregate-error": { @@ -1123,16 +1251,18 @@ "dev": true }, "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/braces": { "version": "3.0.3", @@ -1593,6 +1723,20 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/conventional-changelog-angular": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", @@ -1860,10 +2004,11 @@ "dev": true }, "node_modules/env-ci": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", - "integrity": "sha512-Z8dnwSDbV1XYM9SBF2J0GcNVvmfmfh3a49qddGIROhBoVro6MZVTji15z/sJbQ2ko2ei8n988EU1wzoLU/tF+g==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^8.0.0", "java-properties": "^1.0.2" @@ -2098,44 +2243,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", @@ -2152,15 +2265,6 @@ } ] }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -2233,16 +2337,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, "node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -2290,18 +2384,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/git-log-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", @@ -2354,18 +2436,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", @@ -2381,74 +2451,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" }, "engines": { - "node": ">=18" + "node": ">=0.4.7" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, "node_modules/has-flag": { @@ -2470,12 +2496,13 @@ } }, "node_modules/hook-std": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", - "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-4.0.0.tgz", + "integrity": "sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2494,29 +2521,33 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" }, "engines": { - "node": ">= 14" + "node": ">= 20" } }, "node_modules/human-signals": { @@ -2543,15 +2574,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -2636,37 +2658,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/into-stream": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", - "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", - "dev": true, - "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", @@ -2679,18 +2676,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2839,6 +2824,13 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/json-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -3262,10 +3254,11 @@ "dev": true }, "node_modules/marked": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", - "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -3312,15 +3305,6 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3438,27 +3422,29 @@ } }, "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-9.0.1.tgz", + "integrity": "sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", - "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", + "version": "11.17.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.17.0.tgz", + "integrity": "sha512-PurxiZexEHDTE4SSaLI3ZrnbAGiZfeyUcQcxcP5D+hfytNAze/D1IzDuInTn9XVLIbAQUnQuSPXJx02LHjLvQw==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", "@npmcli/config", "@npmcli/fs", "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", "@npmcli/package-json", "@npmcli/promise-spawn", "@npmcli/redact", @@ -3469,7 +3455,6 @@ "cacache", "chalk", "ci-info", - "cli-columns", "fastest-levenshtein", "fs-minipass", "glob", @@ -3483,7 +3468,6 @@ "libnpmdiff", "libnpmexec", "libnpmfund", - "libnpmhook", "libnpmorg", "libnpmpack", "libnpmpublish", @@ -3497,7 +3481,6 @@ "ms", "node-gyp", "nopt", - "normalize-package-data", "npm-audit-report", "npm-install-checks", "npm-package-arg", @@ -3520,86 +3503,90 @@ "tiny-relative-date", "treeverse", "validate-npm-package-name", - "which", - "write-file-atomic" + "which" ], "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.0", - "@npmcli/config": "^9.0.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.1.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "@sigstore/tuf": "^3.0.0", - "abbrev": "^3.0.0", + "@npmcli/arborist": "^9.8.0", + "@npmcli/config": "^10.11.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.5", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.4", + "@sigstore/tuf": "^4.0.2", + "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.3.0", - "ci-info": "^4.1.0", - "cli-columns": "^4.0.0", + "cacache": "^20.0.4", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.4.5", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.0.2", - "ini": "^5.0.0", - "init-package-json": "^7.0.2", - "is-cidr": "^5.1.0", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.0", - "libnpmexec": "^9.0.0", - "libnpmfund": "^6.0.0", - "libnpmhook": "^11.0.0", - "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.0", - "libnpmpublish": "^10.0.1", - "libnpmsearch": "^8.0.0", - "libnpmteam": "^7.0.0", - "libnpmversion": "^7.0.0", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", + "hosted-git-info": "^9.0.3", + "ini": "^6.0.0", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.4", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.1.10", + "libnpmexec": "^10.3.0", + "libnpmfund": "^7.0.24", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.1.10", + "libnpmpublish": "^11.2.0", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.4", + "make-fetch-happen": "^15.0.6", + "minimatch": "^10.2.5", + "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^11.0.0", - "nopt": "^8.0.0", - "normalize-package-data": "^7.0.0", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^4.0.0", - "pacote": "^19.0.1", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", + "node-gyp": "^12.4.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.1", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.5.1", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", - "read": "^4.0.0", - "semver": "^7.6.3", + "read": "^5.0.1", + "semver": "^7.8.4", "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^9.4.0", - "tar": "^6.2.1", + "ssri": "^13.0.1", + "supports-color": "^10.2.2", + "tar": "^7.5.16", "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", + "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.0", - "which": "^5.0.0", - "write-file-atomic": "^6.0.0" + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", "npx": "bin/npx-cli.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-run-path": { @@ -3614,71 +3601,13 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.3", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@isaacs/fs-minipass": { @@ -3700,7 +3629,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "3.0.0", + "version": "4.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -3708,83 +3637,82 @@ "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.0", + "version": "9.8.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/metavuln-calculator": "^8.0.0", - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.1", - "@npmcli/query": "^4.0.0", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "bin-links": "^5.0.0", - "cacache": "^19.0.1", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^8.0.0", - "npm-install-checks": "^7.1.0", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.1", - "pacote": "^19.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "proggy": "^3.0.0", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^4.0.0", "semver": "^7.3.7", - "ssri": "^12.0.0", + "ssri": "^13.0.0", "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "9.0.0", + "version": "10.11.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "ini": "^5.0.0", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "walk-up-path": "^3.0.1" + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3792,291 +3720,276 @@ "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.1", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.2", + "version": "5.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0" + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "8.0.1", + "version": "9.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cacache": "^19.0.0", - "json-parse-even-better-errors": "^4.0.0", - "pacote": "^20.0.0", - "proc-log": "^5.0.0", + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { - "version": "20.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "3.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.1.0", + "version": "7.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "normalize-package-data": "^7.0.0", - "proc-log": "^5.0.0", - "semver": "^7.5.3" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^6.1.2" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.0.2", + "version": "10.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", + "node_modules/npm/node_modules/@sigstore/core": { + "version": "3.2.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.0.0", + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.5.1", "dev": true, "inBundle": true, "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^3.0.1" - }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "4.1.1", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/abbrev": { - "version": "3.0.0", + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "4.0.2", "dev": true, "inBundle": true, - "license": "ISC", + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/agent-base": { - "version": "7.1.1", + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "3.1.1", "dev": true, "inBundle": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "debug": "^4.3.4" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": ">= 14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/npm/node_modules/@tufjs/models": { + "version": "4.1.0", "dev": true, "inBundle": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", + "node_modules/npm/node_modules/abbrev": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14" } }, "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", + "version": "2.1.0", "dev": true, "inBundle": true, "license": "ISC" @@ -4088,148 +4001,77 @@ "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", + "version": "4.0.4", "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/npm/node_modules/bin-links": { - "version": "5.0.0", + "version": "6.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "cmd-shim": "^7.0.0", - "npm-normalize-package-bin": "^4.0.0", - "proc-log": "^5.0.0", - "read-cmd-shim": "^5.0.0", - "write-file-atomic": "^6.0.0" + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/binary-extensions": { - "version": "2.3.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", + "version": "5.0.6", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/npm/node_modules/cacache": { - "version": "19.0.1", + "version": "20.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^4.0.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "ssri": "^13.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/p-map": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", + "version": "5.6.2", "dev": true, "inBundle": true, "license": "MIT", @@ -4241,16 +4083,16 @@ } }, "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", + "version": "3.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.1.0", + "version": "4.4.0", "dev": true, "funding": [ { @@ -4265,99 +4107,30 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.1", + "version": "5.0.5", "dev": true, "inBundle": true, "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^5.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">= 10" + "node": ">=20" } }, "node_modules/npm/node_modules/cmd-shim": { - "version": "7.0.0", + "version": "8.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", + "version": "2.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 8" + "node": ">= 18" } }, "node_modules/npm/node_modules/cssesc": { @@ -4373,7 +4146,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.7", + "version": "4.4.3", "dev": true, "inBundle": true, "license": "MIT", @@ -4390,7 +4163,7 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "5.2.0", + "version": "8.0.4", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -4398,28 +4171,6 @@ "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", "dev": true, @@ -4429,14 +4180,8 @@ "node": ">=6" } }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", + "version": "3.1.3", "dev": true, "inBundle": true, "license": "Apache-2.0" @@ -4450,22 +4195,6 @@ "node": ">= 4.9.1" } }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/fs-minipass": { "version": "3.0.3", "dev": true, @@ -4479,20 +4208,17 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "10.4.5", + "version": "13.0.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4505,19 +4231,19 @@ "license": "ISC" }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "8.0.2", + "version": "9.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", + "version": "4.2.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause" @@ -4536,12 +4262,12 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.5", + "version": "7.0.6", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -4549,7 +4275,7 @@ } }, "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", + "version": "0.7.2", "dev": true, "inBundle": true, "license": "MIT", @@ -4559,145 +4285,87 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/npm/node_modules/ignore-walk": { - "version": "7.0.0", + "version": "8.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "minimatch": "^9.0.0" + "minimatch": "^10.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/npm/node_modules/ini": { + "version": "6.0.0", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.8.19" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", + "node_modules/npm/node_modules/init-package-json": { + "version": "8.2.5", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", + "validate-npm-package-name": "^7.0.0" + }, "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/ini": { - "version": "5.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "10.2.0", "dev": true, "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "7.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/package-json": "^6.0.0", - "npm-package-arg": "^12.0.0", - "promzard": "^2.0.0", - "read": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, + "license": "MIT", "engines": { "node": ">= 12" } }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.0", + "version": "6.0.4", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^4.1.1" + "cidr-regex": "^5.0.4" }, "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">=20" } }, "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "3.4.3", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "engines": { + "node": ">=20" } }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/json-stringify-nice": { @@ -4731,228 +4399,212 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "9.0.0", + "version": "10.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1" + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.0", + "version": "8.1.10", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "binary-extensions": "^2.3.0", - "diff": "^5.1.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "tar": "^6.2.1" + "@npmcli/arborist": "^9.8.0", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.0", + "version": "10.3.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/run-script": "^9.0.1", + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.8.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "proc-log": "^5.0.0", - "read": "^4.0.0", - "read-package-json-fast": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "read": "^5.0.1", "semver": "^7.3.7", - "walk-up-path": "^3.0.1" + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "11.0.0", + "version": "7.0.24", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "@npmcli/arborist": "^9.8.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "7.0.0", + "version": "8.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.0", + "version": "9.1.10", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/run-script": "^9.0.1", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0" + "@npmcli/arborist": "^9.8.0", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "10.0.1", + "version": "11.2.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@npmcli/package-json": "^7.0.0", "ci-info": "^4.0.0", - "normalize-package-data": "^7.0.0", - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1", - "proc-log": "^5.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7", - "sigstore": "^3.0.0", - "ssri": "^12.0.0" + "sigstore": "^4.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "8.0.0", + "version": "9.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^18.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "7.0.0", + "version": "8.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" + "npm-registry-fetch": "^19.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "7.0.0", + "version": "8.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.1", - "@npmcli/run-script": "^9.0.1", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.7" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.4.3", + "version": "11.5.1", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "14.0.3", + "version": "15.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "proc-log": "^6.0.0", + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", + "version": "10.2.5", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -4970,57 +4622,32 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.0", + "version": "5.0.2", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", + "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" + "iconv-lite": "^0.7.2" } }, "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", + "version": "1.0.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.3" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/npm/node_modules/minipass-pipeline": { @@ -5047,65 +4674,34 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", + "node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { "node": ">=8" } }, "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" + "minipass": "^7.1.2" }, "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/npm/node_modules/ms": { @@ -5115,370 +4711,263 @@ "license": "MIT" }, "node_modules/npm/node_modules/mute-stream": { - "version": "2.0.0", + "version": "3.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/npm/node_modules/node-gyp": { - "version": "11.0.0", + "version": "12.4.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", - "which": "^5.0.0" + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", + "node_modules/npm/node_modules/nopt": { + "version": "9.0.0", "dev": true, "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { - "version": "3.0.1", + "node_modules/npm/node_modules/npm-audit-report": { + "version": "7.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, + "license": "ISC", "engines": { - "node": ">= 18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", + "node_modules/npm/node_modules/npm-bundled": { + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", + "node_modules/npm/node_modules/npm-install-checks": { + "version": "8.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BSD-2-Clause", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" + "semver": "^7.1.1" }, "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "node_modules/npm/node_modules/npm-normalize-package-bin": { "version": "5.0.0", "dev": true, "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/nopt": { - "version": "8.0.0", + "node_modules/npm/node_modules/npm-package-arg": { + "version": "13.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/nopt/node_modules/abbrev": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "4.0.0", + "node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", - "dev": true, - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "9.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^7.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "10.0.0", + "version": "11.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-profile": { - "version": "11.0.1", + "version": "12.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0" + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "18.0.2", + "version": "19.1.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^3.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "3.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", + "version": "7.0.4", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0" - }, "node_modules/npm/node_modules/pacote": { - "version": "19.0.1", + "version": "21.5.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/parse-conflict-json": { - "version": "4.0.0", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "json-parse-even-better-errors": "^4.0.0", + "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.1.2", + "version": "7.1.4", "dev": true, "inBundle": true, "license": "MIT", @@ -5491,21 +4980,21 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "5.0.0", + "version": "6.1.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/proggy": { - "version": "3.0.0", + "version": "4.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/promise-all-reject-late": { @@ -5526,35 +5015,16 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/npm/node_modules/promzard": { - "version": "2.0.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "read": "^4.0.0" + "read": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/qrcode-terminal": { @@ -5566,61 +5036,24 @@ } }, "node_modules/npm/node_modules/read": { - "version": "4.0.0", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "^2.0.0" + "mute-stream": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/read-cmd-shim": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "4.0.0", + "version": "6.0.0", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "dev": true, - "inBundle": true, - "license": "MIT", "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/rimraf": { - "version": "5.0.10", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/safer-buffer": { @@ -5631,7 +5064,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.6.3", + "version": "7.8.4", "dev": true, "inBundle": true, "license": "ISC", @@ -5642,27 +5075,6 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/signal-exit": { "version": "4.1.0", "dev": true, @@ -5676,72 +5088,20 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^3.0.0", - "@sigstore/tuf": "^3.0.0", - "@sigstore/verify": "^2.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^14.0.1", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.0.0", + "version": "4.1.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/smart-buffer": { @@ -5755,12 +5115,12 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.8.3", + "version": "2.8.9", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -5769,12 +5129,12 @@ } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.4", + "version": "8.0.5", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -5782,26 +5142,6 @@ "node": ">= 14" } }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", "dev": true, @@ -5819,19 +5159,13 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.20", + "version": "3.0.23", "dev": true, "inBundle": true, "license": "CC0-1.0" }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "dev": true, - "inBundle": true, - "license": "BSD-3-Clause" - }, "node_modules/npm/node_modules/ssri": { - "version": "12.0.0", + "version": "13.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -5839,123 +5173,35 @@ "minipass": "^7.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/npm/node_modules/tar": { - "version": "6.2.1", + "version": "7.5.16", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/npm/node_modules/text-table": { @@ -5965,254 +5211,147 @@ "license": "MIT" }, "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "3.0.1", + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.17", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { - "version": "3.0.1", + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" + "node": ">=12.0.0" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/which": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" + "node": ">=12" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", "dev": true, "inBundle": true, "license": "ISC", "engines": { - "node": ">=16" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", + "node_modules/npm/node_modules/tuf-js": { + "version": "4.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", + "node_modules/npm/node_modules/undici": { + "version": "6.26.0", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=18.17" } }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT" }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "7.0.2", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "20 || >=22" } }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", + "node_modules/npm/node_modules/which": { + "version": "6.0.1", "dev": true, "inBundle": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-regex": "^6.0.1" + "isexe": "^4.0.0" }, - "engines": { - "node": ">=12" + "bin": { + "node-which": "bin/which.js" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "6.0.0", + "version": "7.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -6265,15 +5404,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-is-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", - "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -6561,25 +5691,23 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true } - ] + } }, "node_modules/rc": { "version": "1.2.8", @@ -6752,45 +5880,12 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rfdc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -6798,16 +5893,17 @@ "dev": true }, "node_modules/semantic-release": { - "version": "24.2.3", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.3.tgz", - "integrity": "sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A==", + "version": "25.0.5", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-25.0.5.tgz", + "integrity": "sha512-mn61SUJwtM8ThrWn2WmgLVpwVJeG/hPSupua1psdMoufmwRIPyvRLkRkL0JDXkP67OntlLWUYnBnfVc8EDO3/g==", "dev": true, + "license": "MIT", "dependencies": { - "@semantic-release/commit-analyzer": "^13.0.0-beta.1", + "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", - "@semantic-release/github": "^11.0.0", - "@semantic-release/npm": "^12.0.0", - "@semantic-release/release-notes-generator": "^14.0.0-beta.1", + "@semantic-release/github": "^12.0.0", + "@semantic-release/npm": "^13.1.1", + "@semantic-release/release-notes-generator": "^14.1.0", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", @@ -6817,27 +5913,26 @@ "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", - "hook-std": "^3.0.0", - "hosted-git-info": "^8.0.0", + "hook-std": "^4.0.0", + "hosted-git-info": "^9.0.0", "import-from-esm": "^2.0.0", "lodash-es": "^4.17.21", - "marked": "^12.0.0", - "marked-terminal": "^7.0.0", + "marked": "^15.0.0", + "marked-terminal": "^7.3.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", - "read-package-up": "^11.0.0", + "read-package-up": "^12.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", - "semver-diff": "^4.0.0", "signale": "^1.2.1", - "yargs": "^17.5.1" + "yargs": "^18.0.0" }, "bin": { "semantic-release": "bin/semantic-release.js" }, "engines": { - "node": ">=20.8.1" + "node": "^22.14.0 || >= 24.10.0" } }, "node_modules/semantic-release/node_modules/@semantic-release/error": { @@ -6892,6 +5987,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/semantic-release/node_modules/execa": { "version": "9.5.2", "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", @@ -6947,15 +6057,16 @@ } }, "node_modules/semantic-release/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", "dev": true, + "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/semantic-release/node_modules/human-signals": { @@ -6991,6 +6102,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/semantic-release/node_modules/normalize-package-data": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/semantic-release/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -7019,6 +6155,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semantic-release/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -7031,6 +6198,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/read-package-up": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz", + "integrity": "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.1", + "read-pkg": "^10.0.0", + "type-fest": "^5.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/read-pkg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semantic-release/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -7055,6 +6273,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/type-fest": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", + "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semantic-release/node_modules/unicorn-magic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", @@ -7067,6 +6301,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/semantic-release/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -7079,21 +6341,6 @@ "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/semver-regex": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", @@ -7242,18 +6489,6 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -7457,6 +6692,19 @@ "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -7578,6 +6826,54 @@ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7602,6 +6898,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -7641,6 +6947,16 @@ "node": ">=0.8.0" } }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -7684,10 +7000,11 @@ } }, "node_modules/universal-user-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", - "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", - "dev": true + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" }, "node_modules/universalify": { "version": "2.0.1", diff --git a/package.json b/package.json index f451773..2d4b694 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { - "name": "emulsify-wordpress", + "name": "emulsify-wordpress-theme", "version": "1.0.0", - "description": "Storybook development + Webpack Build + Wordpress theme", + "description": "Storybook development + Webpack Build + WordPress theme", + "engines": { + "node": ">=24.10" + }, "keywords": [ "component library", "design system", @@ -11,17 +14,16 @@ "styleguide" ], "author": "Four Kitchens <shout@fourkitchens.com>", - "license": "ISC", + "license": "GPL-2.0-only", "repository": { "type": "git", - "url": "git+https://github.com/emulsify-ds/emulsify-wordpress.git" + "url": "git+https://github.com/emulsify-ds/emulsify-wordpress-theme.git" }, "bugs": { - "url": "https://github.com/emulsify-ds/emulsify-wordpress/issues" + "url": "https://github.com/emulsify-ds/emulsify-wordpress-theme/issues" }, "homepage": "https://www.emulsify.info", "scripts": { - "prepare": "husky install", "husky:commit-msg": "commitlint --edit $1", "husky:pre-commit": "npm run lint", "lint": "npm run lint:php", @@ -38,13 +40,14 @@ "devDependencies": { "@commitlint/cli": "^19.4.1", "@commitlint/config-conventional": "^19.4.1", - "@semantic-release/changelog": "^6.0.2", - "@semantic-release/commit-analyzer": "^13.0.0", + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/git": "^10.0.1", - "@semantic-release/github": "^11.0.2", - "@semantic-release/release-notes-generator": "^14.0.0", + "@semantic-release/github": "^12.0.8", + "@semantic-release/npm": "^13.1.5", + "@semantic-release/release-notes-generator": "^14.1.1", "husky": "^9.1.5", "lint-staged": "^15.2.9", - "semantic-release": "^24.1.0" + "semantic-release": "^25.0.5" } } From 644f78f65130285b7f022676675aa396e3cfac6a Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 04:01:13 -0500 Subject: [PATCH 05/55] ci: replace release workflow for wordpress --- .github/workflows/dev-sync.yml | 29 ------- .github/workflows/semantic-release.yml | 101 ++++++++++++++++++++----- release.config.js | 11 ++- 3 files changed, 91 insertions(+), 50 deletions(-) delete mode 100644 .github/workflows/dev-sync.yml diff --git a/.github/workflows/dev-sync.yml b/.github/workflows/dev-sync.yml deleted file mode 100644 index cb33d71..0000000 --- a/.github/workflows/dev-sync.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Sync Drupal Dev Branch -on: - push: - branches: - - '*.x' - workflow_dispatch: -jobs: - sync: - name: Sync Dev Branch - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Extract branch name - shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" - id: extract_branch - - name: Add remote SSH Key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.DRUPAL_ORG_SSH_KEY }} - config: ${{ secrets.SSH_CONFIG }} - known_hosts: ${{ secrets.KNOWN_HOSTS }} - - name: Sync dev branch to Drupal.org Project - run: | - git remote add drupal-org '${{ secrets.DRUPAL_REPO_URL }}' - git push drupal-org ${{ steps.extract_branch.outputs.branch }} diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 4a24fcf..480d9ab 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -1,30 +1,93 @@ -name: Semantic Release on Merge +name: WordPress Release + on: push: branches: - - "main" + - main workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: - release: - name: Attempt Semantic Release + release-readiness: + name: Release readiness runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write steps: - name: Checkout - uses: actions/checkout@v3 - - name: Semantic Release - uses: cycjimmy/semantic-release-action@v3 + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + tools: composer:v2 + coverage: none + + - name: Clean npm install + run: npm ci --ignore-scripts + + - name: Validate Composer metadata + run: composer validate --no-check-publish --strict + + - name: Run PHP lint + run: npm run lint:php + + - name: Run runtime npm audit + run: npm audit --omit=dev + + - name: Check release config syntax + run: node --check release.config.js + + - name: Run release dry run env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Add remote SSH Key - uses: shimataro/ssh-key-action@v2 + run: npm run publish-test -- --no-ci + + - name: Run full npm audit + run: npm audit + + semantic-release: + name: Semantic release + needs: release-readiness + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v6 with: - key: ${{ secrets.DRUPAL_ORG_SSH_KEY }} - config: ${{ secrets.SSH_CONFIG }} - known_hosts: ${{ secrets.KNOWN_HOSTS }} - - name: Sync release tags to Drupal.org Project - if: steps.semantic.outputs.new_release_published == 'true' - run: | - git remote add drupal-org '${{ secrets.DRUPAL_REPO_URL }}' - git push drupal-org --tags - git checkout main - git push drupal-org main + node-version-file: .nvmrc + cache: npm + + - name: Clean npm install + run: npm ci --ignore-scripts + + - name: Semantic release + id: semantic + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npm run publish diff --git a/release.config.js b/release.config.js index ee53ec6..5616e34 100644 --- a/release.config.js +++ b/release.config.js @@ -1,10 +1,17 @@ +const parserOpts = { + headerPattern: /^(\w*)(?:\(([\w$.\-*/ ]*)\))?!?: (.*)$/, + headerCorrespondence: ['type', 'scope', 'subject'], + breakingHeaderPattern: /^(\w*)(?:\(([\w$.\-*/ ]*)\))?!: (.*)$/, + noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING-CHANGE'], +}; + module.exports = { tagFormat: '${version}', branches: ['main'], repositoryUrl: 'git@github.com:emulsify-ds/emulsify-wordpress-theme.git', plugins: [ - '@semantic-release/commit-analyzer', - '@semantic-release/release-notes-generator', + ['@semantic-release/commit-analyzer', { parserOpts }], + ['@semantic-release/release-notes-generator', { parserOpts }], ['@semantic-release/npm', { npmPublish: false }], '@semantic-release/github', ], From 2f9c8ff1030aaab7955e4e495441581f1d6a839c Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 04:07:55 -0500 Subject: [PATCH 06/55] test: add wordpress release readiness checks --- .github/scripts/release-check.cjs | 346 +++++++++++++++++++++++++ .github/workflows/semantic-release.yml | 3 + composer.json | 4 +- package.json | 3 +- style.css | 4 +- whisk/package.json | 22 +- whisk/style.css | 7 +- 7 files changed, 372 insertions(+), 17 deletions(-) create mode 100644 .github/scripts/release-check.cjs diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs new file mode 100644 index 0000000..4f81699 --- /dev/null +++ b/.github/scripts/release-check.cjs @@ -0,0 +1,346 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const repoRoot = path.resolve(__dirname, '../..'); +const results = []; + +function addResult(status, name, detail) { + results.push({ status, name, detail }); +} + +function readFile(relativePath) { + return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8'); +} + +function readJson(relativePath) { + return JSON.parse(readFile(relativePath)); +} + +function ensure(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function semver(value) { + return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(value)); +} + +function runStaticCheck(name, callback) { + try { + const detail = callback(); + addResult('PASS', name, detail); + } + catch (error) { + addResult('FAIL', name, error.message); + } +} + +function listFilesRecursive(relativePath, predicate) { + const absolutePath = path.join(repoRoot, relativePath); + if (!fs.existsSync(absolutePath)) { + return []; + } + + const files = []; + for (const entry of fs.readdirSync(absolutePath, { withFileTypes: true })) { + const childPath = path.join(relativePath, entry.name); + if (entry.isDirectory()) { + if (childPath === '.git') { + continue; + } + files.push(...listFilesRecursive(childPath, predicate)); + } + else if (predicate(childPath)) { + files.push(childPath); + } + } + + return files; +} + +function extractJsonObjectSegment(text, key) { + const keyPattern = new RegExp(`"${key}"\\s*:\\s*\\{`); + const match = keyPattern.exec(text); + if (!match) { + return null; + } + + const start = text.indexOf('{', match.index); + let depth = 0; + let inString = false; + let escaped = false; + + for (let index = start; index < text.length; index += 1) { + const character = text[index]; + + if (inString) { + if (escaped) { + escaped = false; + } + else if (character === '\\') { + escaped = true; + } + else if (character === '"') { + inString = false; + } + continue; + } + + if (character === '"') { + inString = true; + continue; + } + + if (character === '{') { + depth += 1; + continue; + } + + if (character === '}') { + depth -= 1; + if (depth === 0) { + return text.slice(start, index + 1); + } + } + } + + return null; +} + +function findDuplicatePackageScripts(relativePath) { + const raw = readFile(relativePath); + const scriptsBlock = extractJsonObjectSegment(raw, 'scripts'); + ensure(scriptsBlock, `Unable to locate the scripts block in ${relativePath}.`); + + const keys = [...scriptsBlock.matchAll(/^\s*"([^"]+)"\s*:/gm)].map((match) => match[1]); + const seen = new Set(); + const duplicates = new Set(); + + for (const key of keys) { + if (seen.has(key)) { + duplicates.add(key); + } + seen.add(key); + } + + return [...duplicates].sort(); +} + +function parseWordPressThemeHeader(relativePath) { + const header = {}; + const contents = readFile(relativePath); + + for (const line of contents.split(/\r?\n/)) { + const match = line.match(/^\s*\*\s*([^:]+):\s*(.*?)\s*$/); + if (match) { + header[match[1].trim()] = match[2].trim(); + } + } + + return header; +} + +function ensureNoTitleCaseBuildPhrase(label, value) { + ensure(!/Webpack Build|Vite Build/.test(value), `${label} should not use title-case build workflow phrases.`); +} + +function ensureWordPressLanguage(label, value) { + ensure(value.includes('WordPress'), `${label} should use the canonical WordPress spelling.`); + ensure(!/Wordpress/.test(value), `${label} should not use Wordpress.`); +} + +function ensureViteLanguage(label, value) { + ensure(value.includes('Vite-based build workflow'), `${label} should mention the Vite-based build workflow.`); + ensure(value.includes('Emulsify Core 4'), `${label} should mention Emulsify Core 4.`); + ensure(!/Webpack/i.test(value), `${label} should not mention Webpack.`); +} + +function getReleasePluginOptions(releaseConfig, pluginName) { + const plugin = releaseConfig.plugins.find((candidate) => { + if (Array.isArray(candidate)) { + return candidate[0] === pluginName; + } + + return candidate === pluginName; + }); + + ensure(plugin, `release.config.js must include ${pluginName}.`); + return Array.isArray(plugin) ? plugin[1] || {} : {}; +} + +function ensureBreakingParser(label, parserOpts) { + ensure(parserOpts, `${label} parserOpts are required.`); + ensure(parserOpts.breakingHeaderPattern, `${label} must define breakingHeaderPattern.`); + ensure(parserOpts.breakingHeaderPattern.test('feat!: remove legacy API'), `${label} should parse feat!: as breaking.`); + ensure(parserOpts.breakingHeaderPattern.test('feat(theme)!: remove legacy API'), `${label} should parse feat(scope)!: as breaking.`); + ensure(parserOpts.noteKeywords.includes('BREAKING CHANGE'), `${label} should parse BREAKING CHANGE notes.`); + ensure(parserOpts.noteKeywords.includes('BREAKING CHANGES'), `${label} should parse BREAKING CHANGES notes.`); +} + +function runStaticChecks() { + const rootPackage = readJson('package.json'); + const whiskPackage = readJson('whisk/package.json'); + const composer = readJson('composer.json'); + const rootThemeHeader = parseWordPressThemeHeader('style.css'); + const whiskThemeHeader = parseWordPressThemeHeader('whisk/style.css'); + const releaseConfig = require(path.join(repoRoot, 'release.config.js')); + const semanticReleaseWorkflow = readFile('.github/workflows/semantic-release.yml'); + + runStaticCheck('Required files', () => { + const requiredFiles = [ + '.github/scripts/release-check.cjs', + '.github/workflows/semantic-release.yml', + '.gitignore', + '.nvmrc', + 'README.md', + 'composer.json', + 'functions.php', + 'package.json', + 'release.config.js', + 'style.css', + 'whisk/functions.php', + 'whisk/package.json', + 'whisk/project.emulsify.json', + 'whisk/style.css', + ]; + const missingFiles = requiredFiles.filter((file) => !fs.existsSync(path.join(repoRoot, file))); + ensure(missingFiles.length === 0, `Missing required files: ${missingFiles.join(', ')}.`); + return `Found ${requiredFiles.length} required release files.`; + }); + + runStaticCheck('Root release metadata', () => { + ensure(rootPackage.name === 'emulsify-wordpress-theme', 'package.json name should be emulsify-wordpress-theme.'); + ensure(semver(rootPackage.version), 'package.json version must be a valid semver string.'); + ensure(rootPackage.description, 'package.json description is required.'); + ensureWordPressLanguage('package.json description', rootPackage.description); + ensureViteLanguage('package.json description', rootPackage.description); + ensureNoTitleCaseBuildPhrase('package.json description', rootPackage.description); + ensure(rootPackage.license === 'GPL-2.0-only', 'package.json license should be GPL-2.0-only.'); + ensure(rootPackage.engines && rootPackage.engines.node === '>=24.10', 'package.json engines.node should be >=24.10.'); + ensure(rootPackage.repository.url === 'git+https://github.com/emulsify-ds/emulsify-wordpress-theme.git', 'package.json repository.url should target emulsify-wordpress-theme.'); + ensure(rootPackage.bugs.url === 'https://github.com/emulsify-ds/emulsify-wordpress-theme/issues', 'package.json bugs.url should target emulsify-wordpress-theme.'); + ensure(rootPackage.scripts['release:check'] === 'node .github/scripts/release-check.cjs', 'package.json should expose npm run release:check.'); + ensure(rootPackage.devDependencies['@semantic-release/npm'], 'package.json should declare @semantic-release/npm directly.'); + ensure(composer.name === 'emulsify-ds/emulsify-wordpress-theme', 'composer.json name should be emulsify-ds/emulsify-wordpress-theme.'); + ensure(composer.type === 'wordpress-theme', 'composer.json type should be wordpress-theme.'); + ensure(composer.license === 'GPL-2.0-only', 'composer.json license should be GPL-2.0-only.'); + ensure(composer.homepage === 'https://www.emulsify.info', 'composer.json homepage should use the canonical HTTPS URL.'); + ensureWordPressLanguage('composer.json description', composer.description); + ensureViteLanguage('composer.json description', composer.description); + ensure(composer.description.includes('child themes'), 'composer.json description should mention child themes.'); + return `Validated root package ${rootPackage.version} and composer metadata.`; + }); + + runStaticCheck('WordPress theme headers', () => { + ensure(rootThemeHeader['Theme Name'] === 'Emulsify', 'style.css Theme Name should be Emulsify.'); + ensure(rootThemeHeader['Text Domain'] === 'emulsify', 'style.css Text Domain should be emulsify.'); + ensure(rootThemeHeader.Version === rootPackage.version, 'style.css Version should match package.json version.'); + ensure(rootThemeHeader['Requires at least'] === '6.7', 'style.css Requires at least should stay aligned to the WordPress baseline.'); + ensure(rootThemeHeader['Tested up to'] === '6.7', 'style.css Tested up to should stay aligned to the WordPress baseline.'); + ensure(rootThemeHeader['Requires PHP'] === '8.3', 'style.css Requires PHP should stay aligned to the release baseline.'); + ensureViteLanguage('style.css Description', rootThemeHeader.Description); + ensure(whiskThemeHeader['Theme Name'] === 'Whisk', 'whisk/style.css Theme Name should be Whisk.'); + ensure(whiskThemeHeader.Template === 'emulsify', 'whisk/style.css Template should be emulsify.'); + ensure(whiskThemeHeader['Text Domain'] === 'whisk', 'whisk/style.css Text Domain should be whisk.'); + ensure(whiskThemeHeader.Version === whiskPackage.version, 'whisk/style.css Version should match whisk/package.json version.'); + ensure(whiskThemeHeader.Description.includes('child theme'), 'whisk/style.css Description should use child theme language.'); + ensureViteLanguage('whisk/style.css Description', whiskThemeHeader.Description); + return 'Parent and Whisk WordPress theme headers are coherent with package metadata.'; + }); + + runStaticCheck('Whisk Core 4 and Vite metadata', () => { + const scripts = whiskPackage.scripts || {}; + const scriptText = Object.values(scripts).join('\n'); + ensure(whiskPackage.name === 'whisk', 'whisk/package.json name should remain whisk.'); + ensure(semver(whiskPackage.version), 'whisk/package.json version must be a valid semver string.'); + ensure(whiskPackage.description, 'whisk/package.json description is required.'); + ensureViteLanguage('whisk/package.json description', whiskPackage.description); + ensure(whiskPackage.license === 'GPL-2.0-only', 'whisk/package.json license should align with the WordPress theme.'); + ensure(whiskPackage.engines && whiskPackage.engines.node === '>=24', 'whisk/package.json engines.node should be >=24.'); + ensure(whiskPackage.type === 'module', 'whisk/package.json should remain an ES module package.'); + ensure(whiskPackage.dependencies && whiskPackage.dependencies['@emulsify/core'], 'whisk/package.json must declare @emulsify/core.'); + ensure(whiskPackage.dependencies['@emulsify/core'].startsWith('^4.'), 'whisk/package.json should target Emulsify Core 4.'); + ensure(scripts.build && scripts.build.includes('vite build --config node_modules/@emulsify/core/config/vite/vite.config.js'), 'whisk/package.json build script should use the Emulsify Core Vite config.'); + ensure(scripts.vite && scripts.vite.includes('vite build --watch'), 'whisk/package.json should expose a Vite watch script.'); + ensure(scripts.develop && scripts.develop.includes('npm:vite'), 'whisk/package.json develop script should run the Vite watcher.'); + ensure(!scripts.webpack, 'whisk/package.json should not expose a webpack script.'); + ensure(!scripts['build-dev'], 'whisk/package.json should not expose the old Webpack build-dev script.'); + ensure(!/\bwebpack\b/i.test(scriptText), 'whisk/package.json scripts should not reference Webpack.'); + ensure(!String(whiskPackage.dependencies['@emulsify/core']).startsWith('^3.'), 'whisk/package.json should not target Emulsify Core 3.'); + return `Whisk targets ${whiskPackage.dependencies['@emulsify/core']} with Vite scripts.`; + }); + + runStaticCheck('Duplicate package scripts', () => { + const duplicates = [ + ...findDuplicatePackageScripts('package.json').map((key) => `package.json:${key}`), + ...findDuplicatePackageScripts('whisk/package.json').map((key) => `whisk/package.json:${key}`), + ]; + ensure(duplicates.length === 0, `Duplicate script keys found: ${duplicates.join(', ')}.`); + return 'No duplicate script keys were found in package metadata.'; + }); + + runStaticCheck('Semantic release configuration', () => { + const analyzerOptions = getReleasePluginOptions(releaseConfig, '@semantic-release/commit-analyzer'); + const notesOptions = getReleasePluginOptions(releaseConfig, '@semantic-release/release-notes-generator'); + ensure(releaseConfig.tagFormat === '${version}', 'release.config.js should emit non-prefixed semver tags.'); + ensure(Array.isArray(releaseConfig.branches), 'release.config.js branches must be an array.'); + ensure(releaseConfig.branches.length === 1 && releaseConfig.branches[0] === 'main', 'release.config.js should publish only from main.'); + ensureBreakingParser('@semantic-release/commit-analyzer', analyzerOptions.parserOpts); + ensureBreakingParser('@semantic-release/release-notes-generator', notesOptions.parserOpts); + ensure(semanticReleaseWorkflow.includes('release-readiness:'), 'semantic-release.yml should run release-readiness before publishing.'); + ensure(semanticReleaseWorkflow.includes('needs: release-readiness'), 'semantic-release.yml release job should wait for release readiness.'); + ensure(semanticReleaseWorkflow.includes('contents: write'), 'semantic-release.yml should grant GitHub release permissions explicitly.'); + ensure(semanticReleaseWorkflow.includes('id: semantic'), 'semantic-release.yml should expose the semantic-release step as steps.semantic.'); + ensure(semanticReleaseWorkflow.includes('npm run release:check'), 'semantic-release.yml should run local release readiness checks.'); + return 'Semantic release is configured for non-prefixed tags and breaking-change major releases.'; + }); + + runStaticCheck('No Drupal.org workflow references', () => { + const forbiddenPatterns = [ + /Drupal\.org/i, + /drupal-org/i, + /DRUPAL_ORG/, + /DRUPAL_REPO_URL/, + /shimataro\/ssh-key-action/, + /SSH_CONFIG/, + /KNOWN_HOSTS/, + ]; + const files = listFilesRecursive('.github/workflows', (file) => /\.ya?ml$/.test(file)); + const violations = []; + + for (const file of files) { + const contents = readFile(file); + for (const pattern of forbiddenPatterns) { + if (pattern.test(contents)) { + violations.push(`${file} matches ${pattern}`); + } + } + } + + ensure(violations.length === 0, `Remove Drupal.org workflow references:\n${violations.join('\n')}`); + return 'No Drupal.org sync workflow references or stale Drupal.org secrets remain.'; + }); + + runStaticCheck('No .DS_Store files', () => { + const dsStoreFiles = listFilesRecursive('.', (file) => path.basename(file) === '.DS_Store') + .filter((file) => !file.startsWith('.git/')); + ensure(dsStoreFiles.length === 0, `.DS_Store files found outside .git: ${dsStoreFiles.join(', ')}.`); + return 'No .DS_Store files were found outside .git.'; + }); +} + +function printSummary() { + console.log('\nRelease Check Summary'); + for (const result of results) { + console.log(`${result.status.padEnd(4)} ${result.name}: ${result.detail}`); + } +} + +runStaticChecks(); +printSummary(); + +const hasBlockingFailure = results.some((result) => result.status === 'FAIL'); +process.exit(hasBlockingFailure ? 1 : 0); diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 480d9ab..699773a 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -55,6 +55,9 @@ jobs: - name: Check release config syntax run: node --check release.config.js + - name: Run release readiness checks + run: npm run release:check + - name: Run release dry run env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/composer.json b/composer.json index 8d8f7d9..a906148 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "emulsify-ds/emulsify-wordpress-theme", - "description": "The official WordPress base theme for Emulsify that generates custom themes with Storybook development + Webpack Build", + "description": "The official WordPress base theme for Emulsify that generates child themes with Storybook, Emulsify Core 4, and a Vite-based build workflow", "type": "wordpress-theme", - "homepage": "http://emulsify.info", + "homepage": "https://www.emulsify.info", "license": "GPL-2.0-only", "authors": [ { diff --git a/package.json b/package.json index 2d4b694..97831c1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "emulsify-wordpress-theme", "version": "1.0.0", - "description": "Storybook development + Webpack Build + WordPress theme", + "description": "Storybook, Emulsify Core 4, and a Vite-based build workflow for WordPress child themes", "engines": { "node": ">=24.10" }, @@ -30,6 +30,7 @@ "lint:php": "find ./ -name '*.php' -exec php -l {} \\;", "lint-staged": "lint-staged", "prepare": "[ -d '.git' ] && (husky install) || true", + "release:check": "node .github/scripts/release-check.cjs", "publish": "semantic-release --tag-format '${version}'", "publish-test": "semantic-release --tag-format '${version}' --dry-run --debug", "semantic-release": "semantic-release" diff --git a/style.css b/style.css index 4ec9ec3..3423bc0 100644 --- a/style.css +++ b/style.css @@ -1,10 +1,10 @@ /* * Theme Name: Emulsify * Author: Callin Mullaney & Mike Goulding - * Description: The official base theme for Emulsify and it's flexible component driven development. + * Description: The official WordPress base theme for Emulsify with Storybook, Emulsify Core 4, and a Vite-based build workflow. * Text Domain: emulsify * Requires at least: 6.7 * Tested up to: 6.7 * Requires PHP: 8.3 - * Version: 0.1 + * Version: 1.0.0 */ diff --git a/whisk/package.json b/whisk/package.json index 7c4ca1a..de7aeb2 100644 --- a/whisk/package.json +++ b/whisk/package.json @@ -1,7 +1,7 @@ { "name": "whisk", "version": "1.0.0", - "description": "Storybook development + Webpack Build", + "description": "Storybook and a Vite-based build workflow powered by Emulsify Core 4", "keywords": [ "component library", "design system", @@ -11,14 +11,18 @@ "styleguide" ], "author": "Four Kitchens <shout@fourkitchens.com>", - "license": "ISC", + "license": "GPL-2.0-only", + "engines": { + "node": ">=24" + }, "type": "module", "scripts": { "a11y": "npm run storybook-build && node_modules/@emulsify/core/scripts/a11y.js -r", - "build": "npm run ensure-dist && webpack --config node_modules/@emulsify/core/config/webpack/webpack.prod.js", - "build-dev": "npm run ensure-dist && webpack --config node_modules/@emulsify/core/config/webpack/webpack.dev.js", + "audit": "node_modules/@emulsify/core/scripts/audit.js", + "audit:twig-stories": "node_modules/@emulsify/core/scripts/audit-twig-stories.js", + "build": "npm run ensure-dist && vite build --config node_modules/@emulsify/core/config/vite/vite.config.js", "coverage": "npm run test && open-cli .coverage/lcov-report/index.html", - "develop": "npm run ensure-dist && concurrently --raw --no-shell npm:webpack npm:storybook", + "develop": "npm run ensure-dist && concurrently --raw --no-shell npm:vite npm:storybook", "ensure-dist": "mkdir -p dist", "format": "npm run lint-fix; npm run prettier-fix", "lint": "npm run lint-js; npm run lint-styles", @@ -32,13 +36,13 @@ "storybook-build": "npm run build && storybook build -c node_modules/@emulsify/core/.storybook -o .out", "storybook-deploy": "storybook-to-ghpages -o .out", "style-dictionary:build": "node ./src/tokens/tokensTransform.mjs", - "test": "jest --coverage --config ./config/jest.config.js", + "test": "jest --coverage --passWithNoTests --config ./config/jest.config.js", "tokens:build": "npm run tokens:transform && npm run style-dictionary:build", "tokens:transform": "token-transformer ./src/tokens/figma.tokens.json ./src/tokens/transformed.tokens.json", - "twatch": "jest --no-coverage --watch --verbose", - "webpack": "webpack --watch --config node_modules/@emulsify/core/config/webpack/webpack.dev.js" + "twatch": "jest --no-coverage --watch --verbose --passWithNoTests --config ./config/jest.config.js", + "vite": "vite build --watch --config node_modules/@emulsify/core/config/vite/vite.config.js" }, "dependencies": { - "@emulsify/core": "^3.0.2" + "@emulsify/core": "^4.0.0" } } diff --git a/whisk/style.css b/whisk/style.css index 3a211f4..c279cac 100644 --- a/whisk/style.css +++ b/whisk/style.css @@ -1,7 +1,8 @@ /* - * Theme Name: whisk + * Theme Name: Whisk * Author: you * Template: emulsify - * Description: A subtheme build upon the Emulsify Design System. - * Version: 0.1 + * Description: A WordPress child theme built on Emulsify Core 4 with Storybook and a Vite-based build workflow. + * Text Domain: whisk + * Version: 1.0.0 */ From 6ac931384fbd3eff5a81d2d3f428b0d3ef49f2cc Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 04:37:26 -0500 Subject: [PATCH 07/55] feat: upgrade whisk starter to emulsify core 4 --- .github/scripts/release-check.cjs | 7 +- whisk/config/configma.json | 13 --- whisk/config/emulsify-core/.prettierignore | 2 +- whisk/config/emulsify-core/a11y.config.js | 18 ++++ whisk/config/emulsify-core/eslint.config.js | 14 ++++ .../config/emulsify-core/eslintrc.config.json | 17 ---- whisk/config/emulsify-core/storybook/main.js | 84 ++++++++++++++----- .../config/emulsify-core/storybook/preview.js | 41 ++++++++- .../emulsify-core/stylelintrc.config.json | 5 +- .../vite/example.babel.config.cjs | 22 +++++ .../emulsify-core/vite/example.plugins.js | 29 +++++++ .../vite/example.postcss.config.cjs | 10 +++ .../webpack/example.babel.config.js | 18 ---- .../webpack/example.postcss.config.js | 5 -- whisk/config/sd.config.json | 26 ------ whisk/package.json | 23 ++--- whisk/src/foundation.scss | 5 ++ whisk/src/foundation/.gitkeep | 0 whisk/src/layout.scss | 6 ++ whisk/src/layout/.gitkeep | 0 whisk/src/tokens.scss | 4 + 21 files changed, 227 insertions(+), 122 deletions(-) delete mode 100644 whisk/config/configma.json create mode 100644 whisk/config/emulsify-core/a11y.config.js create mode 100644 whisk/config/emulsify-core/eslint.config.js delete mode 100644 whisk/config/emulsify-core/eslintrc.config.json create mode 100644 whisk/config/emulsify-core/vite/example.babel.config.cjs create mode 100644 whisk/config/emulsify-core/vite/example.plugins.js create mode 100644 whisk/config/emulsify-core/vite/example.postcss.config.cjs delete mode 100644 whisk/config/emulsify-core/webpack/example.babel.config.js delete mode 100644 whisk/config/emulsify-core/webpack/example.postcss.config.js delete mode 100644 whisk/config/sd.config.json create mode 100644 whisk/src/foundation.scss delete mode 100644 whisk/src/foundation/.gitkeep create mode 100644 whisk/src/layout.scss delete mode 100644 whisk/src/layout/.gitkeep create mode 100644 whisk/src/tokens.scss diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 4f81699..a7a88ad 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -262,13 +262,18 @@ function runStaticChecks() { ensure(whiskPackage.engines && whiskPackage.engines.node === '>=24', 'whisk/package.json engines.node should be >=24.'); ensure(whiskPackage.type === 'module', 'whisk/package.json should remain an ES module package.'); ensure(whiskPackage.dependencies && whiskPackage.dependencies['@emulsify/core'], 'whisk/package.json must declare @emulsify/core.'); - ensure(whiskPackage.dependencies['@emulsify/core'].startsWith('^4.'), 'whisk/package.json should target Emulsify Core 4.'); + ensure(whiskPackage.dependencies['@emulsify/core'] === '^4.1.0', 'whisk/package.json should target Emulsify Core ^4.1.0.'); ensure(scripts.build && scripts.build.includes('vite build --config node_modules/@emulsify/core/config/vite/vite.config.js'), 'whisk/package.json build script should use the Emulsify Core Vite config.'); ensure(scripts.vite && scripts.vite.includes('vite build --watch'), 'whisk/package.json should expose a Vite watch script.'); ensure(scripts.develop && scripts.develop.includes('npm:vite'), 'whisk/package.json develop script should run the Vite watcher.'); ensure(!scripts.webpack, 'whisk/package.json should not expose a webpack script.'); ensure(!scripts['build-dev'], 'whisk/package.json should not expose the old Webpack build-dev script.'); + ensure(!scripts['tokens:transform'], 'whisk/package.json should not ship a default token-transformer script.'); + ensure(!scripts['tokens:build'], 'whisk/package.json should not ship a default token build script.'); + ensure(!scripts['style-dictionary:build'], 'whisk/package.json should not ship a default Style Dictionary script.'); ensure(!/\bwebpack\b/i.test(scriptText), 'whisk/package.json scripts should not reference Webpack.'); + ensure(!/\btoken-transformer\b/.test(scriptText), 'whisk/package.json scripts should not reference token-transformer.'); + ensure(!/\bstyle-dictionary\b/.test(scriptText), 'whisk/package.json scripts should not reference Style Dictionary.'); ensure(!String(whiskPackage.dependencies['@emulsify/core']).startsWith('^3.'), 'whisk/package.json should not target Emulsify Core 3.'); return `Whisk targets ${whiskPackage.dependencies['@emulsify/core']} with Vite scripts.`; }); diff --git a/whisk/config/configma.json b/whisk/config/configma.json deleted file mode 100644 index 25dd6db..0000000 --- a/whisk/config/configma.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "url": "https://www.figma.com/file/eo87m50yAw3P8iXTypxw4I/Four-Kitchens-UI-Kit?node-id=", - "audioPlayer": "22343%3A575", - "button": "22077%3A27", - "borderRadius": "21931%3A684", - "border": "22092%3A24", - "breakpoint": "21930%3A594", - "lineHeight": "22135%3A97", - "opacity": "22039%3A329", - "size": "3131%3A24", - "spacing": "22039%3A177", - "typeScale": "22135%3A31" -} diff --git a/whisk/config/emulsify-core/.prettierignore b/whisk/config/emulsify-core/.prettierignore index c45aa50..e8c9426 100644 --- a/whisk/config/emulsify-core/.prettierignore +++ b/whisk/config/emulsify-core/.prettierignore @@ -1,4 +1,4 @@ dist .out .coverage -*.min.js \ No newline at end of file +*.min.js diff --git a/whisk/config/emulsify-core/a11y.config.js b/whisk/config/emulsify-core/a11y.config.js new file mode 100644 index 0000000..ccbd7bf --- /dev/null +++ b/whisk/config/emulsify-core/a11y.config.js @@ -0,0 +1,18 @@ +export default { + // A11y linting is done on a component-by-component + // basis, which results in the linter reporting some errors that + // should be ignored. These codes and descriptions allow for those + // errors to be targeted specifically. + ignore: { + // Example: + // codes: ['WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail'], + codes: [], + // Example: + // descriptions: ['This color pair is supplied by third-party content.'], + descriptions: [], + }, + // List of storybook component IDs defined and used in this project. + // Example: + // components: ['components-button--default', 'components-card--default'], + components: [], +}; diff --git a/whisk/config/emulsify-core/eslint.config.js b/whisk/config/emulsify-core/eslint.config.js new file mode 100644 index 0000000..391b919 --- /dev/null +++ b/whisk/config/emulsify-core/eslint.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from 'eslint/config'; +import emulsifyCoreConfig from '../../node_modules/@emulsify/core/config/eslint.config.js'; + +// Extend Emulsify Core's shared flat ESLint config by adding project-specific +// config objects after the spread below. +// +// Example: +// { +// files: ['src/**/*.js', 'components/**/*.js'], +// rules: { +// 'no-unused-vars': 'warn', +// }, +// }, +export default defineConfig([...emulsifyCoreConfig]); diff --git a/whisk/config/emulsify-core/eslintrc.config.json b/whisk/config/emulsify-core/eslintrc.config.json deleted file mode 100644 index 8c43050..0000000 --- a/whisk/config/emulsify-core/eslintrc.config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": [ - "../../node_modules/@emulsify/core/config/eslintrc.config.json" - ], - "globals": { - "Drupal": true, - "drupalSettings": true, - "drupalTranslations": true, - "domready": true, - "jQuery": true, - "_": true, - "matchMedia": true, - "Backbone": true, - "Modernizr": true, - "CKEDITOR": true - } -} diff --git a/whisk/config/emulsify-core/storybook/main.js b/whisk/config/emulsify-core/storybook/main.js index f608f7e..f2b0702 100644 --- a/whisk/config/emulsify-core/storybook/main.js +++ b/whisk/config/emulsify-core/storybook/main.js @@ -1,30 +1,68 @@ -// Pass an empty configOverrides by default. +// This file is loaded by Emulsify Core's shared Storybook main config. +// +// Keep the default export as an object. Emulsify Core shallow-merges this object +// into its default Storybook config. Addons are handled specially: project +// addons are appended to Emulsify Core's defaults unless replaceAddons is true. +// +// This file is for Node-side Storybook configuration, such as: +// - addons +// - static asset directories +// - final Storybook config patches +// +// Browser-side story parameters belong in preview.js. +// Storybook manager branding belongs in theme.js. + +// Pass empty config overrides by default so generated themes inherit Emulsify +// Core's stories, framework, Vite builder, Twig handling, and default addons. const configOverrides = {}; -// Uncomment the following section to override the default Emulsify Core configuration. -// Doing so is a complete override so no configuration from Emulsify Core's main.js will be inherited. -// See https://storybook.js.org/docs/7/configure for details. +// Example: add a project-specific addon and static directory. +// +// Install the addon in the generated theme first: +// npm install --save-dev @storybook/addon-viewport +// +// const configOverrides = { +// addons: ['@storybook/addon-viewport'], +// staticDirs: ['public'], +// }; + +// Example: customize an Emulsify Core default addon without duplicating it. +// // const configOverrides = { -// stories: [ -// '../../../../components/**/*.stories.@(js|jsx|ts|tsx)', -// ], // addons: [ -// '../../../@storybook/addon-a11y', -// '../../../@storybook/addon-links', -// '../../../@storybook/addon-essentials', -// '../../../@storybook/addon-themes', -// '../../../@storybook/addon-styling-webpack' +// { +// name: '@storybook/addon-a11y', +// options: { +// manual: true, +// }, +// }, // ], -// core: { -// builder: 'webpack5', -// }, -// framework: { -// name: '@storybook/html-webpack5', -// options: {}, -// }, -// docs: { -// autodocs: true, -// }, // }; -export default configOverrides; \ No newline at end of file +// Example: replace the full addon list instead of appending to Core defaults. +// Use this only when the project intentionally opts out of Emulsify defaults. +// +// export const replaceAddons = true; +// +// const configOverrides = { +// addons: ['@storybook/addon-viewport'], +// }; + +// Example: patch the final Storybook config after Emulsify Core applies the +// default export above. The env object is the normalized project.emulsify.json +// model resolved by Emulsify Core. +// +// export function extendConfig(config, { env }) { +// return { +// ...config, +// staticDirs: +// env.platform === 'wordpress' +// ? [...(config.staticDirs || []), 'storybook-static-assets'] +// : config.staticDirs, +// }; +// } + +// Avoid redefining stories, framework, core.builder, or viteFinal unless the +// project is intentionally replacing Emulsify Core's Storybook integration. + +export default configOverrides; diff --git a/whisk/config/emulsify-core/storybook/preview.js b/whisk/config/emulsify-core/storybook/preview.js index ad7c4bf..253de17 100644 --- a/whisk/config/emulsify-core/storybook/preview.js +++ b/whisk/config/emulsify-core/storybook/preview.js @@ -1,5 +1,40 @@ -// export project specific parameters as config overrides. +// This file is loaded in Storybook's browser preview iframe through Vite. +// +// Use it for Storybook preview parameters and browser-side imports that stories +// need, such as global CSS. Emulsify Core keeps its default a11y parameters and +// merges this project's parameter overrides into them. +// // See https://storybook.js.org/docs/writing-stories/parameters#story-parameters. -const overrideParams = {}; -export default overrideParams; \ No newline at end of file +// Example: load project CSS into every story. +// +// import '../../../src/global/storybook.css'; + +// Example: override selected Storybook parameters. +// +// export const parameters = { +// layout: 'fullscreen', +// backgrounds: { +// default: 'light', +// values: [ +// { name: 'light', value: '#ffffff' }, +// { name: 'dark', value: '#00202E' }, +// ], +// }, +// controls: { +// expanded: true, +// matchers: { +// color: /(background|color)$/i, +// date: /Date$/i, +// }, +// }, +// a11y: { +// config: { +// detailedReport: true, +// }, +// }, +// }; + +export const parameters = {}; + +export default parameters; diff --git a/whisk/config/emulsify-core/stylelintrc.config.json b/whisk/config/emulsify-core/stylelintrc.config.json index 7bad9d6..f4c7901 100644 --- a/whisk/config/emulsify-core/stylelintrc.config.json +++ b/whisk/config/emulsify-core/stylelintrc.config.json @@ -1,8 +1,5 @@ { "extends": [ "../../node_modules/@emulsify/core/config/.stylelintrc.json" - ], - "rules": { - "no-duplicate-selectors": null - } + ] } diff --git a/whisk/config/emulsify-core/vite/example.babel.config.cjs b/whisk/config/emulsify-core/vite/example.babel.config.cjs new file mode 100644 index 0000000..733fd57 --- /dev/null +++ b/whisk/config/emulsify-core/vite/example.babel.config.cjs @@ -0,0 +1,22 @@ +// Rename this file to babel.config.cjs only when a project-specific Vite plugin +// or build tool is configured to load Babel. + +module.exports = (api) => { + api.cache(true); + + return { + presets: [ + // Example: + // + // [ + // '@babel/preset-env', + // { + // useBuiltIns: 'entry', + // corejs: 3, + // }, + // ], + // ['minify', { builtIns: false }], + ], + comments: false, + }; +}; diff --git a/whisk/config/emulsify-core/vite/example.plugins.js b/whisk/config/emulsify-core/vite/example.plugins.js new file mode 100644 index 0000000..cb88510 --- /dev/null +++ b/whisk/config/emulsify-core/vite/example.plugins.js @@ -0,0 +1,29 @@ +// Rename this file to plugins.js, plugins.mjs, or plugins.cjs when the project +// needs to extend Emulsify Core's Vite config. +// +// This file is intentionally a no-op example. Uncomment only the pieces your +// project needs. + +// Example: add Vite plugins from project dependencies. +// +// import Inspect from 'vite-plugin-inspect'; +// +// export default function projectPlugins({ env }) { +// return [ +// Inspect(), +// ]; +// } + +export default []; + +// Example: patch the final Vite config after Emulsify Core assembles it. +// Prefer plugin exports for normal framework integration; use extendConfig() +// when a Vite option cannot be expressed through a plugin. +// +// export function extendConfig(config, { env }) { +// return { +// css: { +// postcss: './config/emulsify-core/vite/postcss.config.cjs', +// }, +// }; +// } diff --git a/whisk/config/emulsify-core/vite/example.postcss.config.cjs b/whisk/config/emulsify-core/vite/example.postcss.config.cjs new file mode 100644 index 0000000..420a497 --- /dev/null +++ b/whisk/config/emulsify-core/vite/example.postcss.config.cjs @@ -0,0 +1,10 @@ +// Rename this file to postcss.config.cjs when a project needs its own PostCSS +// setup, then point Vite to it from plugins.js with extendConfig(). + +module.exports = { + plugins: [ + // Example: + // + // require('autoprefixer')(), + ], +}; diff --git a/whisk/config/emulsify-core/webpack/example.babel.config.js b/whisk/config/emulsify-core/webpack/example.babel.config.js deleted file mode 100644 index 495063e..0000000 --- a/whisk/config/emulsify-core/webpack/example.babel.config.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = (api) => { - api.cache(true); - - const presets = [ - [ - '@babel/preset-env', - { - useBuiltIns: 'entry', - corejs: 3, - }, - ], - ['minify', { builtIns: false }], - ]; - - const comments = false; - - return { presets, comments }; -}; diff --git a/whisk/config/emulsify-core/webpack/example.postcss.config.js b/whisk/config/emulsify-core/webpack/example.postcss.config.js deleted file mode 100644 index 6e64681..0000000 --- a/whisk/config/emulsify-core/webpack/example.postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -const autoPrefixer = require('autoprefixer'); - -module.exports = { - plugins: [autoPrefixer()], -}; diff --git a/whisk/config/sd.config.json b/whisk/config/sd.config.json deleted file mode 100644 index 2387529..0000000 --- a/whisk/config/sd.config.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "source": [ - "dist/tokens/transformed.tokens.json" - ], - "log": { - "verbosity": "verbose" - }, - "platforms": { - "scss": { - "transforms": [ - "attribute/cti", - "name/kebab", - "time/seconds", - "html/icon", - "color/css" - ], - "files": [ - { - "destination": "src/tokens/_tokens.scss", - "format": "scss/variables", - "mapName": "my-tokens" - } - ] - } - } -} \ No newline at end of file diff --git a/whisk/package.json b/whisk/package.json index de7aeb2..ae433c4 100644 --- a/whisk/package.json +++ b/whisk/package.json @@ -18,8 +18,8 @@ "type": "module", "scripts": { "a11y": "npm run storybook-build && node_modules/@emulsify/core/scripts/a11y.js -r", - "audit": "node_modules/@emulsify/core/scripts/audit.js", - "audit:twig-stories": "node_modules/@emulsify/core/scripts/audit-twig-stories.js", + "audit": "sh -c 'node_modules/@emulsify/core/scripts/audit.js \"$@\"; status=$?; printf \"\\nAudit docs: https://github.com/emulsify-ds/emulsify-core/blob/4.x/docs/migration-4x.md#storybook-migration\\n\"; exit $status' --", + "audit:twig-stories": "sh -c 'node_modules/@emulsify/core/scripts/audit-twig-stories.js \"$@\"; status=$?; printf \"\\nMigration docs: https://github.com/emulsify-ds/emulsify-core/blob/4.x/docs/storybook.md#legacy-twig-story-compatibility\\n\"; exit $status' --", "build": "npm run ensure-dist && vite build --config node_modules/@emulsify/core/config/vite/vite.config.js", "coverage": "npm run test && open-cli .coverage/lcov-report/index.html", "develop": "npm run ensure-dist && concurrently --raw --no-shell npm:vite npm:storybook", @@ -27,22 +27,23 @@ "format": "npm run lint-fix; npm run prettier-fix", "lint": "npm run lint-js; npm run lint-styles", "lint-fix": "npm run lint-js -- --fix; npm run lint-styles -- --fix", - "lint-js": "eslint --config config/emulsify-core/eslintrc.config.json --no-eslintrc --no-error-on-unmatched-pattern ./components", + "lint-js": "eslint --config config/emulsify-core/eslint.config.js --no-error-on-unmatched-pattern ./config ./src ./components", "lint-staged": "lint-staged", - "lint-styles": "stylelint --config config/emulsify-core/stylelintrc.config.json './components/**/*.scss'", - "prettier": "prettier --config config/emulsify-core/prettierrc.config.json --ignore-path config/emulsify-core/.prettierignore --ignore-unknown \"./components/**/*.{js,yml,scss,md}\"", - "prettier-fix": "prettier --config config/emulsify-core/prettierrc.config.json --ignore-path config/emulsify-core/.prettierignore --write --ignore-unknown \"./components/**/*.{js,yml,scss,md}\"", + "lint-styles": "stylelint --config config/emulsify-core/stylelintrc.config.json --allow-empty-input './src/**/*.scss' './components/**/*.scss'", + "prettier": "prettier --config config/emulsify-core/prettierrc.config.json --ignore-path config/emulsify-core/.prettierignore --ignore-unknown --no-error-on-unmatched-pattern './config/**/*.{js,cjs,mjs,json,yml,scss,md,html}' './src/**/*.{js,cjs,mjs,json,yml,scss,md,html}' './components/**/*.{js,cjs,mjs,json,yml,scss,md,html}'", + "prettier-fix": "prettier --config config/emulsify-core/prettierrc.config.json --ignore-path config/emulsify-core/.prettierignore --write --ignore-unknown --no-error-on-unmatched-pattern './config/**/*.{js,cjs,mjs,json,yml,scss,md,html}' './src/**/*.{js,cjs,mjs,json,yml,scss,md,html}' './components/**/*.{js,cjs,mjs,json,yml,scss,md,html}'", "storybook": "NODE_OPTIONS=--no-deprecation storybook dev -c node_modules/@emulsify/core/.storybook --ci -p 6006", "storybook-build": "npm run build && storybook build -c node_modules/@emulsify/core/.storybook -o .out", - "storybook-deploy": "storybook-to-ghpages -o .out", - "style-dictionary:build": "node ./src/tokens/tokensTransform.mjs", "test": "jest --coverage --passWithNoTests --config ./config/jest.config.js", - "tokens:build": "npm run tokens:transform && npm run style-dictionary:build", - "tokens:transform": "token-transformer ./src/tokens/figma.tokens.json ./src/tokens/transformed.tokens.json", "twatch": "jest --no-coverage --watch --verbose --passWithNoTests --config ./config/jest.config.js", "vite": "vite build --watch --config node_modules/@emulsify/core/config/vite/vite.config.js" }, "dependencies": { - "@emulsify/core": "^4.0.0" + "@emulsify/core": "^4.1.0" + }, + "overrides": { + "glob": "^13.0.6", + "locutus": "^3.0.36", + "minimatch@3.0.x": "^3.1.5" } } diff --git a/whisk/src/foundation.scss b/whisk/src/foundation.scss new file mode 100644 index 0000000..0e2948b --- /dev/null +++ b/whisk/src/foundation.scss @@ -0,0 +1,5 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} diff --git a/whisk/src/foundation/.gitkeep b/whisk/src/foundation/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/whisk/src/layout.scss b/whisk/src/layout.scss new file mode 100644 index 0000000..6ea20e0 --- /dev/null +++ b/whisk/src/layout.scss @@ -0,0 +1,6 @@ +img, +svg, +video { + max-width: 100%; + height: auto; +} diff --git a/whisk/src/layout/.gitkeep b/whisk/src/layout/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/whisk/src/tokens.scss b/whisk/src/tokens.scss new file mode 100644 index 0000000..d2ec2be --- /dev/null +++ b/whisk/src/tokens.scss @@ -0,0 +1,4 @@ +:root { + --emulsify-color-text: #111; + --emulsify-color-background: #fff; +} From d924e9d4ca90d052f96fe782f34a8619f911dcf1 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 09:38:04 -0500 Subject: [PATCH 08/55] feat: align whisk starter structure with emulsify drupal --- .github/scripts/release-check.cjs | 26 ++++++++++++++++++++ components/.gitkeep | 0 whisk/assets/audio/.gitkeep | 0 whisk/assets/icons/angle-down.svg | 6 ----- whisk/assets/icons/arrowLeft.svg | 1 - whisk/assets/icons/arrowRight.svg | 16 ------------ whisk/assets/icons/chevron-down.svg | 4 --- whisk/assets/icons/chevronLeft.svg | 1 - whisk/assets/icons/chevronRight.svg | 1 - whisk/assets/icons/circle-info.svg | 4 --- whisk/assets/icons/close-alt.svg | 1 - whisk/assets/icons/close.svg | 3 --- whisk/assets/icons/crisisAlert.svg | 8 ------ whisk/assets/icons/download.svg | 27 --------------------- whisk/assets/icons/expand.svg | 1 - whisk/assets/icons/facebook.svg | 3 --- whisk/assets/icons/home.svg | 4 --- whisk/assets/icons/instagram.svg | 4 --- whisk/assets/icons/linkedin.svg | 5 ---- whisk/assets/icons/list.svg | 3 --- whisk/assets/icons/menu-close.svg | 5 ---- whisk/assets/icons/menu.svg | 3 --- whisk/assets/icons/pause.svg | 1 - whisk/assets/icons/play.svg | 10 -------- whisk/assets/icons/programs-all.svg | 8 ------ whisk/assets/icons/programs-by-category.svg | 12 --------- whisk/assets/icons/programs-switch-view.svg | 4 --- whisk/assets/icons/search.svg | 3 --- whisk/assets/icons/starburst.svg | 23 ------------------ whisk/assets/icons/submenu.svg | 3 --- whisk/assets/icons/triangle-exclamation.svg | 4 --- whisk/assets/icons/twitter.svg | 3 --- whisk/assets/icons/xmark.svg | 4 --- whisk/assets/icons/youtube.svg | 3 --- whisk/assets/video/.gitkeep | 0 whisk/components/.gitkeep | 0 whisk/project.emulsify.json | 2 +- 37 files changed, 27 insertions(+), 179 deletions(-) delete mode 100644 components/.gitkeep delete mode 100644 whisk/assets/audio/.gitkeep delete mode 100644 whisk/assets/icons/angle-down.svg delete mode 100644 whisk/assets/icons/arrowLeft.svg delete mode 100644 whisk/assets/icons/arrowRight.svg delete mode 100644 whisk/assets/icons/chevron-down.svg delete mode 100644 whisk/assets/icons/chevronLeft.svg delete mode 100644 whisk/assets/icons/chevronRight.svg delete mode 100644 whisk/assets/icons/circle-info.svg delete mode 100644 whisk/assets/icons/close-alt.svg delete mode 100644 whisk/assets/icons/close.svg delete mode 100644 whisk/assets/icons/crisisAlert.svg delete mode 100644 whisk/assets/icons/download.svg delete mode 100644 whisk/assets/icons/expand.svg delete mode 100644 whisk/assets/icons/facebook.svg delete mode 100644 whisk/assets/icons/home.svg delete mode 100644 whisk/assets/icons/instagram.svg delete mode 100644 whisk/assets/icons/linkedin.svg delete mode 100644 whisk/assets/icons/list.svg delete mode 100644 whisk/assets/icons/menu-close.svg delete mode 100644 whisk/assets/icons/menu.svg delete mode 100644 whisk/assets/icons/pause.svg delete mode 100644 whisk/assets/icons/play.svg delete mode 100644 whisk/assets/icons/programs-all.svg delete mode 100644 whisk/assets/icons/programs-by-category.svg delete mode 100644 whisk/assets/icons/programs-switch-view.svg delete mode 100644 whisk/assets/icons/search.svg delete mode 100644 whisk/assets/icons/starburst.svg delete mode 100644 whisk/assets/icons/submenu.svg delete mode 100644 whisk/assets/icons/triangle-exclamation.svg delete mode 100644 whisk/assets/icons/twitter.svg delete mode 100644 whisk/assets/icons/xmark.svg delete mode 100644 whisk/assets/icons/youtube.svg delete mode 100644 whisk/assets/video/.gitkeep delete mode 100644 whisk/components/.gitkeep diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index a7a88ad..967db33 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -186,6 +186,7 @@ function runStaticChecks() { const composer = readJson('composer.json'); const rootThemeHeader = parseWordPressThemeHeader('style.css'); const whiskThemeHeader = parseWordPressThemeHeader('whisk/style.css'); + const whiskProject = readJson('whisk/project.emulsify.json'); const releaseConfig = require(path.join(repoRoot, 'release.config.js')); const semanticReleaseWorkflow = readFile('.github/workflows/semantic-release.yml'); @@ -261,6 +262,14 @@ function runStaticChecks() { ensure(whiskPackage.license === 'GPL-2.0-only', 'whisk/package.json license should align with the WordPress theme.'); ensure(whiskPackage.engines && whiskPackage.engines.node === '>=24', 'whisk/package.json engines.node should be >=24.'); ensure(whiskPackage.type === 'module', 'whisk/package.json should remain an ES module package.'); + ensure(whiskProject.project.platform === 'none', 'whisk/project.emulsify.json should use platform "none" until Core ships a WordPress adapter.'); + ensure(whiskProject.project.name === 'whisk', 'whisk/project.emulsify.json project.name should remain whisk.'); + ensure(whiskProject.project.machineName === 'whisk', 'whisk/project.emulsify.json project.machineName should remain whisk.'); + ensure(fs.existsSync(path.join(repoRoot, 'whisk/src/components')), 'whisk/src/components should be the primary component source.'); + ensure(!fs.existsSync(path.join(repoRoot, 'whisk/components')), 'whisk/components should not remain as an unused starter placeholder.'); + for (const entryFile of ['foundation.scss', 'layout.scss', 'tokens.scss']) { + ensure(fs.existsSync(path.join(repoRoot, 'whisk/src', entryFile)), `whisk/src/${entryFile} should exist for Core 4 Vite global entries.`); + } ensure(whiskPackage.dependencies && whiskPackage.dependencies['@emulsify/core'], 'whisk/package.json must declare @emulsify/core.'); ensure(whiskPackage.dependencies['@emulsify/core'] === '^4.1.0', 'whisk/package.json should target Emulsify Core ^4.1.0.'); ensure(scripts.build && scripts.build.includes('vite build --config node_modules/@emulsify/core/config/vite/vite.config.js'), 'whisk/package.json build script should use the Emulsify Core Vite config.'); @@ -278,6 +287,23 @@ function runStaticChecks() { return `Whisk targets ${whiskPackage.dependencies['@emulsify/core']} with Vite scripts.`; }); + runStaticCheck('Starter asset placeholders', () => { + const iconFiles = listFilesRecursive('whisk/assets/icons', (file) => path.basename(file) !== '.gitkeep'); + const expectedPlaceholders = [ + 'whisk/assets/fonts/.gitkeep', + 'whisk/assets/icons/.gitkeep', + 'whisk/assets/images/.gitkeep', + ]; + + ensure(iconFiles.length === 0, `Remove client-specific starter icons: ${iconFiles.join(', ')}.`); + for (const placeholder of expectedPlaceholders) { + ensure(fs.existsSync(path.join(repoRoot, placeholder)), `${placeholder} should keep the starter asset directory.`); + } + ensure(!fs.existsSync(path.join(repoRoot, 'whisk/assets/audio')), 'whisk/assets/audio should not ship as a default starter directory.'); + ensure(!fs.existsSync(path.join(repoRoot, 'whisk/assets/video')), 'whisk/assets/video should not ship as a default starter directory.'); + return 'Whisk keeps only generic starter asset placeholders.'; + }); + runStaticCheck('Duplicate package scripts', () => { const duplicates = [ ...findDuplicatePackageScripts('package.json').map((key) => `package.json:${key}`), diff --git a/components/.gitkeep b/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/whisk/assets/audio/.gitkeep b/whisk/assets/audio/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/whisk/assets/icons/angle-down.svg b/whisk/assets/icons/angle-down.svg deleted file mode 100644 index d3fc045..0000000 --- a/whisk/assets/icons/angle-down.svg +++ /dev/null @@ -1,6 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! - Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --> - <path - d="M360.5 217.5l-152 143.1C203.9 365.8 197.9 368 192 368s-11.88-2.188-16.5-6.562L23.5 217.5C13.87 208.3 13.47 193.1 22.56 183.5C31.69 173.8 46.94 173.5 56.5 182.6L192 310.9l135.5-128.4c9.562-9.094 24.75-8.75 33.94 .9375C370.5 193.1 370.1 208.3 360.5 217.5z" /> -</svg> \ No newline at end of file diff --git a/whisk/assets/icons/arrowLeft.svg b/whisk/assets/icons/arrowLeft.svg deleted file mode 100644 index 0c9252c..0000000 --- a/whisk/assets/icons/arrowLeft.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 3334 3334" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="icons"><g id="ui-gambling-website-lined-icnos-casinoshunter"><g id="_1" serif:id="1"><path id="right-arrow" d="M981.391,2712.26l-922.2,-914.391l-7.71,-7.155c-30.857,-30.598 -48.809,-70.952 -51.481,-118.63l0.154,-16.028c2.238,-37.768 16.671,-73.849 45.114,-106.181l10.762,-11.107l925.361,-917.699c68.435,-67.861 179.085,-67.861 247.519,0c68.844,68.267 68.844,179.257 0.006,247.519l-638.939,633.699l2567.96,-0.063c96.702,-0 175.392,78.03 175.392,174.657c0,96.627 -78.69,174.658 -175.39,174.658l-2547.35,0.063l618.314,613.134c68.844,68.267 68.844,179.257 0,247.524c-68.434,67.861 -179.084,67.861 -247.519,0Z" style="fill-rule:nonzero;"/></g></g></g></svg> \ No newline at end of file diff --git a/whisk/assets/icons/arrowRight.svg b/whisk/assets/icons/arrowRight.svg deleted file mode 100644 index 1e578a3..0000000 --- a/whisk/assets/icons/arrowRight.svg +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> -<svg width="800px" height="800px" viewBox="0 -6.5 38 38" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> - - <title>right-arrow - Created with Sketch. - - - - - - - - - - \ No newline at end of file diff --git a/whisk/assets/icons/chevron-down.svg b/whisk/assets/icons/chevron-down.svg deleted file mode 100644 index 9b2dcc1..0000000 --- a/whisk/assets/icons/chevron-down.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/chevronLeft.svg b/whisk/assets/icons/chevronLeft.svg deleted file mode 100644 index 045a093..0000000 --- a/whisk/assets/icons/chevronLeft.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/whisk/assets/icons/chevronRight.svg b/whisk/assets/icons/chevronRight.svg deleted file mode 100644 index f2a6bc2..0000000 --- a/whisk/assets/icons/chevronRight.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/whisk/assets/icons/circle-info.svg b/whisk/assets/icons/circle-info.svg deleted file mode 100644 index 8828c26..0000000 --- a/whisk/assets/icons/circle-info.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/close-alt.svg b/whisk/assets/icons/close-alt.svg deleted file mode 100644 index 7440a67..0000000 --- a/whisk/assets/icons/close-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/whisk/assets/icons/close.svg b/whisk/assets/icons/close.svg deleted file mode 100644 index 421d698..0000000 --- a/whisk/assets/icons/close.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/crisisAlert.svg b/whisk/assets/icons/crisisAlert.svg deleted file mode 100644 index 4d47037..0000000 --- a/whisk/assets/icons/crisisAlert.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/whisk/assets/icons/download.svg b/whisk/assets/icons/download.svg deleted file mode 100644 index df6233f..0000000 --- a/whisk/assets/icons/download.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/whisk/assets/icons/expand.svg b/whisk/assets/icons/expand.svg deleted file mode 100644 index c0c66e5..0000000 --- a/whisk/assets/icons/expand.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/whisk/assets/icons/facebook.svg b/whisk/assets/icons/facebook.svg deleted file mode 100644 index 94ea2c1..0000000 --- a/whisk/assets/icons/facebook.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/home.svg b/whisk/assets/icons/home.svg deleted file mode 100644 index 657b7af..0000000 --- a/whisk/assets/icons/home.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/whisk/assets/icons/instagram.svg b/whisk/assets/icons/instagram.svg deleted file mode 100644 index e4318d5..0000000 --- a/whisk/assets/icons/instagram.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/linkedin.svg b/whisk/assets/icons/linkedin.svg deleted file mode 100644 index 6bed756..0000000 --- a/whisk/assets/icons/linkedin.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/list.svg b/whisk/assets/icons/list.svg deleted file mode 100644 index d2085ee..0000000 --- a/whisk/assets/icons/list.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/menu-close.svg b/whisk/assets/icons/menu-close.svg deleted file mode 100644 index 3201bc6..0000000 --- a/whisk/assets/icons/menu-close.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/menu.svg b/whisk/assets/icons/menu.svg deleted file mode 100644 index 69f2c93..0000000 --- a/whisk/assets/icons/menu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/pause.svg b/whisk/assets/icons/pause.svg deleted file mode 100644 index 641a58e..0000000 --- a/whisk/assets/icons/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/whisk/assets/icons/play.svg b/whisk/assets/icons/play.svg deleted file mode 100644 index 495fd0f..0000000 --- a/whisk/assets/icons/play.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/whisk/assets/icons/programs-all.svg b/whisk/assets/icons/programs-all.svg deleted file mode 100644 index c230a22..0000000 --- a/whisk/assets/icons/programs-all.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/whisk/assets/icons/programs-by-category.svg b/whisk/assets/icons/programs-by-category.svg deleted file mode 100644 index f57f57d..0000000 --- a/whisk/assets/icons/programs-by-category.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/whisk/assets/icons/programs-switch-view.svg b/whisk/assets/icons/programs-switch-view.svg deleted file mode 100644 index 3f8c3a5..0000000 --- a/whisk/assets/icons/programs-switch-view.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/whisk/assets/icons/search.svg b/whisk/assets/icons/search.svg deleted file mode 100644 index 7f88f07..0000000 --- a/whisk/assets/icons/search.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/starburst.svg b/whisk/assets/icons/starburst.svg deleted file mode 100644 index 1ebb1b5..0000000 --- a/whisk/assets/icons/starburst.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/whisk/assets/icons/submenu.svg b/whisk/assets/icons/submenu.svg deleted file mode 100644 index 767de0b..0000000 --- a/whisk/assets/icons/submenu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/triangle-exclamation.svg b/whisk/assets/icons/triangle-exclamation.svg deleted file mode 100644 index 43ef0d9..0000000 --- a/whisk/assets/icons/triangle-exclamation.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/twitter.svg b/whisk/assets/icons/twitter.svg deleted file mode 100644 index 151a319..0000000 --- a/whisk/assets/icons/twitter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/icons/xmark.svg b/whisk/assets/icons/xmark.svg deleted file mode 100644 index 1a2f3b6..0000000 --- a/whisk/assets/icons/xmark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/whisk/assets/icons/youtube.svg b/whisk/assets/icons/youtube.svg deleted file mode 100644 index 992d54a..0000000 --- a/whisk/assets/icons/youtube.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/whisk/assets/video/.gitkeep b/whisk/assets/video/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/whisk/components/.gitkeep b/whisk/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/whisk/project.emulsify.json b/whisk/project.emulsify.json index b1f47c9..5444712 100644 --- a/whisk/project.emulsify.json +++ b/whisk/project.emulsify.json @@ -1,6 +1,6 @@ { "project": { - "platform": "wordpress", + "platform": "none", "name": "whisk", "machineName": "whisk" }, From 2c1af6178e4d1f8a61a139bc029249d3c54c213b Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 09:51:17 -0500 Subject: [PATCH 09/55] feat: rebuild timber theme bootstrap --- functions.php | 20 +-- includes/class-assets.php | 78 +++++++++ includes/class-blocks.php | 168 +++++++++++++++++++ includes/class-bootstrap.php | 101 ++++++++++++ includes/class-cli.php | 165 +++++++++++++++++++ includes/class-context.php | 134 +++++++++++++++ includes/class-emulsify-cli.php | 174 -------------------- includes/class-emulsify-theme.php | 218 ------------------------ includes/class-missing-timber.php | 94 +++++++++++ includes/class-setup.php | 76 +++++++++ includes/class-timber-integration.php | 42 +++++ includes/class-twig.php | 228 ++++++++++++++++++++++++++ includes/global-assets.php | 61 ------- includes/register-blocks.php | 100 ----------- includes/twig-namespaces.php | 15 -- 15 files changed, 1094 insertions(+), 580 deletions(-) create mode 100644 includes/class-assets.php create mode 100644 includes/class-blocks.php create mode 100644 includes/class-bootstrap.php create mode 100644 includes/class-cli.php create mode 100644 includes/class-context.php delete mode 100644 includes/class-emulsify-cli.php delete mode 100644 includes/class-emulsify-theme.php create mode 100644 includes/class-missing-timber.php create mode 100644 includes/class-setup.php create mode 100644 includes/class-timber-integration.php create mode 100644 includes/class-twig.php delete mode 100644 includes/global-assets.php delete mode 100644 includes/register-blocks.php delete mode 100644 includes/twig-namespaces.php diff --git a/functions.php b/functions.php index 657fe2e..85b08c7 100644 --- a/functions.php +++ b/functions.php @@ -1,18 +1,14 @@ - * @since 1.0.0 + * @package Emulsify */ +if ( ! defined( 'ABSPATH' ) ) { + return; +} + +require_once __DIR__ . '/includes/class-bootstrap.php'; -require 'includes/class-emulsify-cli.php'; -require 'includes/class-emulsify-theme.php'; -require 'includes/twig-namespaces.php'; -require 'includes/register-blocks.php'; -require 'includes/global-assets.php'; +\Emulsify\Theme\Bootstrap::init( __DIR__ ); diff --git a/includes/class-assets.php b/includes/class-assets.php new file mode 100644 index 0000000..ff6c452 --- /dev/null +++ b/includes/class-assets.php @@ -0,0 +1,78 @@ +enqueue_global_styles( 'emulsify' ); + } + + /** + * Enqueues global block editor styles. + * + * @return void + */ + public function editor_styles(): void { + $this->enqueue_global_styles( 'emulsify-editor' ); + } + + /** + * Enqueues all CSS files from dist/global. + * + * @param string $context Handle prefix. + * @return void + */ + private function enqueue_global_styles( string $context ): void { + $base_path = get_theme_file_path( 'dist/global' ); + $base_uri = get_theme_file_uri( 'dist/global' ); + + if ( ! is_dir( $base_path ) || ! is_readable( $base_path ) ) { + return; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $base_path, \RecursiveDirectoryIterator::SKIP_DOTS ) + ); + + foreach ( $iterator as $file ) { + if ( ! $file->isFile() || 'css' !== $file->getExtension() ) { + continue; + } + + $relative_path = ltrim( str_replace( $base_path, '', $file->getPathname() ), '/\\' ); + $relative_path = str_replace( '\\', '/', $relative_path ); + $handle = $context . '-' . sanitize_key( str_replace( '/', '-', basename( $file->getBasename(), '.css' ) ) ); + + wp_enqueue_style( + $handle, + trailingslashit( $base_uri ) . $relative_path, + array(), + filemtime( $file->getPathname() ) + ); + } + } +} diff --git a/includes/class-blocks.php b/includes/class-blocks.php new file mode 100644 index 0000000..8c17b89 --- /dev/null +++ b/includes/class-blocks.php @@ -0,0 +1,168 @@ +isDir() || $item->isDot() ) { + continue; + } + + $slug = $item->getFilename(); + $json_path = $item->getPathname() . '/' . $slug . '.component.json'; + $twig_path = 'dist/components/' . $slug . '/' . $slug . '.twig'; + + if ( ! is_readable( $json_path ) || ! is_readable( get_stylesheet_directory() . '/' . $twig_path ) ) { + continue; + } + + $config_contents = file_get_contents( $json_path ); + + if ( ! is_string( $config_contents ) ) { + continue; + } + + $config = json_decode( $config_contents, true ); + + if ( ! is_array( $config ) || JSON_ERROR_NONE !== json_last_error() ) { + continue; + } + + $block_args = array_merge( + array( + 'name' => 'emulsify/' . sanitize_title( $slug ), + 'title' => ucwords( str_replace( '-', ' ', $slug ) ), + 'mode' => 'preview', + 'render_callback' => array( $this, 'render_block' ), + 'twig_template' => $twig_path, + ), + $config + ); + + acf_register_block_type( $block_args ); + } + } + + /** + * Renders an ACF block with Timber. + * + * @param array $block Block settings and attributes. + * @param string $content Inner block content. + * @param bool $is_preview TRUE during editor preview render. + * @param mixed $post_id Current post ID. + * @return void + */ + public function render_block( $block, $content = '', $is_preview = false, $post_id = 0 ): void { + if ( ! is_array( $block ) ) { + $this->render_error( __( 'Block rendering requires valid block metadata.', 'emulsify' ) ); + return; + } + + if ( ! class_exists( '\Timber\Timber' ) ) { + $this->render_error( __( 'Block rendering requires Timber.', 'emulsify' ) ); + return; + } + + $context = \Timber\Timber::context(); + $context['block'] = $block; + $context['content'] = is_scalar( $content ) ? (string) $content : ''; + $context['fields'] = $this->fields( $post_id ); + $context['is_preview'] = (bool) $is_preview; + $template = $this->template( $block ); + + if ( '' === $template ) { + $this->render_error( __( 'Block rendering error: no Twig template defined.', 'emulsify' ) ); + return; + } + + try { + \Timber\Timber::render( $template, $context ); + } catch ( \Throwable $throwable ) { + $this->render_error( $throwable->getMessage() ); + } + } + + /** + * Gets ACF fields when ACF is active. + * + * @param mixed $post_id Current post ID. + * @return array ACF field values. + */ + private function fields( $post_id ): array { + if ( ! function_exists( 'get_fields' ) ) { + return array(); + } + + $fields = get_fields( $post_id ); + + return is_array( $fields ) ? $fields : array(); + } + + /** + * Gets the block Twig template. + * + * @param array $block Block settings and attributes. + * @return string Template path. + */ + private function template( array $block ): string { + if ( ! empty( $block['data']['twig_template'] ) && is_string( $block['data']['twig_template'] ) ) { + return $block['data']['twig_template']; + } + + if ( ! empty( $block['twig_template'] ) && is_string( $block['twig_template'] ) ) { + return $block['twig_template']; + } + + return ''; + } + + /** + * Renders an editor-only block error. + * + * @param string $message Error message. + * @return void + */ + private function render_error( string $message ): void { + if ( current_user_can( 'edit_posts' ) ) { + printf( + '

    %s %s

    ', + esc_html__( 'Emulsify block error:', 'emulsify' ), + esc_html( $message ) + ); + } + } +} diff --git a/includes/class-bootstrap.php b/includes/class-bootstrap.php new file mode 100644 index 0000000..764f833 --- /dev/null +++ b/includes/class-bootstrap.php @@ -0,0 +1,101 @@ +register(); + } + + /** + * Constructor. + * + * @param string $theme_dir Absolute path to the parent theme directory. + */ + private function __construct( string $theme_dir ) { + $this->theme_dir = rtrim( $theme_dir, '/\\' ); + } + + /** + * Registers all theme services. + * + * @return void + */ + private function register(): void { + $this->load_vendor_autoload(); + $this->load_classes(); + + ( new Setup() )->register(); + ( new Assets() )->register(); + ( new Blocks() )->register(); + ( new Cli() )->register(); + + $timber = new Timber_Integration(); + + if ( $timber->register() ) { + ( new Context() )->register(); + ( new Twig() )->register(); + return; + } + + ( new Missing_Timber() )->register(); + } + + /** + * Loads Composer dependencies when the theme was installed with Composer. + * + * @return void + */ + private function load_vendor_autoload(): void { + $autoload = $this->theme_dir . '/vendor/autoload.php'; + + if ( is_readable( $autoload ) ) { + require_once $autoload; + } + } + + /** + * Loads theme service classes. + * + * @return void + */ + private function load_classes(): void { + $files = array( + 'class-assets.php', + 'class-blocks.php', + 'class-cli.php', + 'class-context.php', + 'class-missing-timber.php', + 'class-setup.php', + 'class-timber-integration.php', + 'class-twig.php', + ); + + foreach ( $files as $file ) { + require_once __DIR__ . '/' . $file; + } + } +} diff --git a/includes/class-cli.php b/includes/class-cli.php new file mode 100644 index 0000000..1497a56 --- /dev/null +++ b/includes/class-cli.php @@ -0,0 +1,165 @@ +`. + * + * @param array $args Positional arguments. + * @param array $assoc_args Named arguments. + * @return void + */ + public function __invoke( array $args, array $assoc_args ): void { + $this->generate( $args, $assoc_args ); + } + + /** + * Generates a new child theme from whisk. + * + * @param array $args Positional arguments. + * @param array $assoc_args Named arguments. + * @return void + */ + private function generate( array $args, array $assoc_args ): void { + $label = isset( $args[0] ) && is_string( $args[0] ) && '' !== trim( $args[0] ) ? sanitize_text_field( $args[0] ) : 'New Theme'; + $machine_name = self::convert_label_to_machine_name( $label ); + $parent = isset( $assoc_args['parent'] ) && is_string( $assoc_args['parent'] ) ? sanitize_key( $assoc_args['parent'] ) : 'emulsify'; + $source = get_theme_root() . "/{$parent}/whisk"; + $destination = get_theme_root() . "/{$machine_name}"; + + \WP_CLI::log( sprintf( 'Generating child theme "%s" from Emulsify.', $label ) ); + + if ( ! is_dir( $source ) ) { + \WP_CLI::error( sprintf( 'Source directory not found: %s', $source ) ); + } + + if ( ! $this->copy_theme( $source, $destination ) ) { + \WP_CLI::error( sprintf( 'Failed generating child theme at: %s', $destination ) ); + } + + $this->rename_instances( $destination, 'whisk', $machine_name ); + + \WP_CLI::success( sprintf( 'Child theme "%s" created at "%s".', $label, $destination ) ); + } + + /** + * Copies the starter theme into the child theme destination. + * + * @param string $source Source directory. + * @param string $destination Destination directory. + * @return bool TRUE on success. + */ + private function copy_theme( string $source, string $destination ): bool { + if ( ! wp_mkdir_p( $destination ) ) { + return false; + } + + if ( ! function_exists( 'copy_dir' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + WP_Filesystem(); + + $copied = copy_dir( $source, $destination ); + + return ! is_wp_error( $copied ); + } + + /** + * Renames starter file names and contents to the new theme slug. + * + * @param string $dir Directory to process recursively. + * @param string $old_slug Original slug. + * @param string $new_slug New slug. + * @return void + */ + private function rename_instances( string $dir, string $old_slug, string $new_slug ): void { + if ( ! is_dir( $dir ) ) { + return; + } + + global $wp_filesystem; + + if ( empty( $wp_filesystem ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + WP_Filesystem(); + } + + try { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $dir, \RecursiveDirectoryIterator::SKIP_DOTS ), + \RecursiveIteratorIterator::SELF_FIRST + ); + } catch ( \UnexpectedValueException $exception ) { + return; + } + + foreach ( $iterator as $item ) { + $path = $item->getPathname(); + + if ( false !== strpos( $path, $old_slug ) ) { + $new_path = str_replace( $old_slug, $new_slug, $path ); + + if ( ! $wp_filesystem->move( $path, $new_path ) ) { + \WP_CLI::warning( sprintf( 'Could not move %s to %s.', $path, $new_path ) ); + continue; + } + + $path = $new_path; + } + + if ( ! $item->isFile() ) { + continue; + } + + $contents = $wp_filesystem->get_contents( $path ); + + if ( ! is_string( $contents ) ) { + continue; + } + + $updated = str_replace( $old_slug, $new_slug, $contents ); + + if ( $updated !== $contents ) { + $wp_filesystem->put_contents( $path, $updated, FS_CHMOD_FILE ); + } + } + } + + /** + * Converts a human theme name into a filesystem-safe slug. + * + * @param string $label Theme label. + * @return string Theme slug. + */ + private static function convert_label_to_machine_name( string $label ): string { + $slug = preg_replace( '/[^a-z0-9]+/ui', '_', strtolower( $label ) ); + $slug = preg_replace( '/_{2,}/', '_', (string) $slug ); + $slug = preg_replace( '/^_+|_+$/', '', (string) $slug ); + + return '' !== $slug ? $slug : 'new_theme'; + } +} diff --git a/includes/class-context.php b/includes/class-context.php new file mode 100644 index 0000000..5e63a79 --- /dev/null +++ b/includes/class-context.php @@ -0,0 +1,134 @@ +theme(); + $context['menu'] = $this->menu(); + $context['post'] = $context['post'] ?? $this->post(); + $context['wp'] = $this->wordpress_helpers(); + $context['body_class'] = $context['body_class'] ?? implode( ' ', get_body_class() ); + + return $context; + } + + /** + * Gets normalized theme metadata for Twig. + * + * @return array Theme metadata. + */ + private function theme(): array { + $theme = wp_get_theme(); + $parent = $theme->parent(); + + return array( + 'name' => $theme->get( 'Name' ), + 'version' => $theme->get( 'Version' ), + 'text_domain' => $theme->get( 'TextDomain' ), + 'link' => get_stylesheet_directory_uri(), + 'path' => get_stylesheet_directory(), + 'template_directory_uri' => get_template_directory_uri(), + 'template_directory' => get_template_directory(), + 'stylesheet_directory' => get_stylesheet_directory(), + 'parent' => $parent ? $parent->get( 'Name' ) : null, + ); + } + + /** + * Gets the primary Timber menu when one exists. + * + * @return mixed Timber menu or null. + */ + private function menu() { + if ( ! class_exists( '\Timber\Timber' ) || ! method_exists( '\Timber\Timber', 'get_menu' ) ) { + return null; + } + + try { + if ( has_nav_menu( 'primary' ) ) { + return \Timber\Timber::get_menu( 'primary' ); + } + + return \Timber\Timber::get_menu(); + } catch ( \Throwable $throwable ) { + return null; + } + } + + /** + * Gets the current Timber post for singular requests. + * + * @return mixed Timber post or null. + */ + private function post() { + if ( ! is_singular() || ! class_exists( '\Timber\Timber' ) || ! method_exists( '\Timber\Timber', 'get_post' ) ) { + return null; + } + + try { + return \Timber\Timber::get_post(); + } catch ( \Throwable $throwable ) { + return null; + } + } + + /** + * Provides common WordPress values to Twig templates. + * + * @return array WordPress helper values. + */ + private function wordpress_helpers(): array { + return array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'body_class' => implode( ' ', get_body_class() ), + 'home_url' => home_url( '/' ), + 'is_404' => is_404(), + 'is_archive' => is_archive(), + 'is_front_page' => is_front_page(), + 'is_home' => is_home(), + 'is_page' => is_page(), + 'is_search' => is_search(), + 'is_single' => is_single(), + 'is_singular' => is_singular(), + 'is_user_logged_in' => is_user_logged_in(), + 'login_url' => wp_login_url(), + 'logout_url' => wp_logout_url(), + 'rest_url' => function_exists( 'rest_url' ) ? rest_url() : '', + 'search_query' => get_search_query(), + 'site_url' => site_url( '/' ), + 'stylesheet_directory_uri' => get_stylesheet_directory_uri(), + 'template_directory_uri' => get_template_directory_uri(), + 'theme_json' => file_exists( get_theme_file_path( 'theme.json' ) ), + 'user' => is_user_logged_in() ? wp_get_current_user() : null, + ); + } +} diff --git a/includes/class-emulsify-cli.php b/includes/class-emulsify-cli.php deleted file mode 100644 index f336cff..0000000 --- a/includes/class-emulsify-cli.php +++ /dev/null @@ -1,174 +0,0 @@ - - * - * @param array $args Positional arguments passed to the command. - * @param array $assoc_args Named (associative) arguments passed to the command. - */ - public function __invoke( $args, $assoc_args ) { - return $this->generate( $args, $assoc_args ); - } - - /** - * Generates a new Emulsify sub-theme. - * - * @param array $args Positional arguments passed to the command. - * @param array $assoc_args Named (associative) arguments passed to the command. - */ - public function generate( $args, $assoc_args ) { - // 1) Validate & sanitize the human label. - $label = ( isset( $args[0] ) && is_string( $args[0] ) && trim( $args[0] ) !== '' ) - ? sanitize_text_field( $args[0] ) - : 'New Theme'; - - // 2) Build a machine-friendly slug. - $machine_name = self::convert_label_to_machine_name( $label ); - - WP_CLI::log( sprintf( 'Cooking up a sub-theme: "%s" from Emulsify', $label, $machine_name ) ); - - // 3) Point at *only* the `whisk` folder inside the Emulsify theme. - $parent = ( isset( $assoc_args['parent'] ) && is_string( $assoc_args['parent'] ) ) - ? sanitize_key( $assoc_args['parent'] ) - : 'emulsify'; - // Adjust this path if your whisk theme lives somewhere else inside emulsify. - $sub_dir = 'whisk'; - $src = get_theme_root() . "/{$parent}/{$sub_dir}"; - - if ( ! is_dir( $src ) ) { - WP_CLI::error( sprintf( 'Source directory not found: %s', $src ) ); - } - - // 4) Copy it into a new folder named after your slug. - $dst = get_theme_root() . "/{$machine_name}"; - if ( ! $this->copy_theme( $src, $dst ) ) { - WP_CLI::error( sprintf( 'Failed generating sub-theme "%s"', $src, $dst ) ); - } - - // 5) Rename all occurrences of “whisk” inside file/folder names and file contents. - $old_slug = basename( $sub_dir ); // “whisk” - $this->rename_instances( $dst, $old_slug, $machine_name ); - - WP_CLI::success( sprintf( "Ding! Sub-theme '%s' created at '%s'.", $label, $dst ) ); - } - - /** - * Copies the parent theme into the sub-theme directory. - * - * @param string $src Source directory path. - * @param string $dst Destination directory path. - * @return bool True on success, false on failure. - */ - private function copy_theme( string $src, string $dst ): bool { - // Ensure destination exists. - if ( ! wp_mkdir_p( $dst ) ) { - return false; - } - - // Load WP Filesystem API if not already. - if ( ! function_exists( 'copy_dir' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - // Initialize the filesystem, so $wp_filesystem is not null. - WP_Filesystem(); - - global $wp_filesystem; - // Now safely call copy_dir(). - $copied = copy_dir( $src, $dst ); - if ( is_wp_error( $copied ) ) { - return false; - } - return true; - } - - /** - * Renames files, directories, and file contents from old slug to new slug. - * - * @param string $dir Directory to process recursively. - * @param string $old_slug Original theme slug. - * @param string $new_slug New theme slug. - */ - private function rename_instances( string $dir, string $old_slug, string $new_slug ): void { - // Bail if there’s nothing here. - if ( ! is_dir( $dir ) ) { - return; - } - - // Initialize fs if you haven’t already... - global $wp_filesystem; - if ( empty( $wp_filesystem ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - WP_Filesystem(); - } - - // Safely build iterator. - try { - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ), - RecursiveIteratorIterator::SELF_FIRST - ); - } catch ( \UnexpectedValueException $e ) { - // Directory unreadable or missing – just skip. - return; - } - - foreach ( $iterator as $item ) { - $path = $item->getPathname(); - - // Rename directories and files that contain the slug. - if ( false !== strpos( $path, $old_slug ) ) { - $new_path = str_replace( $old_slug, $new_slug, $path ); - if ( ! $wp_filesystem->move( $path, $new_path ) ) { - WP_CLI::warning( sprintf( 'Could not move %s to %s.', $path, $new_path ) ); - continue; - } - $path = $new_path; - } - - // If this is a file, replace slug inside its contents. - if ( $item->isFile() ) { - $contents = $wp_filesystem->get_contents( $path ); - $updated = str_replace( $old_slug, $new_slug, $contents ); - if ( $updated !== $contents ) { - $wp_filesystem->put_contents( $path, $updated, FS_CHMOD_FILE ); - } - } - } - } - - /** - * Converts a human-readable theme name into a machine-friendly slug. - * - * @param string $label The human-readable theme name. - * @return string The machine-friendly version. - */ - private static function convert_label_to_machine_name( string $label ): string { - // Lowercase and replace non-alphanumerics with underscores. - $slug = preg_replace( '/[^a-z0-9]+/ui', '_', strtolower( $label ) ); - // Collapse consecutive underscores. - $slug = preg_replace( '/_{2,}/', '_', $slug ); - // Trim leading or trailing underscores. - $slug = preg_replace( '/^_+|_+$/', '', $slug ); - return $slug; - } -} - -// Register the `emulsify` WP-CLI command. -WP_CLI::add_command( 'emulsify', 'Emulsify_CLI' ); diff --git a/includes/class-emulsify-theme.php b/includes/class-emulsify-theme.php deleted file mode 100644 index a712da1..0000000 --- a/includes/class-emulsify-theme.php +++ /dev/null @@ -1,218 +0,0 @@ - [ - // 'callable' => [ $this, 'my_filter_callback' ], - // ],. - ); - return array_merge( $filters, $additional ); - } - - /** - * Registers custom Twig functions. - * - * @param array $functions Existing Twig functions. - * @return array Modified functions. - */ - public function add_functions_to_twig( $functions ) { - $additional = array( - 'bem' => array( - 'callable' => array( $this, 'bem' ), - 'needs_context' => true, - 'is_safe' => array( 'html' ), - ), - 'add_attributes' => array( - 'callable' => array( $this, 'add_attributes' ), - 'needs_context' => true, - 'is_safe' => array( 'html' ), - ), - ); - return array_merge( $functions, $additional ); - } - - /** - * BEM helper for Timber/Twig. - * - * Usage in Twig: - *
    - * - * Or via an options array/object: - * {{ bem({ - * block: 'button', - * element: 'icon', - * modifiers:['small'], - * extra: ['js-clickable'] - * }) }} - * - * @param string|array|object $base_class Either the base class name (string) or an options array/object. - * @param array|string $modifiers Modifier suffixes. - * @param string $blockname Optional block name for elements (makes block__element). - * @param array|string $extra Any extra classes to append. - * - * @return string Safe HTML attribute string, e.g. class="block block--mod extra". - */ - public function bem( $base_class, $modifiers = array(), $blockname = '', $extra = array() ) { - // Allow passing an options array/object as single arg. - if ( is_object( $base_class ) || ( is_array( $base_class ) && isset( $base_class['block'] ) ) ) { - $opts = (array) $base_class; - $base_class = $opts['block'] ?? ''; - $blockname = $opts['element'] ?? ''; - $modifiers = $opts['modifiers'] ?? array(); - $extra = $opts['extra'] ?? array(); - } - - // 1) Normalize to flat arrays - $modifiers = is_array( $modifiers ) ? $modifiers : array( $modifiers ); - $extra = is_array( $extra ) ? $extra : array( $extra ); - - // 2) Filter out non‐scalar values so we never get an array element inside $classes - $modifiers = array_filter( - $modifiers, - function ( $m ) { - return is_scalar( $m ) && '' !== (string) $m; - } - ); - $extra = array_filter( - $extra, - function ( $e ) { - return is_scalar( $e ) && '' !== (string) $e; - } - ); - - // 3) Build the BEM base and modifier classes - $classes = array(); - $base = $blockname ? "{$blockname}__{$base_class}" : $base_class; - $classes[] = $base; - foreach ( $modifiers as $mod ) { - $classes[] = "{$base}--{$mod}"; - } - // 4) Any extra non‐BEM classes - foreach ( $extra as $e ) { - $classes[] = (string) $e; - } - - // 5) Return a single, escaped class="…" string - $class_string = implode( ' ', $classes ); - return sprintf( 'class="%s"', esc_attr( $class_string ) ); - } - - /** - * Build a string of HTML attributes from an associative array. - * - * Usage in Twig: - * - * - * @param array $context Timber context (unused here). - * @param array $additional_attributes ['attr' => 'value' or [ 'a','b' ], 'custom'=>'x=y' ]. - * - * @return string A string like: id="email" class="input input--large" data-foo="bar" - */ - public function add_attributes( $context, $additional_attributes = array() ) { - $attributes = array(); - - foreach ( $additional_attributes as $key => $value ) { - switch ( gettype( $value ) ) { - case 'array': - foreach ( $value as $index => $item ) { - // Handle bem() output. - if ( $item instanceof Attribute ) { - // Remove the item. - unset( $value[ $index ] ); - $value = array_merge( $value, $item->toArray()[ $key ] ); - } - } - break; - - default: - // Set value to an empty string. - $value = ''; - break; - } - // Merge additional attribute values with existing ones. - if ( $context['attributes']->offsetExists( $key ) ) { - $existing_attribute = $context['attributes']->offsetGet( $key )->value(); - $value = array_merge( $existing_attribute, $value ); - } - $context['attributes']->setAttribute( $key, $value ); - } - - // Set all attributes. - foreach ( $context['attributes'] as $key => $value ) { - $attributes->setAttribute( $key, $value ); - // Remove this attribute from context so it doesn't filter down to child - // elements. - $context['attributes']->removeAttribute( $key ); - } - - return $attributes; - } -} - -Timber::init(); - -// Lets get cooking. -new Emulsify_Theme(); diff --git a/includes/class-missing-timber.php b/includes/class-missing-timber.php new file mode 100644 index 0000000..100facd --- /dev/null +++ b/includes/class-missing-timber.php @@ -0,0 +1,94 @@ +

    %s

    ', + esc_html( $this->message() ) + ); + } + + /** + * Stops frontend rendering before Timber templates are loaded. + * + * @return void + */ + public function runtime_error(): void { + if ( is_admin() || $this->is_ajax_request() || $this->is_json_request() ) { + return; + } + + wp_die( + wp_kses_post( + sprintf( + '

    %s

    %s

    ', + $this->message(), + 'composer require timber/timber' + ) + ), + esc_html__( 'Timber is required', 'emulsify' ), + array( 'response' => 500 ) + ); + } + + /** + * Gets the dependency error message. + * + * @return string Error message. + */ + private function message(): string { + return __( 'Emulsify requires Timber to render WordPress templates. Install Timber with Composer or activate the Timber plugin.', 'emulsify' ); + } + + /** + * Checks whether the current request is AJAX. + * + * @return bool TRUE for AJAX requests. + */ + private function is_ajax_request(): bool { + if ( function_exists( 'wp_doing_ajax' ) ) { + return wp_doing_ajax(); + } + + return defined( 'DOING_AJAX' ) && DOING_AJAX; + } + + /** + * Checks whether the current request is JSON. + * + * @return bool TRUE for JSON requests. + */ + private function is_json_request(): bool { + return function_exists( 'wp_is_json_request' ) && wp_is_json_request(); + } +} diff --git a/includes/class-setup.php b/includes/class-setup.php new file mode 100644 index 0000000..99330f2 --- /dev/null +++ b/includes/class-setup.php @@ -0,0 +1,76 @@ + true, 'flex-width' => true ) ); + + add_image_size( 'emulsify-wide', 1600, 900, true ); + add_image_size( 'emulsify-card', 768, 432, true ); + + add_theme_support( + 'html5', + array( + 'caption', + 'comment-form', + 'comment-list', + 'gallery', + 'navigation-widgets', + 'script', + 'search-form', + 'style', + ) + ); + + add_theme_support( 'align-wide' ); + add_theme_support( 'responsive-embeds' ); + add_theme_support( 'wp-block-styles' ); + add_theme_support( 'editor-styles' ); + add_theme_support( 'appearance-tools' ); + add_editor_style( 'style.css' ); + } + + /** + * Registers navigation menu locations. + * + * @return void + */ + public function menus(): void { + register_nav_menus( + array( + 'primary' => __( 'Primary Navigation', 'emulsify' ), + 'footer' => __( 'Footer Navigation', 'emulsify' ), + ) + ); + } +} diff --git a/includes/class-timber-integration.php b/includes/class-timber-integration.php new file mode 100644 index 0000000..d54a683 --- /dev/null +++ b/includes/class-timber-integration.php @@ -0,0 +1,42 @@ +is_available() ) { + return false; + } + + try { + \Timber\Timber::init(); + } catch ( \Throwable $throwable ) { + return false; + } + + return true; + } + + /** + * Checks whether Timber is loaded by Composer or a plugin. + * + * @return bool TRUE when Timber can be used. + */ + private function is_available(): bool { + return class_exists( '\Timber\Timber' ); + } +} diff --git a/includes/class-twig.php b/includes/class-twig.php new file mode 100644 index 0000000..25f300d --- /dev/null +++ b/includes/class-twig.php @@ -0,0 +1,228 @@ +add_path( $loader, $path[0], $path[1] ); + } + + return $loader; + } + + /** + * Registers Twig functions. + * + * @param array $functions Existing Timber functions. + * @return array Updated Timber functions. + */ + public function functions( array $functions ): array { + $functions['bem'] = array( + 'callable' => array( $this, 'bem' ), + 'is_safe' => array( 'html' ), + ); + + $functions['add_attributes'] = array( + 'callable' => array( $this, 'add_attributes' ), + 'is_safe' => array( 'html' ), + ); + + return $functions; + } + + /** + * Builds a BEM class attribute. + * + * @param mixed $base_class Base class string or options array. + * @param array|string $modifiers Modifier suffixes. + * @param string $blockname Optional BEM block name. + * @param array|string $extra Extra classes. + * @return string HTML class attribute. + */ + public function bem( $base_class, $modifiers = array(), string $blockname = '', $extra = array() ): string { + if ( is_object( $base_class ) || is_array( $base_class ) ) { + $options = (array) $base_class; + + if ( isset( $options['block'], $options['element'] ) ) { + $blockname = (string) $options['block']; + $base_class = (string) $options['element']; + } else { + $base_class = $options['base_class'] ?? $options['block'] ?? ''; + $blockname = $options['blockname'] ?? $options['block_name'] ?? ''; + } + + $modifiers = $options['modifiers'] ?? array(); + $extra = $options['extra_classes'] ?? $options['extra'] ?? array(); + } + + $base_class = trim( (string) $base_class ); + $blockname = trim( $blockname ); + + if ( '' === $base_class ) { + return ''; + } + + $base = '' !== $blockname ? $blockname . '__' . $base_class : $base_class; + $classes = array( $base ); + $modifiers = $this->normalize_list( $modifiers ); + $extra = $this->normalize_list( $extra ); + + foreach ( $modifiers as $modifier ) { + $classes[] = $base . '--' . $modifier; + } + + $classes = array_merge( $classes, $extra ); + + return sprintf( 'class="%s"', esc_attr( implode( ' ', array_unique( $classes ) ) ) ); + } + + /** + * Builds HTML attributes from an associative array. + * + * @param mixed $attributes Attribute array or legacy context argument. + * @param mixed $additional_attributes Optional attribute array. + * @return string HTML attributes. + */ + public function add_attributes( $attributes = array(), $additional_attributes = null ): string { + if ( is_array( $additional_attributes ) ) { + $attributes = $additional_attributes; + } + + if ( ! is_array( $attributes ) ) { + return ''; + } + + $output = array(); + + foreach ( $attributes as $name => $value ) { + if ( null === $value || false === $value ) { + continue; + } + + if ( is_int( $name ) ) { + if ( is_scalar( $value ) && '' !== trim( (string) $value ) ) { + $output[] = trim( (string) $value ); + } + continue; + } + + $name = sanitize_key( (string) $name ); + + if ( '' === $name ) { + continue; + } + + if ( true === $value ) { + $output[] = esc_attr( $name ); + continue; + } + + if ( 'class' === $name ) { + $value = implode( ' ', $this->normalize_list( $this->normalize_class_attribute( $value ) ) ); + } elseif ( is_array( $value ) ) { + $value = implode( ' ', $this->normalize_list( $value ) ); + } + + if ( is_scalar( $value ) && '' !== trim( (string) $value ) ) { + $output[] = sprintf( '%s="%s"', esc_attr( $name ), esc_attr( (string) $value ) ); + } + } + + return implode( ' ', $output ); + } + + /** + * Adds a Twig path when it exists. + * + * @param mixed $loader Timber loader. + * @param string $path Filesystem path. + * @param string $namespace Twig namespace. + * @return void + */ + private function add_path( $loader, string $path, string $namespace ): void { + if ( is_dir( $path ) && is_readable( $path ) ) { + $loader->addPath( $path, $namespace ); + } + } + + /** + * Normalizes class-like values into a flat list. + * + * @param mixed $value Class value. + * @return array Class list. + */ + private function normalize_list( $value ): array { + $items = array(); + + if ( is_array( $value ) ) { + foreach ( $value as $item ) { + $items = array_merge( $items, $this->normalize_list( $item ) ); + } + return $items; + } + + if ( is_scalar( $value ) ) { + $value = trim( (string) $value ); + + if ( '' !== $value ) { + $items[] = $value; + } + } + + return $items; + } + + /** + * Extracts classes from a class attribute string. + * + * @param mixed $value Class attribute or class list. + * @return mixed Normalized class value. + */ + private function normalize_class_attribute( $value ) { + if ( is_string( $value ) && preg_match( '/class=["\']([^"\']+)["\']/', $value, $matches ) ) { + return $matches[1]; + } + + return $value; + } +} diff --git a/includes/global-assets.php b/includes/global-assets.php deleted file mode 100644 index 4904aac..0000000 --- a/includes/global-assets.php +++ /dev/null @@ -1,61 +0,0 @@ -isFile() && 'css' === $file->getExtension() ) { - $filename = $file->getBasename(); - $handle = $context . '-' . basename( $filename, '.css' ); - - $relative_path = ltrim( str_replace( $base_path, '', $file->getPathname() ), '/' ); - $relative_path = str_replace( '\\', '/', $relative_path ); // Normalize for Windows. - $file_uri = trailingslashit( $base_uri ) . $relative_path; - - wp_enqueue_style( - $handle, - $file_uri, - array(), - filemtime( $file->getPathname() ) - ); - } - } -} - -/** - * Enqueue all global styles on the frontend. - */ -function emulsify_enqueue_frontend_styles() { - emulsify_enqueue_global_styles( 'emulsify' ); -} -add_action( 'wp_enqueue_scripts', 'emulsify_enqueue_frontend_styles' ); - -/** - * Enqueue all global styles in the block editor. - */ -function emulsify_enqueue_block_editor_styles() { - emulsify_enqueue_global_styles( 'emulsify-editor' ); -} -add_action( 'enqueue_block_assets', 'emulsify_enqueue_block_editor_styles' ); diff --git a/includes/register-blocks.php b/includes/register-blocks.php deleted file mode 100644 index 21a3490..0000000 --- a/includes/register-blocks.php +++ /dev/null @@ -1,100 +0,0 @@ -isDir() && ! $item->isDot() ) { - $slug = $item->getFilename(); - $json_path = $item->getPathname() . '/' . $slug . '.component.json'; - $twig_path = 'dist/components/' . $slug . '/' . $slug . '.twig'; - - // Skip if either the JSON config or Twig template file is missing. - if ( ! file_exists( $json_path ) || ! file_exists( get_stylesheet_directory() . '/' . $twig_path ) ) { - continue; - } - - // Load and decode the component's JSON configuration file. - $config = json_decode( file_get_contents( $json_path ), true ); - - // Skip invalid or incomplete JSON configs. - if ( ! is_array( $config ) || empty( $config['name'] ) ) { - continue; - } - - // Define base/default arguments for the block registration. - $block_args = array_merge( - array( - 'name' => 'emulsify/' . strtolower( ucwords( str_replace( '-', ' ', $slug ) ) ), - // Prefix the block name for uniqueness within Gutenberg. - 'title' => strtolower( ucwords( str_replace( '-', ' ', $slug ) ) ), - 'mode' => 'preview', - // Timber-compatible render callback. - 'render_callback' => 'render_twig_block', - // Custom key for locating the Twig template. - 'twig_template' => $twig_path, - ), - $config - ); - - // Register the block with ACF. - acf_register_block_type( $block_args ); - } - } -} -add_action( 'acf/init', 'register_emulsify_blocks' ); - -/** - * Timber render callback for ACF Gutenberg blocks. - * - * Passes block data and ACF fields into the specified Twig template for rendering. - * - * @param array $block The block settings and attributes. - * @param string $content The block's inner content (empty for dynamic blocks). - * @param bool $is_preview True during editor preview render. - * @param int $post_id The ID of the current post being edited or viewed. - * - * @return void - */ -function render_twig_block( $block, $content = '', $is_preview = false, $post_id = 0 ) { - // Get standard Timber context. - $context = Timber::context(); - $context['block'] = $block; - $context['fields'] = get_fields(); - - // Add preview mode flag to context. - if ( $is_preview ) { - $context['is_preview'] = true; - } - - // Try to get the template path from either block data or custom property. - if ( ! empty( $block['data']['twig_template'] ) ) { - Timber::render( $block['data']['twig_template'], $context ); - } elseif ( ! empty( $block['twig_template'] ) ) { - Timber::render( $block['twig_template'], $context ); - } else { - // Fallback message if no template is defined. - echo '

    Block rendering error: No template defined.

    '; - } -} diff --git a/includes/twig-namespaces.php b/includes/twig-namespaces.php deleted file mode 100644 index 27c93b3..0000000 --- a/includes/twig-namespaces.php +++ /dev/null @@ -1,15 +0,0 @@ -addPath( __DIR__ . '/../templates', 'emulsify-tpl' ); - $loader->addPath( __DIR__ . '/../components', 'components' ); - return $loader; - } -); From 308cb9527f06326f42a2c11d8b9ba4b1e077e668 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 10:01:48 -0500 Subject: [PATCH 10/55] fix: implement timber attribute helpers --- .github/scripts/attribute-helper-smoke.php | 153 ++++++++ .github/scripts/release-check.cjs | 20 ++ includes/class-attribute-bag.php | 388 +++++++++++++++++++++ includes/class-bootstrap.php | 1 + includes/class-twig.php | 242 ++++++++----- 5 files changed, 711 insertions(+), 93 deletions(-) create mode 100644 .github/scripts/attribute-helper-smoke.php create mode 100644 includes/class-attribute-bag.php diff --git a/.github/scripts/attribute-helper-smoke.php b/.github/scripts/attribute-helper-smoke.php new file mode 100644 index 0000000..c1398d1 --- /dev/null +++ b/.github/scripts/attribute-helper-smoke.php @@ -0,0 +1,153 @@ +functions( array() ) as $name => $definition ) { + $options = array( + 'is_safe' => $definition['is_safe'] ?? array(), + 'needs_context' => $definition['needs_context'] ?? false, + ); + + $environment->addFunction( new \Twig\TwigFunction( $name, $definition['callable'], $options ) ); + } +} + +$helpers = new Twig(); + +emulsify_attribute_helper_assert_same( + 'bem positional syntax', + 'class="button button--primary"', + (string) $helpers->bem( 'button', array( 'primary' ) ) +); + +emulsify_attribute_helper_assert_same( + 'bem object syntax', + 'data-state="open" disabled class="card__title card__title--featured u-mb-0"', + (string) $helpers->bem( + array( + 'block' => 'card', + 'element' => 'title', + 'modifiers' => array( 'featured' ), + 'extra' => array( 'u-mb-0' ), + 'attributes' => array( + 'data-state' => 'open', + 'disabled' => true, + 'onclick bad' => 'ignored', + ), + ) + ) +); + +emulsify_attribute_helper_assert_same( + 'add_attributes array syntax', + 'class="foo" data-label="A&B" hidden', + (string) $helpers->add_attributes( + array( + 'class' => array( 'foo' ), + 'data-label' => 'A&B', + 'hidden' => true, + ) + ) +); + +$bag = new AttributeBag( + array( + 'class' => array( 'foo', 'foo' ), + 'id' => 'example', + ) +); +$bag->merge( + array( + 'class' => array( 'bar' ), + 'id' => 'updated', + ) +); + +emulsify_attribute_helper_assert_same( + 'AttributeBag merge syntax', + 'class="foo bar" id="updated"', + (string) $helpers->add_attributes( $bag ) +); + +emulsify_attribute_helper_assert_same( + 'context-aware add_attributes syntax', + 'class="context foo"', + (string) $helpers->add_attributes( + array( 'attributes' => new AttributeBag( array( 'class' => array( 'context' ) ) ) ), + array( 'class' => array( 'foo' ) ) + ) +); + +emulsify_attribute_helper_assert_same( + 'context-aware bem syntax', + 'class="button button--primary context"', + (string) $helpers->bem( + array( 'attributes' => new AttributeBag( array( 'class' => array( 'context' ) ) ) ), + 'button', + array( 'primary' ) + ) +); + +$autoload = __DIR__ . '/../../vendor/autoload.php'; + +if ( is_readable( $autoload ) ) { + require_once $autoload; +} + +if ( class_exists( \Twig\Environment::class ) && class_exists( \Twig\Loader\ArrayLoader::class ) && class_exists( \Twig\TwigFunction::class ) ) { + $environment = new \Twig\Environment( + new \Twig\Loader\ArrayLoader( + array( + 'fixture' => '{{ bem("button", ["primary"]) }}|{{ add_attributes({ class: ["foo"] }) }}', + ) + ), + array( 'autoescape' => false ) + ); + + emulsify_register_smoke_twig_functions( $environment, $helpers ); + + emulsify_attribute_helper_assert_same( + 'Twig fixture render', + 'class="button button--primary"|class="foo"', + $environment->render( 'fixture' ) + ); + + echo "Attribute helper smoke checks passed with Twig fixture rendering.\n"; + exit( 0 ); +} + +echo "Attribute helper smoke checks passed. Twig fixture rendering skipped because Twig is not installed.\n"; diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 967db33..c8faa5a 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -199,9 +199,12 @@ function runStaticChecks() { 'README.md', 'composer.json', 'functions.php', + 'includes/class-attribute-bag.php', + 'includes/class-twig.php', 'package.json', 'release.config.js', 'style.css', + '.github/scripts/attribute-helper-smoke.php', 'whisk/functions.php', 'whisk/package.json', 'whisk/project.emulsify.json', @@ -252,6 +255,23 @@ function runStaticChecks() { return 'Parent and Whisk WordPress theme headers are coherent with package metadata.'; }); + runStaticCheck('Timber attribute helpers', () => { + const bootstrap = readFile('includes/class-bootstrap.php'); + const twig = readFile('includes/class-twig.php'); + const attributeBag = readFile('includes/class-attribute-bag.php'); + const smoke = readFile('.github/scripts/attribute-helper-smoke.php'); + + ensure(bootstrap.includes('class-attribute-bag.php'), 'Bootstrap should load AttributeBag before Twig helpers.'); + ensure(attributeBag.includes('implements \\Stringable'), 'AttributeBag should serialize safely in Twig string contexts.'); + ensure(attributeBag.includes('function addClass'), 'AttributeBag should support Core-style class merging.'); + ensure(attributeBag.includes('function toString'), 'AttributeBag should expose explicit serialization.'); + ensure(twig.includes("'needs_context' => true"), 'Timber helper functions should accept Twig context.'); + ensure(twig.includes('new AttributeBag'), 'Twig helpers should return AttributeBag objects.'); + ensure(smoke.includes('{{ bem("button", ["primary"]) }}'), 'Attribute helper smoke script should render a bem() Twig fixture.'); + ensure(smoke.includes('{{ add_attributes({ class: ["foo"] }) }}'), 'Attribute helper smoke script should render an add_attributes() Twig fixture.'); + return 'Attribute helper runtime and smoke fixture are wired.'; + }); + runStaticCheck('Whisk Core 4 and Vite metadata', () => { const scripts = whiskPackage.scripts || {}; const scriptText = Object.values(scripts).join('\n'); diff --git a/includes/class-attribute-bag.php b/includes/class-attribute-bag.php new file mode 100644 index 0000000..cd5f7b9 --- /dev/null +++ b/includes/class-attribute-bag.php @@ -0,0 +1,388 @@ + + */ + private $attributes = array(); + + /** + * Constructor. + * + * @param mixed $attributes Initial attributes. + */ + public function __construct( $attributes = array() ) { + $this->merge( $attributes ); + } + + /** + * Creates a cloned attribute bag. + * + * @return self Cloned attribute bag. + */ + public function copy(): self { + return new self( $this->toArray() ); + } + + /** + * Appends class tokens. + * + * @param mixed $value Class value. + * @return self Current instance. + */ + public function addClass( $value ): self { + $tokens = self::classTokensFromValue( $value ); + + if ( empty( $tokens ) ) { + return $this; + } + + $existing = $this->attributes['class'] ?? array(); + $this->attributes['class'] = self::uniqueList( array_merge( (array) $existing, $tokens ) ); + + return $this; + } + + /** + * Snake-case alias for PHP callers. + * + * @param mixed $value Class value. + * @return self Current instance. + */ + public function add_class( $value ): self { + return $this->addClass( $value ); + } + + /** + * Gets the normalized class list. + * + * @return array Class tokens. + */ + public function getClassList(): array { + return $this->attributes['class'] ?? array(); + } + + /** + * Sets or merges one attribute. + * + * @param string $name Attribute name. + * @param mixed $value Attribute value. + * @return self Current instance. + */ + public function set( string $name, $value ): self { + $attribute_name = trim( $name ); + + if ( ! self::isSafeAttributeName( $attribute_name ) ) { + return $this; + } + + if ( 'class' === $attribute_name ) { + $class_string = is_string( $value ) ? self::parseClassAttributeString( $value ) : null; + $this->addClass( null !== $class_string ? $class_string : $value ); + return $this; + } + + $normalized_value = self::valueToAttributeParts( $value ); + + if ( null === $normalized_value ) { + return $this; + } + + $this->attributes[ $attribute_name ] = $normalized_value; + + return $this; + } + + /** + * Merges an array, traversable value, or another AttributeBag. + * + * @param mixed $value Attribute source. + * @return self Current instance. + */ + public function merge( $value ): self { + if ( empty( $value ) ) { + return $this; + } + + if ( $value instanceof self ) { + $value = $value->toArray(); + } + + if ( $value instanceof \Traversable ) { + $value = iterator_to_array( $value ); + } + + if ( is_object( $value ) ) { + $value = get_object_vars( $value ); + } + + if ( ! is_array( $value ) ) { + return $this; + } + + foreach ( $value as $name => $attribute_value ) { + if ( '_keys' === $name || ! is_string( $name ) ) { + continue; + } + + $this->set( $name, $attribute_value ); + } + + return $this; + } + + /** + * Converts attributes to a plain array. + * + * @return array Attribute map. + */ + public function toArray(): array { + return $this->attributes; + } + + /** + * Core-compatible alias. + * + * @return array Attribute map. + */ + public function toObject(): array { + return $this->toArray(); + } + + /** + * Serializes attributes for Twig output. + * + * @return string Safe serialized attributes. + */ + public function toString(): string { + $output = array(); + + foreach ( $this->attributes as $name => $value ) { + if ( 'class' === $name && is_array( $value ) ) { + if ( empty( $value ) ) { + continue; + } + + $output[] = sprintf( 'class="%s"', self::escapeAttributeValue( implode( ' ', $value ) ) ); + continue; + } + + if ( true === $value ) { + $output[] = $name; + continue; + } + + if ( is_array( $value ) ) { + $value = implode( ' ', self::flattenList( $value ) ); + } + + if ( is_scalar( $value ) ) { + $output[] = sprintf( '%s="%s"', $name, self::escapeAttributeValue( (string) $value ) ); + } + } + + return implode( ' ', $output ); + } + + /** + * Serializes attributes for string contexts. + * + * @return string Safe serialized attributes. + */ + public function __toString(): string { + return $this->toString(); + } + + /** + * Converts scalar, array, or AttributeBag values into class tokens. + * + * @param mixed $value Class value. + * @return array Class tokens. + */ + public static function classTokensFromValue( $value ): array { + if ( $value instanceof self ) { + return $value->getClassList(); + } + + $tokens = array(); + + foreach ( self::flattenList( $value ) as $item ) { + if ( $item instanceof self ) { + $tokens = array_merge( $tokens, $item->getClassList() ); + continue; + } + + foreach ( preg_split( '/\s+/', (string) $item ) as $candidate ) { + $token = self::cleanClassToken( $candidate ); + + if ( '' !== $token ) { + $tokens[] = $token; + } + } + } + + return self::uniqueList( $tokens ); + } + + /** + * Converts a value into serializable non-class attribute parts. + * + * @param mixed $value Attribute value. + * @return mixed Normalized value or null when it should not render. + */ + private static function valueToAttributeParts( $value ) { + if ( $value instanceof self ) { + return $value->toArray(); + } + + if ( null === $value || false === $value ) { + return null; + } + + if ( is_array( $value ) ) { + return array_map( + static function ( $item ): string { + return (string) $item; + }, + self::flattenList( $value ) + ); + } + + if ( true === $value ) { + return true; + } + + if ( is_scalar( $value ) ) { + return (string) $value; + } + + if ( is_object( $value ) && method_exists( $value, '__toString' ) ) { + return (string) $value; + } + + return null; + } + + /** + * Flattens scalar and nested array values. + * + * @param mixed $value Value to flatten. + * @return array Flattened list. + */ + private static function flattenList( $value ): array { + if ( null === $value || false === $value ) { + return array(); + } + + if ( ! is_array( $value ) ) { + return array( $value ); + } + + $items = array(); + + foreach ( $value as $item ) { + $items = array_merge( $items, self::flattenList( $item ) ); + } + + return $items; + } + + /** + * Removes duplicate values while preserving first-seen order. + * + * @param array $values Values to deduplicate. + * @return array Unique values. + */ + private static function uniqueList( array $values ): array { + $unique = array(); + $seen = array(); + + foreach ( $values as $value ) { + $key = (string) $value; + + if ( isset( $seen[ $key ] ) ) { + continue; + } + + $seen[ $key ] = true; + $unique[] = $value; + } + + return $unique; + } + + /** + * Cleans one value into a CSS class token. + * + * @param mixed $value Candidate class token. + * @return string Clean class token. + */ + private static function cleanClassToken( $value ): string { + $raw = trim( (string) $value ); + + if ( '' === $raw ) { + return ''; + } + + $cleaned = preg_replace( '/[^_a-zA-Z0-9-]+/', '-', $raw ); + $cleaned = trim( (string) $cleaned, '-' ); + $cleaned = preg_replace( '/^([0-9])/', '_$1', $cleaned ); + + return (string) $cleaned; + } + + /** + * Extracts a legacy class="..." string. + * + * @param string $value Candidate class attribute. + * @return string|null Class value when the string is class-only. + */ + private static function parseClassAttributeString( string $value ): ?string { + if ( preg_match( '/^class=(["\'])(.*?)\1$/', $value, $matches ) ) { + return $matches[2]; + } + + return null; + } + + /** + * Checks whether an attribute name can be serialized safely. + * + * @param string $name Attribute name. + * @return bool TRUE when safe. + */ + private static function isSafeAttributeName( string $name ): bool { + return 1 === preg_match( '/^[A-Za-z_:][A-Za-z0-9:_.-]*$/', $name ); + } + + /** + * Escapes an attribute value for double-quoted HTML output. + * + * @param string $value Attribute value. + * @return string Escaped value. + */ + private static function escapeAttributeValue( string $value ): string { + return strtr( + $value, + array( + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + ) + ); + } +} diff --git a/includes/class-bootstrap.php b/includes/class-bootstrap.php index 764f833..2808ef3 100644 --- a/includes/class-bootstrap.php +++ b/includes/class-bootstrap.php @@ -84,6 +84,7 @@ private function load_vendor_autoload(): void { */ private function load_classes(): void { $files = array( + 'class-attribute-bag.php', 'class-assets.php', 'class-blocks.php', 'class-cli.php', diff --git a/includes/class-twig.php b/includes/class-twig.php index 25f300d..f4bdaed 100644 --- a/includes/class-twig.php +++ b/includes/class-twig.php @@ -58,13 +58,15 @@ public function loader_paths( $loader ) { */ public function functions( array $functions ): array { $functions['bem'] = array( - 'callable' => array( $this, 'bem' ), - 'is_safe' => array( 'html' ), + 'callable' => array( $this, 'bem' ), + 'needs_context' => true, + 'is_safe' => array( 'html' ), ); $functions['add_attributes'] = array( - 'callable' => array( $this, 'add_attributes' ), - 'is_safe' => array( 'html' ), + 'callable' => array( $this, 'add_attributes' ), + 'needs_context' => true, + 'is_safe' => array( 'html' ), ); return $functions; @@ -73,116 +75,184 @@ public function functions( array $functions ): array { /** * Builds a BEM class attribute. * - * @param mixed $base_class Base class string or options array. - * @param array|string $modifiers Modifier suffixes. - * @param string $blockname Optional BEM block name. - * @param array|string $extra Extra classes. - * @return string HTML class attribute. + * @param mixed ...$arguments Core-style BEM arguments. + * @return AttributeBag HTML attributes. */ - public function bem( $base_class, $modifiers = array(), string $blockname = '', $extra = array() ): string { - if ( is_object( $base_class ) || is_array( $base_class ) ) { - $options = (array) $base_class; - - if ( isset( $options['block'], $options['element'] ) ) { - $blockname = (string) $options['block']; - $base_class = (string) $options['element']; - } else { - $base_class = $options['base_class'] ?? $options['block'] ?? ''; - $blockname = $options['blockname'] ?? $options['block_name'] ?? ''; - } + public function bem( ...$arguments ): AttributeBag { + $context = $this->shift_twig_context( $arguments ); + $options = $this->normalize_bem_options( + $arguments[0] ?? '', + $arguments[1] ?? array(), + $arguments[2] ?? '', + $arguments[3] ?? array(), + $arguments[4] ?? array() + ); + $base_class = trim( (string) $options['base_class'] ); + $blockname = trim( (string) $options['blockname'] ); + $classes = array(); + + if ( '' !== $base_class ) { + $class_prefix = '' !== $blockname ? $blockname . '__' . $base_class : $base_class; + $classes[] = $class_prefix; - $modifiers = $options['modifiers'] ?? array(); - $extra = $options['extra_classes'] ?? $options['extra'] ?? array(); + foreach ( $this->normalize_list( $options['modifiers'] ) as $modifier ) { + $classes[] = $class_prefix . '--' . $modifier; + } } - $base_class = trim( (string) $base_class ); - $blockname = trim( $blockname ); + $classes = array_merge( $classes, $this->normalize_list( $options['extra'] ) ); - if ( '' === $base_class ) { - return ''; - } + $attribute_bag = new AttributeBag( $options['attributes'] ); + $attribute_bag->addClass( $classes ); + $attribute_bag->merge( $this->attributes_from_context( $context ) ); - $base = '' !== $blockname ? $blockname . '__' . $base_class : $base_class; - $classes = array( $base ); - $modifiers = $this->normalize_list( $modifiers ); - $extra = $this->normalize_list( $extra ); + return $attribute_bag; + } - foreach ( $modifiers as $modifier ) { - $classes[] = $base . '--' . $modifier; + /** + * Builds HTML attributes from an associative array. + * + * @param mixed ...$arguments Attribute arguments. + * @return AttributeBag HTML attributes. + */ + public function add_attributes( ...$arguments ): AttributeBag { + $context = $this->shift_twig_context( $arguments ); + $attribute_bag = $this->attributes_from_context( $context ); + $additional = $arguments[0] ?? array(); + $legacy_context = array(); + + if ( $this->is_twig_context( $additional ) && isset( $arguments[1] ) ) { + $legacy_context = $additional; + $additional = $arguments[1]; + } + + if ( ! empty( $legacy_context ) ) { + $attribute_bag->merge( $this->attributes_from_context( $legacy_context ) ); } - $classes = array_merge( $classes, $extra ); + $attribute_bag->merge( $additional ); - return sprintf( 'class="%s"', esc_attr( implode( ' ', array_unique( $classes ) ) ) ); + return $attribute_bag; } /** - * Builds HTML attributes from an associative array. + * Adds a Twig path when it exists. * - * @param mixed $attributes Attribute array or legacy context argument. - * @param mixed $additional_attributes Optional attribute array. - * @return string HTML attributes. + * @param mixed $loader Timber loader. + * @param string $path Filesystem path. + * @param string $namespace Twig namespace. + * @return void */ - public function add_attributes( $attributes = array(), $additional_attributes = null ): string { - if ( is_array( $additional_attributes ) ) { - $attributes = $additional_attributes; + private function add_path( $loader, string $path, string $namespace ): void { + if ( is_dir( $path ) && is_readable( $path ) ) { + $loader->addPath( $path, $namespace ); } + } - if ( ! is_array( $attributes ) ) { - return ''; + /** + * Normalizes positional and object-style BEM arguments. + * + * @param mixed $base_class Base class or options object. + * @param mixed $modifiers Modifier values. + * @param mixed $blockname Block name. + * @param mixed $extra Extra classes. + * @param mixed $attributes Extra attributes. + * @return array Normalized BEM options. + */ + private function normalize_bem_options( $base_class, $modifiers, $blockname, $extra, $attributes ): array { + if ( ! is_array( $base_class ) && ! is_object( $base_class ) ) { + return array( + 'base_class' => $base_class, + 'modifiers' => $modifiers, + 'blockname' => $blockname, + 'extra' => $extra, + 'attributes' => $attributes, + ); } - $output = array(); + $options = is_array( $base_class ) ? $base_class : get_object_vars( $base_class ); + $has_bem_object_shape = $this->has_non_empty_option( $options, 'block' ) && $this->has_non_empty_option( $options, 'element' ); - foreach ( $attributes as $name => $value ) { - if ( null === $value || false === $value ) { - continue; - } + return array( + 'base_class' => $this->first_option( $options, array( 'baseClass', 'base_class', 'base' ), $has_bem_object_shape ? $options['element'] : ( $options['block'] ?? '' ) ), + 'modifiers' => $options['modifiers'] ?? array(), + 'blockname' => $this->first_option( $options, array( 'blockname', 'blockName' ), $has_bem_object_shape ? $options['block'] : ( $options['element'] ?? '' ) ), + 'extra' => $options['extra'] ?? array(), + 'attributes' => $options['attributes'] ?? array(), + ); + } - if ( is_int( $name ) ) { - if ( is_scalar( $value ) && '' !== trim( (string) $value ) ) { - $output[] = trim( (string) $value ); - } - continue; - } + /** + * Removes a Timber context argument from a Twig callback argument list. + * + * @param array $arguments Function arguments. + * @return array Timber context. + */ + private function shift_twig_context( array &$arguments ): array { + if ( count( $arguments ) > 1 && is_array( $arguments[0] ) && ( empty( $arguments[0] ) || $this->is_twig_context( $arguments[0] ) ) ) { + return array_shift( $arguments ); + } - $name = sanitize_key( (string) $name ); + return array(); + } - if ( '' === $name ) { - continue; - } + /** + * Checks whether a value looks like a Timber context array. + * + * @param mixed $value Value to inspect. + * @return bool TRUE when the value looks like Timber context. + */ + private function is_twig_context( $value ): bool { + if ( ! is_array( $value ) ) { + return false; + } - if ( true === $value ) { - $output[] = esc_attr( $name ); - continue; + foreach ( array( 'attributes', 'site', 'theme', 'post', 'wp' ) as $key ) { + if ( array_key_exists( $key, $value ) ) { + return true; } + } - if ( 'class' === $name ) { - $value = implode( ' ', $this->normalize_list( $this->normalize_class_attribute( $value ) ) ); - } elseif ( is_array( $value ) ) { - $value = implode( ' ', $this->normalize_list( $value ) ); - } + return false; + } - if ( is_scalar( $value ) && '' !== trim( (string) $value ) ) { - $output[] = sprintf( '%s="%s"', esc_attr( $name ), esc_attr( (string) $value ) ); + /** + * Builds an AttributeBag from Timber context attributes. + * + * @param array $context Timber context. + * @return AttributeBag Context attributes. + */ + private function attributes_from_context( array $context ): AttributeBag { + return new AttributeBag( $context['attributes'] ?? array() ); + } + + /** + * Gets the first non-empty option value. + * + * @param array $options Option map. + * @param array $keys Candidate keys. + * @param mixed $default Default value. + * @return mixed Option value. + */ + private function first_option( array $options, array $keys, $default = null ) { + foreach ( $keys as $key ) { + if ( $this->has_non_empty_option( $options, $key ) ) { + return $options[ $key ]; } } - return implode( ' ', $output ); + return $default; } /** - * Adds a Twig path when it exists. + * Checks whether an option key contains a non-empty value. * - * @param mixed $loader Timber loader. - * @param string $path Filesystem path. - * @param string $namespace Twig namespace. - * @return void + * @param array $options Option map. + * @param string $key Option key. + * @return bool TRUE when set and non-empty. */ - private function add_path( $loader, string $path, string $namespace ): void { - if ( is_dir( $path ) && is_readable( $path ) ) { - $loader->addPath( $path, $namespace ); - } + private function has_non_empty_option( array $options, string $key ): bool { + return array_key_exists( $key, $options ) && null !== $options[ $key ] && '' !== $options[ $key ] && false !== $options[ $key ]; } /** @@ -211,18 +281,4 @@ private function normalize_list( $value ): array { return $items; } - - /** - * Extracts classes from a class attribute string. - * - * @param mixed $value Class attribute or class list. - * @return mixed Normalized class value. - */ - private function normalize_class_attribute( $value ) { - if ( is_string( $value ) && preg_match( '/class=["\']([^"\']+)["\']/', $value, $matches ) ) { - return $matches[1]; - } - - return $value; - } } From 7decd36efa359d98a8fcf9de5a7e6095ae8d77f1 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Sat, 27 Jun 2026 10:18:39 -0500 Subject: [PATCH 11/55] feat: refine timber template defaults --- 404.php | 2 +- archive.php | 4 +- author.php | 4 +- includes/class-twig.php | 2 +- index.php | 10 +-- page.php | 2 +- search.php | 2 +- single.php | 13 +++- templates/404.twig | 7 +- templates/archive.twig | 32 +++++--- templates/author.twig | 18 ++++- templates/index.twig | 33 ++++++--- templates/layouts/base.twig | 61 ++++++++------- templates/page.twig | 24 ++++-- templates/partials/comment-form.twig | 69 +++++++++-------- templates/partials/comment.twig | 33 +++++---- templates/partials/footer.twig | 6 +- templates/partials/head.twig | 5 +- templates/partials/menu.twig | 30 ++++++-- templates/partials/pagination.twig | 54 +++++--------- templates/partials/tease-post.twig | 24 +++--- templates/partials/tease.twig | 35 ++++++--- templates/search.twig | 25 ++++--- templates/single-password.twig | 24 +++--- templates/single.twig | 86 ++++++++++++++-------- whisk/templates/404.twig | 7 +- whisk/templates/archive.twig | 32 +++++--- whisk/templates/author.twig | 18 ++++- whisk/templates/index.twig | 33 ++++++--- whisk/templates/layouts/base.twig | 63 ++++++++-------- whisk/templates/page.twig | 24 ++++-- whisk/templates/partials/comment-form.twig | 69 +++++++++-------- whisk/templates/partials/comment.twig | 33 +++++---- whisk/templates/partials/footer.twig | 6 +- whisk/templates/partials/head.twig | 5 +- whisk/templates/partials/menu.twig | 30 ++++++-- whisk/templates/partials/pagination.twig | 54 +++++--------- whisk/templates/partials/tease-post.twig | 24 +++--- whisk/templates/partials/tease.twig | 35 ++++++--- whisk/templates/search.twig | 25 ++++--- whisk/templates/single-password.twig | 24 +++--- whisk/templates/single.twig | 86 ++++++++++++++-------- 42 files changed, 685 insertions(+), 488 deletions(-) diff --git a/404.php b/404.php index 91d13bd..e601483 100644 --- a/404.php +++ b/404.php @@ -10,4 +10,4 @@ use Timber\Timber; $context = Timber::context(); -Timber::render( 'templates/404.twig', $context ); +Timber::render( '@templates/404.twig', $context ); diff --git a/archive.php b/archive.php index 876cc58..12fb16a 100644 --- a/archive.php +++ b/archive.php @@ -9,7 +9,7 @@ use Timber\Timber; -$templates = array( 'templates/archive.twig', 'templates/index.twig' ); +$templates = array( '@templates/archive.twig', '@templates/index.twig' ); $page_title = 'Archive'; if ( is_day() ) { @@ -24,7 +24,7 @@ $page_title = single_cat_title( '', false ); } elseif ( is_post_type_archive() ) { $page_title = post_type_archive_title( '', false ); - array_unshift( $templates, 'templates/archive-' . get_post_type() . '.twig' ); + array_unshift( $templates, '@templates/archive-' . get_post_type() . '.twig' ); } $context = Timber::context( diff --git a/author.php b/author.php index a829c20..81bca45 100644 --- a/author.php +++ b/author.php @@ -13,10 +13,10 @@ if ( isset( $context['author'] ) ) { /* translators: %s is the author’s display name. */ - $context['title'] = sprintf( __( 'Archive of %s', 'timber-starter' ), $context['author']->name() ); + $context['title'] = sprintf( __( 'Archive of %s', 'emulsify' ), $context['author']->name() ); } Timber::render( - array( 'templates/author.twig', 'templates/archive.twig' ), + array( '@templates/author.twig', '@templates/archive.twig' ), $context ); diff --git a/includes/class-twig.php b/includes/class-twig.php index f4bdaed..702ceee 100644 --- a/includes/class-twig.php +++ b/includes/class-twig.php @@ -189,7 +189,7 @@ private function normalize_bem_options( $base_class, $modifiers, $blockname, $ex * @return array Timber context. */ private function shift_twig_context( array &$arguments ): array { - if ( count( $arguments ) > 1 && is_array( $arguments[0] ) && ( empty( $arguments[0] ) || $this->is_twig_context( $arguments[0] ) ) ) { + if ( count( $arguments ) > 1 && is_array( $arguments[0] ) ) { return array_shift( $arguments ); } diff --git a/index.php b/index.php index 97f94fd..394c0fe 100644 --- a/index.php +++ b/index.php @@ -14,16 +14,12 @@ use Timber\Timber; -$templates = array( 'templates/index.twig' ); +$templates = array( '@templates/index.twig' ); if ( is_home() ) { - array_unshift( $templates, 'templates/front-page.twig', 'templates/home.twig' ); + array_unshift( $templates, '@templates/front-page.twig', '@templates/home.twig' ); } -$context = Timber::context( - array( - 'foo' => 'bar', - ) -); +$context = Timber::context(); Timber::render( $templates, $context ); diff --git a/page.php b/page.php index 3f57f3f..31e708a 100644 --- a/page.php +++ b/page.php @@ -11,4 +11,4 @@ $context = Timber::context(); -Timber::render( 'templates/page.twig', $context ); +Timber::render( '@templates/page.twig', $context ); diff --git a/search.php b/search.php index 43c9487..b646336 100644 --- a/search.php +++ b/search.php @@ -7,7 +7,7 @@ use Timber\Timber; -$templates = array( 'templates/search.twig', 'templates/archive.twig', 'templates/index.twig' ); +$templates = array( '@templates/search.twig', '@templates/archive.twig', '@templates/index.twig' ); $context = Timber::context( array( diff --git a/single.php b/single.php index 885d7c2..3fbaf73 100644 --- a/single.php +++ b/single.php @@ -10,11 +10,16 @@ use Timber\Timber; $context = Timber::context(); -$wp_post = $context['post']; -$templates = array( 'templates/single-' . $wp_post->post_type . '.twig', 'templates/single.twig' ); +$wp_post = $context['post'] ?? null; +$post_type = $wp_post->post_type ?? get_post_type(); +$templates = array( '@templates/single.twig' ); -if ( post_password_required( $wp_post->ID ) ) { - $templates = 'templates/single-password.twig'; +if ( $post_type ) { + array_unshift( $templates, '@templates/single-' . $post_type . '.twig' ); +} + +if ( $wp_post && post_password_required( $wp_post->ID ) ) { + $templates = '@templates/single-password.twig'; } Timber::render( $templates, $context ); diff --git a/templates/404.twig b/templates/404.twig index fa4e23c..53aa070 100644 --- a/templates/404.twig +++ b/templates/404.twig @@ -1,5 +1,8 @@ -{% extends 'templates/layouts/base.twig' %} +{% extends '@templates/layouts/base.twig' %} {% block content %} - Sorry, we couldn't find what you're looking for. +
    +

    Page not found

    +

    The requested page could not be found.

    +
    {% endblock %} diff --git a/templates/archive.twig b/templates/archive.twig index 6f3c0f3..3198a42 100644 --- a/templates/archive.twig +++ b/templates/archive.twig @@ -1,15 +1,25 @@ -{% extends 'templates/layouts/base.twig' %} +{% extends '@templates/layouts/base.twig' %} {% block content %} - {% for post in posts %} - {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} - {% endfor %} + {% set entries = posts|default([]) %} - {% include 'templates/partials/pagination.twig' with { - pagination: posts.pagination({ - show_all: false, - mid_size: 3, - end_size: 2 - }) - } %} + {% if entries is not empty %} +
    + {% for post in entries %} + {% include ['@templates/partials/tease-' ~ post.type ~ '.twig', '@templates/partials/tease.twig'] with { + post: post + } only %} + {% endfor %} +
    + + {% include '@templates/partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } only %} + {% else %} +

    No posts were found.

    + {% endif %} {% endblock %} diff --git a/templates/author.twig b/templates/author.twig index 0249fea..3061798 100644 --- a/templates/author.twig +++ b/templates/author.twig @@ -1,7 +1,17 @@ -{% extends 'templates/layouts/base.twig' %} +{% extends '@templates/layouts/base.twig' %} {% block content %} - {% for post in posts %} - {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} - {% endfor %} + {% set entries = posts|default([]) %} + + {% if entries is not empty %} +
    + {% for post in entries %} + {% include ['@templates/partials/tease-' ~ post.type ~ '.twig', '@templates/partials/tease.twig'] with { + post: post + } only %} + {% endfor %} +
    + {% else %} +

    No posts were found for this author.

    + {% endif %} {% endblock %} diff --git a/templates/index.twig b/templates/index.twig index e610d48..3198a42 100644 --- a/templates/index.twig +++ b/templates/index.twig @@ -1,16 +1,25 @@ -{% extends 'templates/layouts/base.twig' %} +{% extends '@templates/layouts/base.twig' %} {% block content %} -

    {{ foo }}

    - {% for post in posts %} - {% include ['templates/partials/tease-' ~ post.type ~ '.twig', 'templates/partials/tease.twig'] %} - {% endfor %} + {% set entries = posts|default([]) %} - {% include 'templates/partials/pagination.twig' with { - pagination: posts.pagination({ - show_all: false, - mid_size: 3, - end_size: 2 - }) - } %} + {% if entries is not empty %} +
    + {% for post in entries %} + {% include ['@templates/partials/tease-' ~ post.type ~ '.twig', '@templates/partials/tease.twig'] with { + post: post + } only %} + {% endfor %} +
    + + {% include '@templates/partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } only %} + {% else %} +

    No posts were found.

    + {% endif %} {% endblock %} diff --git a/templates/layouts/base.twig b/templates/layouts/base.twig index 20175ec..34074bd 100644 --- a/templates/layouts/base.twig +++ b/templates/layouts/base.twig @@ -1,42 +1,47 @@ - - + + {% block head %} - {% include 'templates/partials/head.twig' %} + {% include '@templates/partials/head.twig' %} {% endblock %} - + {{ function('wp_body_open') }} - -
    - {% block header %} -
    -

    - {{ site.name }} -

    - - -
    - {% endblock %} -
    + -
    - {% if title %} -

    {{ title }}

    - {% endif %} -
    - {% block content %} - Sorry, no content + + +
    + {% if title|default(null) %} + + {% endif %} + + {% block content %}{% endblock %}
    {% block footer %} - {% include 'templates/partials/footer.twig' %} + {% include '@templates/partials/footer.twig' %} {% endblock %} + {{ function('wp_footer') }} {% do action('get_footer') %} diff --git a/templates/page.twig b/templates/page.twig index d2c0571..18ba3fe 100644 --- a/templates/page.twig +++ b/templates/page.twig @@ -1,12 +1,20 @@ -{% extends 'templates/layouts/base.twig' %} +{% extends '@templates/layouts/base.twig' %} {% block content %} -
    -
    -
    -

    {{ post.title }}

    -
    {{ post.content }}
    -
    + {% if post|default(null) %} +
    +
    +

    {{ post.title }}

    +
    + +
    + {{ post.content }} +
    -
    + {% else %} +

    No page content was found.

    + {% endif %} {% endblock %} diff --git a/templates/partials/comment-form.twig b/templates/partials/comment-form.twig index 565f4b6..e410c94 100644 --- a/templates/partials/comment-form.twig +++ b/templates/partials/comment-form.twig @@ -1,32 +1,37 @@ -
    -

    Add comment

    -
    - {% if user %} - - - - {% else %} - - - - {% endif %} - - - - - -

    Your comment will be revised by the site if needed.

    -
    -
    +{% if post|default(null) and post.id|default(null) %} + {% set current_user = user|default(null) %} + +
    +

    Leave a comment

    + +
    + {% if current_user %} + + + + {% else %} +

    + + +

    +

    + + +

    +

    + + +

    + {% endif %} + +

    + + +

    + + + + +
    +
    +{% endif %} diff --git a/templates/partials/comment.twig b/templates/partials/comment.twig index 3bac23c..c056a05 100644 --- a/templates/partials/comment.twig +++ b/templates/partials/comment.twig @@ -1,19 +1,24 @@ -
    -
    {{ comment.author.name }} says
    -
    {{ comment.content|wpautop }}
    +{% if comment|default(null) %} +
    + {% if comment.author.name|default(null) %} +
    +

    {{ comment.author.name }}

    +
    + {% endif %} -
    - {% include 'templates/partials/comment-form.twig' %} + {% if comment.content|default(null) %} +
    {{ comment.content|wpautop }}
    + {% endif %} - {% if post.comments %} -

    replies

    -
    - {% for cmt in comment.children %} - {% include 'templates/partials/comment.twig' with { - comment: cmt - } %} + {% set children = comment.children|default([]) %} + {% if children is not empty %} +
    + {% for child in children %} + {% include '@templates/partials/comment.twig' with { + comment: child + } only %} {% endfor %}
    {% endif %} -
    -
    + +{% endif %} diff --git a/templates/partials/footer.twig b/templates/partials/footer.twig index 606165c..6ee6988 100644 --- a/templates/partials/footer.twig +++ b/templates/partials/footer.twig @@ -1 +1,5 @@ -
    Copyright {{ 'now'|date('Y') }}
    +
    + +
    diff --git a/templates/partials/head.twig b/templates/partials/head.twig index ea68c2a..8896ee6 100644 --- a/templates/partials/head.twig +++ b/templates/partials/head.twig @@ -1,9 +1,6 @@ - - + - - {% do action('get_header') %} {{ function('wp_head') }} diff --git a/templates/partials/menu.twig b/templates/partials/menu.twig index 74a6bd6..50dcadc 100644 --- a/templates/partials/menu.twig +++ b/templates/partials/menu.twig @@ -1,11 +1,25 @@ -{% if menu %} -
      - {% for item in items %} -
    • - {{ item.title }} - {% include 'templates/partials/menu.twig' with { - items: item.children - } %} +{% set menu_items = items|default([]) %} +{% if menu_items is not empty %} + diff --git a/templates/partials/pagination.twig b/templates/partials/pagination.twig index e4d737b..20f0f3d 100644 --- a/templates/partials/pagination.twig +++ b/templates/partials/pagination.twig @@ -1,43 +1,27 @@ -{% if posts.pagination.pages is not empty %} -
    ' ), + 'File caption render_block should append the attachment caption.' + ); + + emulsify_editor_enhancements_smoke_write( $child . '/dist/global/editor/js/index.js', 'window.editorSmoke = true;' ); + emulsify_editor_enhancements_smoke_write( $child . '/dist/global/editor/css/index.css', '.editor-smoke{}' ); + emulsify_editor_enhancements_smoke_write( $parent . '/dist/global/editor/js/index.js', 'window.parentEditorSmoke = true;' ); + + do_action( 'enqueue_block_editor_assets' ); + + emulsify_editor_enhancements_smoke_assert( + $asset_directories_seen, + 'emulsify_theme_editor_asset_directories should run during editor asset discovery.' + ); + emulsify_editor_enhancements_smoke_assert( + $asset_files_seen, + 'emulsify_theme_editor_asset_files should run during editor asset discovery.' + ); + emulsify_editor_enhancements_smoke_assert( + 1 === count( $GLOBALS['emulsify_editor_enhancements_smoke_styles'] ), + 'Editor asset enqueue should include the child editor stylesheet.' + ); + emulsify_editor_enhancements_smoke_assert( + 1 === count( $GLOBALS['emulsify_editor_enhancements_smoke_scripts'] ), + 'Editor asset enqueue should include the child editor script and skip duplicate parent relative paths.' + ); + emulsify_editor_enhancements_smoke_assert( + in_array( 'wp-blocks', $GLOBALS['emulsify_editor_enhancements_smoke_scripts'][0]['deps'], true ), + 'Editor script should declare WordPress editor dependencies.' + ); + emulsify_editor_enhancements_smoke_assert( + ! empty( $GLOBALS['emulsify_editor_enhancements_smoke_inline'] ) + && false !== strpos( $GLOBALS['emulsify_editor_enhancements_smoke_inline'][0]['data'], 'emulsifyEditorEnhancements' ) + && false !== strpos( $GLOBALS['emulsify_editor_enhancements_smoke_inline'][0]['data'], '"fileCaption":{"enabled":true' ), + 'Editor script should receive the filtered enhancement config.' + ); + + echo "Editor enhancements smoke checks passed.\n"; +} finally { + emulsify_editor_enhancements_smoke_remove( $work_root ); +} diff --git a/.github/scripts/editor-policy-smoke.php b/.github/scripts/editor-policy-smoke.php new file mode 100644 index 0000000..1e76298 --- /dev/null +++ b/.github/scripts/editor-policy-smoke.php @@ -0,0 +1,374 @@ + $accepted_args, + 'callback' => $callback, + ); + + ksort( $GLOBALS['emulsify_editor_policy_smoke_filters'][ $hook ] ); + + return true; + } +} + +if ( ! function_exists( 'apply_filters' ) ) { + function apply_filters( string $hook, $value, ...$arguments ) { + if ( empty( $GLOBALS['emulsify_editor_policy_smoke_filters'][ $hook ] ) ) { + return $value; + } + + foreach ( $GLOBALS['emulsify_editor_policy_smoke_filters'][ $hook ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + $value = call_user_func_array( + $callback['callback'], + array_slice( + array_merge( array( $value ), $arguments ), + 0, + $callback['accepted_args'] + ) + ); + } + } + + return $value; + } +} + +if ( ! function_exists( 'get_stylesheet_directory' ) ) { + function get_stylesheet_directory(): string { + return $GLOBALS['emulsify_editor_policy_smoke_child']; + } +} + +if ( ! function_exists( 'get_template_directory' ) ) { + function get_template_directory(): string { + return $GLOBALS['emulsify_editor_policy_smoke_parent']; + } +} + +if ( ! function_exists( 'get_post_type' ) ) { + function get_post_type( $post ): string { + return is_object( $post ) && isset( $post->post_type ) ? (string) $post->post_type : ''; + } +} + +if ( ! function_exists( 'current_user_can' ) ) { + function current_user_can( string $capability ): bool { + return in_array( $capability, $GLOBALS['emulsify_editor_policy_smoke_current_caps'], true ); + } +} + +/** + * Fails the smoke script when an assertion is false. + * + * @param bool $condition Assertion condition. + * @param string $message Failure message. + * @return void + */ +function emulsify_editor_policy_smoke_assert( bool $condition, string $message ): void { + if ( ! $condition ) { + throw new RuntimeException( $message ); + } +} + +/** + * Writes a fixture file. + * + * @param string $path File path. + * @param string $contents File contents. + * @return void + */ +function emulsify_editor_policy_smoke_write( string $path, string $contents ): void { + $directory = dirname( $path ); + + if ( ! is_dir( $directory ) && ! mkdir( $directory, 0777, true ) ) { + throw new RuntimeException( sprintf( 'Could not create fixture directory: %s', $directory ) ); + } + + if ( false === file_put_contents( $path, $contents ) ) { + throw new RuntimeException( sprintf( 'Could not write fixture file: %s', $path ) ); + } +} + +/** + * Recursively removes a path. + * + * @param string $path Path to remove. + * @return void + */ +function emulsify_editor_policy_smoke_remove( string $path ): void { + if ( ! file_exists( $path ) ) { + return; + } + + if ( is_file( $path ) || is_link( $path ) ) { + unlink( $path ); + return; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $iterator as $item ) { + $item->isDir() && ! $item->isLink() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); + } + + rmdir( $path ); +} + +$repo_root = dirname( __DIR__, 2 ); +$work_root = sys_get_temp_dir() . '/emulsify-editor-policy-' . uniqid( '', true ); +$child = $work_root . '/child-theme'; +$parent = $work_root . '/parent-theme'; +$context = (object) array( + 'post' => (object) array( + 'post_type' => 'page', + ), +); + +$GLOBALS['emulsify_editor_policy_smoke_child'] = $child; +$GLOBALS['emulsify_editor_policy_smoke_parent'] = $parent; + +try { + require_once $repo_root . '/includes/class-editor-policy.php'; + + $policy = new Emulsify\Theme\Editor_Policy(); + $policy->register(); + + foreach ( array( 'allowed_block_types_all', 'block_editor_settings_all', 'register_post_type_args', 'block_type_metadata_settings', 'register_block_type_args' ) as $hook ) { + emulsify_editor_policy_smoke_assert( + isset( $GLOBALS['emulsify_editor_policy_smoke_filters'][ $hook ] ), + sprintf( 'Editor policy should register the %s hook.', $hook ) + ); + } + + emulsify_editor_policy_smoke_assert( + true === apply_filters( 'allowed_block_types_all', true, $context ), + 'Editor policy should preserve default allowed block behavior when unconfigured.' + ); + + $default_settings = array( + 'blockPatterns' => array( + array( + 'name' => 'core/query', + ), + ), + 'enableUserPatterns' => true, + ); + + emulsify_editor_policy_smoke_assert( + $default_settings === apply_filters( 'block_editor_settings_all', $default_settings, $context ), + 'Editor policy should preserve editor settings when unconfigured.' + ); + + emulsify_editor_policy_smoke_assert( + array( 'capabilities' => array( 'create_posts' => 'edit_posts' ) ) === apply_filters( + 'register_post_type_args', + array( + 'capabilities' => array( + 'create_posts' => 'edit_posts', + ), + ), + 'wp_block' + ), + 'Editor policy should preserve wp_block capabilities when unconfigured.' + ); + + emulsify_editor_policy_smoke_write( + $child . '/patterns/hero.json', + (string) json_encode( + array( + 'content' => '

    Intro

    ', + ) + ) + ); + + add_filter( + 'emulsify_theme_editor_policy_options', + static function ( array $options ) use ( $child ): array { + $options['allowed_block_types'] = array( + 'default' => array( 'core/paragraph', 'heading' ), + 'by_post_type' => array( + 'page' => array( 'core/image' ), + ), + ); + $options['auto_allow_pattern_blocks'] = true; + $options['pattern_directories'] = array( $child . '/patterns' ); + $options['pattern_namespaces'] = array( 'project', 'theme' ); + $options['disable_user_patterns_for_non_admins'] = true; + $options['admin_capability'] = 'manage_options'; + $options['restrict_wp_block_creation'] = true; + $options['wp_block_create_capability'] = 'manage_options'; + $options['block_support_overrides'] = array( + '*' => array( + 'supports' => array( + 'styles' => false, + ), + ), + 'core/button' => array( + 'styles' => array(), + 'supports' => array( + 'color' => array( + 'gradients' => false, + ), + ), + ), + ); + + return $options; + } + ); + + add_filter( + 'emulsify_theme_allowed_block_types', + static function ( ?array $blocks, string $post_type ): ?array { + if ( 'page' === $post_type && is_array( $blocks ) ) { + $blocks[] = 'core/quote'; + } + + return $blocks; + }, + 10, + 2 + ); + + add_filter( + 'emulsify_theme_pattern_namespaces', + static function ( array $namespaces ): array { + $namespaces[] = 'child'; + + return $namespaces; + } + ); + + add_filter( + 'emulsify_theme_block_support_overrides', + static function ( array $overrides, string $block_name, array $settings, string $source ): array { + if ( 'core/button' === $block_name && 'register_block_type_args' === $source ) { + $overrides['core/button']['supports']['anchor'] = false; + } + + return $overrides; + }, + 10, + 4 + ); + + $allowed = apply_filters( 'allowed_block_types_all', array( 'core/html' ), $context ); + + foreach ( array( 'core/html', 'core/paragraph', 'core/heading', 'core/image', 'core/quote', 'acf/project-hero' ) as $expected_block ) { + emulsify_editor_policy_smoke_assert( + in_array( $expected_block, $allowed, true ), + sprintf( 'Allowed block policy should include %s.', $expected_block ) + ); + } + + emulsify_editor_policy_smoke_assert( + 1 === count( array_keys( $allowed, 'core/paragraph', true ) ), + 'Allowed block policy should deduplicate configured and pattern-derived block names.' + ); + + $filtered_settings = apply_filters( + 'block_editor_settings_all', + array( + 'blockPatterns' => array( + array( + 'name' => 'project/hero', + ), + array( + 'name' => 'theme/card', + ), + array( + 'name' => 'child/banner', + ), + array( + 'name' => 'core/query', + ), + ), + 'enableUserPatterns' => true, + ), + $context + ); + $pattern_names = array_column( $filtered_settings['blockPatterns'], 'name' ); + + emulsify_editor_policy_smoke_assert( + array( 'project/hero', 'theme/card', 'child/banner' ) === $pattern_names, + 'Pattern namespace policy should keep only configured namespaces.' + ); + emulsify_editor_policy_smoke_assert( + false === $filtered_settings['enableUserPatterns'], + 'Editor policy should disable user-created patterns for users without the admin capability.' + ); + + $wp_block_args = apply_filters( 'register_post_type_args', array( 'capabilities' => array( 'edit_posts' => 'edit_posts' ) ), 'wp_block' ); + + emulsify_editor_policy_smoke_assert( + true === $wp_block_args['map_meta_cap'] && 'manage_options' === $wp_block_args['capabilities']['create_posts'], + 'Editor policy should restrict wp_block creation to the configured capability.' + ); + emulsify_editor_policy_smoke_assert( + array() === apply_filters( 'register_post_type_args', array(), 'post' ), + 'Editor policy should leave non-wp_block post type arguments alone.' + ); + + $metadata_settings = apply_filters( + 'block_type_metadata_settings', + array( + 'supports' => array( + 'spacing' => true, + ), + ), + array( + 'name' => 'core/button', + ) + ); + + emulsify_editor_policy_smoke_assert( + false === $metadata_settings['supports']['styles'] + && true === $metadata_settings['supports']['spacing'] + && false === $metadata_settings['supports']['color']['gradients'] + && array() === $metadata_settings['styles'], + 'Block support overrides should apply to metadata settings without dropping existing supports.' + ); + + $registration_args = apply_filters( + 'register_block_type_args', + array( + 'supports' => array( + 'anchor' => true, + ), + ), + 'core/button' + ); + + emulsify_editor_policy_smoke_assert( + false === $registration_args['supports']['styles'] + && false === $registration_args['supports']['anchor'] + && false === $registration_args['supports']['color']['gradients'], + 'Block support overrides should apply to register_block_type_args and honor source-aware filters.' + ); + + echo "Editor policy smoke checks passed.\n"; +} catch ( Throwable $throwable ) { + fwrite( STDERR, $throwable->getMessage() . "\n" ); + exit( 1 ); +} finally { + emulsify_editor_policy_smoke_remove( $work_root ); +} diff --git a/.github/scripts/pattern-registry-smoke.php b/.github/scripts/pattern-registry-smoke.php new file mode 100644 index 0000000..285e99a --- /dev/null +++ b/.github/scripts/pattern-registry-smoke.php @@ -0,0 +1,342 @@ + $accepted_args, + 'callback' => $callback, + ); + + ksort( $GLOBALS['emulsify_pattern_smoke_actions'][ $hook ] ); + + return true; + } +} + +if ( ! function_exists( 'do_action' ) ) { + function do_action( string $hook, ...$arguments ): void { + if ( empty( $GLOBALS['emulsify_pattern_smoke_actions'][ $hook ] ) ) { + return; + } + + foreach ( $GLOBALS['emulsify_pattern_smoke_actions'][ $hook ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + call_user_func_array( + $callback['callback'], + array_slice( $arguments, 0, $callback['accepted_args'] ) + ); + } + } + } +} + +if ( ! function_exists( 'add_filter' ) ) { + function add_filter( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): bool { + $GLOBALS['emulsify_pattern_smoke_filters'][ $hook ][ $priority ][] = array( + 'accepted_args' => $accepted_args, + 'callback' => $callback, + ); + + ksort( $GLOBALS['emulsify_pattern_smoke_filters'][ $hook ] ); + + return true; + } +} + +if ( ! function_exists( 'apply_filters' ) ) { + function apply_filters( string $hook, $value, ...$arguments ) { + if ( empty( $GLOBALS['emulsify_pattern_smoke_filters'][ $hook ] ) ) { + return $value; + } + + foreach ( $GLOBALS['emulsify_pattern_smoke_filters'][ $hook ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + $value = call_user_func_array( + $callback['callback'], + array_slice( + array_merge( array( $value ), $arguments ), + 0, + $callback['accepted_args'] + ) + ); + } + } + + return $value; + } +} + +if ( ! function_exists( 'get_stylesheet_directory' ) ) { + function get_stylesheet_directory(): string { + return $GLOBALS['emulsify_pattern_smoke_child']; + } +} + +if ( ! function_exists( 'get_template_directory' ) ) { + function get_template_directory(): string { + return $GLOBALS['emulsify_pattern_smoke_parent']; + } +} + +if ( ! function_exists( 'register_block_pattern' ) ) { + function register_block_pattern( string $name, array $args ): bool { + $GLOBALS['emulsify_pattern_smoke_patterns'][ $name ] = $args; + + return true; + } +} + +if ( ! function_exists( 'register_block_pattern_category' ) ) { + function register_block_pattern_category( string $name, array $args ): bool { + $GLOBALS['emulsify_pattern_smoke_categories'][ $name ] = $args; + + return true; + } +} + +/** + * Fails the smoke script when an assertion is false. + * + * @param bool $condition Assertion condition. + * @param string $message Failure message. + * @return void + */ +function emulsify_pattern_smoke_assert( bool $condition, string $message ): void { + if ( ! $condition ) { + throw new RuntimeException( $message ); + } +} + +/** + * Writes a fixture file. + * + * @param string $path File path. + * @param string $contents File contents. + * @return void + */ +function emulsify_pattern_smoke_write( string $path, string $contents ): void { + $directory = dirname( $path ); + + if ( ! is_dir( $directory ) && ! mkdir( $directory, 0777, true ) ) { + throw new RuntimeException( sprintf( 'Could not create fixture directory: %s', $directory ) ); + } + + if ( false === file_put_contents( $path, $contents ) ) { + throw new RuntimeException( sprintf( 'Could not write fixture file: %s', $path ) ); + } +} + +/** + * Recursively removes a path. + * + * @param string $path Path to remove. + * @return void + */ +function emulsify_pattern_smoke_remove( string $path ): void { + if ( ! file_exists( $path ) ) { + return; + } + + if ( is_file( $path ) || is_link( $path ) ) { + unlink( $path ); + return; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $iterator as $item ) { + $item->isDir() && ! $item->isLink() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); + } + + rmdir( $path ); +} + +$repo_root = dirname( __DIR__, 2 ); +$work_root = sys_get_temp_dir() . '/emulsify-pattern-registry-' . uniqid( '', true ); +$child = $work_root . '/child-theme'; +$parent = $work_root . '/parent-theme'; +$extra = $work_root . '/extra-patterns'; + +$GLOBALS['emulsify_pattern_smoke_child'] = $child; +$GLOBALS['emulsify_pattern_smoke_parent'] = $parent; + +try { + require_once $repo_root . '/includes/class-patterns.php'; + + $patterns = new Emulsify\Theme\Patterns(); + $patterns->register(); + + emulsify_pattern_smoke_assert( + isset( $GLOBALS['emulsify_pattern_smoke_actions']['init'] ), + 'Patterns service should register on init.' + ); + + do_action( 'init' ); + + emulsify_pattern_smoke_assert( + array() === $GLOBALS['emulsify_pattern_smoke_patterns'], + 'Patterns service should no-op when no pattern directories exist.' + ); + + emulsify_pattern_smoke_write( + $child . '/patterns/hero.json', + (string) json_encode( + array( + 'name' => 'child/hero', + 'title' => 'Child Hero', + 'description' => 'A child pattern.', + 'categories' => array( 'starter' ), + 'keywords' => array( 'hero' ), + 'postTypes' => array( 'page' ), + 'viewportWidth' => 1200, + 'content' => '

    Child hero

    ', + ) + ) + ); + emulsify_pattern_smoke_write( + $child . '/patterns/override.json', + (string) json_encode( + array( + 'name' => 'child/override', + 'title' => 'Child Override', + 'content' => '

    Child override

    ', + ) + ) + ); + emulsify_pattern_smoke_write( + $parent . '/patterns/override.json', + (string) json_encode( + array( + 'name' => 'parent/override', + 'title' => 'Parent Override', + 'content' => '

    Parent override

    ', + ) + ) + ); + emulsify_pattern_smoke_write( + $parent . '/patterns/invalid.json', + (string) json_encode( + array( + 'name' => 'parent/invalid', + 'title' => 'Invalid', + ) + ) + ); + emulsify_pattern_smoke_write( + $extra . '/extra.json', + (string) json_encode( + array( + 'name' => 'extra/promo', + 'title' => 'Extra Promo', + 'categories' => array( 'extra' ), + 'content' => '

    Extra promo

    ', + ) + ) + ); + + add_filter( + 'emulsify_theme_pattern_directories', + static function ( array $directories ) use ( $extra ): array { + $directories[] = array( + 'path' => $extra, + 'source' => 'smoke', + ); + + return $directories; + } + ); + + add_filter( + 'emulsify_theme_pattern_data', + static function ( array $data, array $file ): array { + if ( 'hero.json' === $file['relative'] ) { + $data['title'] = 'Filtered Hero'; + } + + return $data; + }, + 10, + 2 + ); + + add_filter( + 'emulsify_theme_pattern_categories', + static function ( array $categories ): array { + $categories['starter']['description'] = 'Starter category from smoke coverage.'; + + return $categories; + } + ); + + add_filter( + 'emulsify_theme_pattern_args', + static function ( array $args, array $data ): array { + if ( 'extra/promo' === $args['name'] ) { + $args['keywords'][] = 'filtered'; + } + + return $args; + }, + 10, + 2 + ); + + $GLOBALS['emulsify_pattern_smoke_patterns'] = array(); + $GLOBALS['emulsify_pattern_smoke_categories'] = array(); + + $patterns->register_patterns(); + + emulsify_pattern_smoke_assert( + array( 'child/hero', 'child/override', 'extra/promo' ) === array_keys( $GLOBALS['emulsify_pattern_smoke_patterns'] ), + 'Patterns service should register valid child-first and filtered JSON patterns.' + ); + emulsify_pattern_smoke_assert( + ! isset( $GLOBALS['emulsify_pattern_smoke_patterns']['parent/override'] ), + 'Child pattern JSON files should override parent files with the same basename.' + ); + emulsify_pattern_smoke_assert( + 'Filtered Hero' === $GLOBALS['emulsify_pattern_smoke_patterns']['child/hero']['title'], + 'Decoded pattern data filter should alter metadata before registration.' + ); + emulsify_pattern_smoke_assert( + 1200 === $GLOBALS['emulsify_pattern_smoke_patterns']['child/hero']['viewportWidth'], + 'Patterns service should preserve viewportWidth metadata.' + ); + emulsify_pattern_smoke_assert( + array( 'starter', 'extra' ) === array_keys( $GLOBALS['emulsify_pattern_smoke_categories'] ), + 'Patterns service should register categories discovered from valid pattern metadata.' + ); + emulsify_pattern_smoke_assert( + 'Starter category from smoke coverage.' === $GLOBALS['emulsify_pattern_smoke_categories']['starter']['description'], + 'Pattern categories filter should alter category registration args.' + ); + emulsify_pattern_smoke_assert( + in_array( 'filtered', $GLOBALS['emulsify_pattern_smoke_patterns']['extra/promo']['keywords'], true ), + 'Pattern args filter should alter final registration args.' + ); + + echo "Pattern registry smoke checks passed.\n"; +} catch ( Throwable $throwable ) { + fwrite( STDERR, $throwable->getMessage() . "\n" ); + exit( 1 ); +} finally { + emulsify_pattern_smoke_remove( $work_root ); +} diff --git a/.github/scripts/pr-validation.cjs b/.github/scripts/pr-validation.cjs index 7fccdaa..afffdc1 100644 --- a/.github/scripts/pr-validation.cjs +++ b/.github/scripts/pr-validation.cjs @@ -27,9 +27,14 @@ run('Install Composer dependencies for Twig smoke coverage', 'composer', [ '--prefer-dist', ]); run('Run PHP lint', 'npm', ['run', 'lint:php']); +run('Run ACF Local JSON smoke test', 'npm', ['run', 'smoke:acf-json']); run('Run Twig attribute helper smoke test', 'npm', ['run', 'smoke:attributes']); run('Run child theme generator smoke test', 'npm', ['run', 'smoke:child-theme-generator']); run('Run component locator smoke test', 'npm', ['run', 'smoke:component-locator']); +run('Run core block Twig renderer smoke test', 'npm', ['run', 'smoke:core-block-twig']); +run('Run editor enhancements smoke test', 'npm', ['run', 'smoke:editor-enhancements']); +run('Run editor policy smoke test', 'npm', ['run', 'smoke:editor-policy']); +run('Run pattern registry smoke test', 'npm', ['run', 'smoke:patterns']); run('Run parent theme filter smoke test', 'npm', ['run', 'smoke:theme-filters']); run('Run Twig project namespace smoke test', 'npm', ['run', 'smoke:twig-project-namespace']); run('Install Whisk dependencies', 'npm', ['run', 'whisk:install']); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index eb28193..0750f0f 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -247,8 +247,12 @@ function runStaticChecks() { architecture: readFile('docs/parent-child-architecture.md'), twig: readFile('docs/timber-and-twig-authoring.md'), workflow: readFile('docs/core-4-vite-workflow.md'), + acfJson: readFile('docs/acf-local-json.md'), acfBlocks: readFile('docs/acf-twig-blocks.md'), nativeBlocks: readFile('docs/native-gutenberg-blocks.md'), + blockPatterns: readFile('docs/block-patterns.md'), + editorEnhancements: readFile('docs/editor-enhancements.md'), + editorPolicy: readFile('docs/editor-policy.md'), assets: readFile('docs/asset-loading.md'), cli: readFile('docs/wp-cli-child-theme-generation.md'), release: readFile('docs/release-process.md'), @@ -283,10 +287,14 @@ function runStaticChecks() { '.gitignore', '.nvmrc', 'README.md', + 'docs/acf-local-json.md', 'docs/acf-twig-blocks.md', 'docs/asset-loading.md', + 'docs/block-patterns.md', 'docs/core-4-vite-workflow.md', + 'docs/editor-enhancements.md', 'docs/native-gutenberg-blocks.md', + 'docs/editor-policy.md', 'docs/parent-child-architecture.md', 'docs/release-process.md', 'docs/sister-project-parity.md', @@ -295,8 +303,12 @@ function runStaticChecks() { 'docs/wp-cli-child-theme-generation.md', 'composer.json', 'functions.php', + 'includes/class-acf-local-json.php', 'includes/class-attribute-bag.php', 'includes/class-cli.php', + 'includes/class-editor-enhancements.php', + 'includes/class-editor-policy.php', + 'includes/class-patterns.php', 'includes/class-twig.php', 'package.json', 'release.config.js', @@ -309,9 +321,13 @@ function runStaticChecks() { 'templates/search.twig', 'templates/single-password.twig', 'templates/single.twig', + '.github/scripts/acf-local-json-smoke.php', '.github/scripts/attribute-helper-smoke.php', '.github/scripts/child-theme-generator-smoke.php', '.github/scripts/component-locator-smoke.php', + '.github/scripts/editor-enhancements-smoke.php', + '.github/scripts/editor-policy-smoke.php', + '.github/scripts/pattern-registry-smoke.php', '.github/scripts/theme-filters-smoke.php', '.github/scripts/twig-project-namespace-smoke.php', '.github/scripts/wordpress-fixture-smoke.cjs', @@ -319,11 +335,17 @@ function runStaticChecks() { 'whisk/package.json', 'whisk/project.emulsify.json', 'whisk/style.css', - 'whisk/src/components/button/button.component.json', - 'whisk/src/components/button/button.data.json', - 'whisk/src/components/button/button.scss', - 'whisk/src/components/button/button.stories.js', - 'whisk/src/components/button/button.twig', + 'whisk/config/acf-json/.gitkeep', + 'whisk/patterns/.gitkeep', + 'whisk/src/components/.gitkeep', + 'whisk/src/editor/columns-equal-height.component.js', + 'whisk/src/editor/default-config.component.js', + 'whisk/src/editor/embed-variations.component.js', + 'whisk/src/editor/file-caption.component.js', + 'whisk/src/editor/index.js', + 'whisk/src/editor/index.scss', + 'whisk/src/editor/placement.component.js', + 'whisk/src/editor/utils.component.js', 'whisk/templates/page.twig', ]; const missingFiles = requiredFiles.filter((file) => !fs.existsSync(path.join(repoRoot, file))); @@ -344,9 +366,13 @@ function runStaticChecks() { ensure(rootPackage.bugs.url === 'https://github.com/emulsify-ds/emulsify-wordpress-theme/issues', 'package.json bugs.url should target emulsify-wordpress-theme.'); ensure(rootPackage.scripts['pr:check'] === 'node .github/scripts/pr-validation.cjs', 'package.json should expose npm run pr:check.'); ensure(rootPackage.scripts['release:check'] === 'node .github/scripts/release-check.cjs', 'package.json should expose npm run release:check.'); + ensure(rootPackage.scripts['smoke:acf-json'] === 'php .github/scripts/acf-local-json-smoke.php', 'package.json should expose npm run smoke:acf-json.'); ensure(rootPackage.scripts['smoke:attributes'] === 'php .github/scripts/attribute-helper-smoke.php', 'package.json should expose npm run smoke:attributes.'); ensure(rootPackage.scripts['smoke:child-theme-generator'] === 'php .github/scripts/child-theme-generator-smoke.php', 'package.json should expose npm run smoke:child-theme-generator.'); ensure(rootPackage.scripts['smoke:component-locator'] === 'php .github/scripts/component-locator-smoke.php', 'package.json should expose npm run smoke:component-locator.'); + ensure(rootPackage.scripts['smoke:editor-enhancements'] === 'php .github/scripts/editor-enhancements-smoke.php', 'package.json should expose npm run smoke:editor-enhancements.'); + ensure(rootPackage.scripts['smoke:editor-policy'] === 'php .github/scripts/editor-policy-smoke.php', 'package.json should expose npm run smoke:editor-policy.'); + ensure(rootPackage.scripts['smoke:patterns'] === 'php .github/scripts/pattern-registry-smoke.php', 'package.json should expose npm run smoke:patterns.'); ensure(rootPackage.scripts['smoke:theme-filters'] === 'php .github/scripts/theme-filters-smoke.php', 'package.json should expose npm run smoke:theme-filters.'); ensure(rootPackage.scripts['smoke:twig-project-namespace'] === 'php .github/scripts/twig-project-namespace-smoke.php', 'package.json should expose npm run smoke:twig-project-namespace.'); ensure(rootPackage.scripts['whisk:install'], 'package.json should expose npm run whisk:install.'); @@ -394,7 +420,7 @@ function runStaticChecks() { ensure(attributeBag.includes('function toString'), 'AttributeBag should expose explicit serialization.'); ensure(twig.includes("'needs_context' => true"), 'Timber helper functions should accept Twig context.'); ensure(twig.includes('new AttributeBag'), 'Twig helpers should return AttributeBag objects.'); - ensure(smoke.includes('{{ bem("button", ["primary"]) }}'), 'Attribute helper smoke script should render a bem() Twig fixture.'); + ensure(smoke.includes('{{ bem("example-card", ["featured"]) }}'), 'Attribute helper smoke script should render a bem() Twig fixture.'); ensure(smoke.includes('{{ add_attributes({ class: ["foo"] }) }}'), 'Attribute helper smoke script should render an add_attributes() Twig fixture.'); return 'Attribute helper runtime and smoke fixture are wired.'; }); @@ -414,6 +440,7 @@ function runStaticChecks() { ensure(cli.includes("data['project']['name']"), 'Child theme generator should update project.emulsify.json project.name.'); ensure(cli.includes("data['project']['machineName']"), 'Child theme generator should update project.emulsify.json project.machineName.'); ensure(cli.includes("data['name'] = $machine_name"), 'Child theme generator should update package.json name.'); + ensure(cli.includes('collect_pattern_updates'), 'Child theme generator should update starter pattern namespaces.'); ensure(!cli.includes('rename_instances'), 'Child theme generator should not use blind recursive starter string replacement.'); ensure(smoke.includes("'machine-name' => 'acme-child'"), 'Child theme generator smoke should cover --machine-name.'); ensure(smoke.includes("'dry-run' => true"), 'Child theme generator smoke should cover --dry-run.'); @@ -421,6 +448,9 @@ function runStaticChecks() { ensure(smoke.includes("'activate' => true"), 'Child theme generator smoke should cover --activate.'); ensure(smoke.includes('project.emulsify.json'), 'Child theme generator smoke should validate project.emulsify.json updates.'); ensure(smoke.includes("'wordpress' === $project['project']['platform']"), 'Child theme generator smoke should validate the WordPress platform adapter.'); + ensure(smoke.includes('smoke-pattern.json'), 'Child theme generator smoke should validate optional copied pattern namespace updates.'); + ensure(smoke.includes("! is_dir( $destination . '/src/components/button' )"), 'Child theme generator smoke should prove removed starter components are not copied.'); + ensure(smoke.includes("! is_file( $destination . '/src/foundation.scss' )"), 'Child theme generator smoke should prove assumed Sass entrypoints are not copied.'); return 'WP-CLI child theme generation uses safe options and targeted metadata updates.'; }); @@ -460,45 +490,72 @@ function runStaticChecks() { }); runStaticCheck('Runtime filters', () => { + const acfJson = readFile('includes/class-acf-local-json.php'); const assets = readFile('includes/class-assets.php'); const twig = readFile('includes/class-twig.php'); const context = readFile('includes/class-context.php'); const setup = readFile('includes/class-setup.php'); + const editorEnhancements = readFile('includes/class-editor-enhancements.php'); + const editorPolicy = readFile('includes/class-editor-policy.php'); + const patterns = readFile('includes/class-patterns.php'); const locator = readFile('includes/Blocks/class-component-locator.php'); const acfBlocks = readFile('includes/Blocks/class-acf-blocks.php'); const nativeBlocks = readFile('includes/Blocks/class-native-blocks.php'); + const acfJsonSmoke = readFile('.github/scripts/acf-local-json-smoke.php'); + const editorEnhancementsSmoke = readFile('.github/scripts/editor-enhancements-smoke.php'); const smoke = readFile('.github/scripts/theme-filters-smoke.php'); + const editorPolicySmoke = readFile('.github/scripts/editor-policy-smoke.php'); + const patternSmoke = readFile('.github/scripts/pattern-registry-smoke.php'); + const smokeText = [acfJsonSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); const expectedFilters = [ + 'emulsify_theme_acf_json_enabled', + 'emulsify_theme_acf_json_save_path', + 'emulsify_theme_acf_json_load_paths', + 'emulsify_theme_acf_json_remove_default_load_path', 'emulsify_theme_asset_directories', 'emulsify_theme_asset_files', + 'emulsify_theme_editor_enhancements_config', + 'emulsify_theme_editor_asset_directories', + 'emulsify_theme_editor_asset_files', 'emulsify_theme_twig_namespaces', 'emulsify_theme_context', 'emulsify_theme_acf_block_metadata', 'emulsify_theme_acf_block_args', 'emulsify_theme_native_block_directories', + 'emulsify_theme_pattern_directories', + 'emulsify_theme_pattern_data', + 'emulsify_theme_pattern_categories', + 'emulsify_theme_pattern_args', 'emulsify_theme_component_roots', 'emulsify_theme_setup_options', + 'emulsify_theme_editor_policy_options', + 'emulsify_theme_allowed_block_types', + 'emulsify_theme_pattern_namespaces', + 'emulsify_theme_block_support_overrides', ]; - const runtimeText = [assets, twig, context, setup, locator, acfBlocks, nativeBlocks].join('\n'); + const runtimeText = [acfJson, assets, twig, context, setup, editorEnhancements, editorPolicy, patterns, locator, acfBlocks, nativeBlocks].join('\n'); for (const filter of expectedFilters) { ensure(runtimeText.includes(filter), `${filter} should be registered in runtime PHP code.`); - ensure(smoke.includes(filter), `${filter} should be covered by the runtime filter smoke test.`); + ensure(smokeText.includes(filter), `${filter} should be covered by a runtime filter smoke test.`); ensure(docsText.includes(filter), `${filter} should be documented in docs.`); } + ensure(acfJsonSmoke.includes('ACF Local JSON smoke checks passed'), 'ACF Local JSON smoke should have a clear success message.'); + ensure(editorEnhancementsSmoke.includes('Editor enhancements smoke checks passed'), 'Editor enhancements smoke should have a clear success message.'); ensure(smoke.includes('Theme filter smoke checks passed'), 'Runtime filter smoke should have a clear success message.'); + ensure(editorPolicySmoke.includes('Editor policy smoke checks passed'), 'Editor policy smoke should have a clear success message.'); + ensure(patternSmoke.includes('Pattern registry smoke checks passed'), 'Pattern registry smoke should have a clear success message.'); return 'Parent runtime exposes documented filters with smoke coverage.'; }); runStaticCheck('Whisk Core 4 and Vite metadata', () => { const scripts = whiskPackage.scripts || {}; const scriptText = Object.values(scripts).join('\n'); - const buttonTwig = readFile('whisk/src/components/button/button.twig'); - const buttonScss = readFile('whisk/src/components/button/button.scss'); - const buttonStory = readFile('whisk/src/components/button/button.stories.js'); - const buttonData = readJson('whisk/src/components/button/button.data.json'); - const buttonComponent = readJson('whisk/src/components/button/button.component.json'); + const editorEntrypoint = readFile('whisk/src/editor/index.js'); + const editorConfig = readFile('whisk/src/editor/default-config.component.js'); + const starterComponentFiles = listFilesRecursive('whisk/src/components', (file) => path.basename(file) !== '.gitkeep'); + const starterPatternFiles = listFilesRecursive('whisk/patterns', (file) => path.basename(file) !== '.gitkeep'); ensure(whiskPackage.name === 'whisk', 'whisk/package.json name should remain whisk.'); ensure(semver(whiskPackage.version), 'whisk/package.json version must be a valid semver string.'); ensure(whiskPackage.version === '2.0.0', 'whisk/package.json version should prepare the 2.0.0 release.'); @@ -510,19 +567,19 @@ function runStaticChecks() { ensure(whiskProject.project.platform === 'wordpress', 'whisk/project.emulsify.json should use the WordPress platform adapter.'); ensure(whiskProject.project.name === 'whisk', 'whisk/project.emulsify.json project.name should remain whisk.'); ensure(whiskProject.project.machineName === 'whisk', 'whisk/project.emulsify.json project.machineName should remain whisk.'); - ensure(fs.existsSync(path.join(repoRoot, 'whisk/src/components')), 'whisk/src/components should be the primary component source.'); + ensure(fs.existsSync(path.join(repoRoot, 'whisk/src/components/.gitkeep')), 'whisk/src/components should remain as an empty optional component placeholder.'); ensure(!fs.existsSync(path.join(repoRoot, 'whisk/components')), 'whisk/components should not remain as an unused starter placeholder.'); + ensure(starterComponentFiles.length === 0, `Whisk should not ship concrete starter component files: ${starterComponentFiles.join(', ')}.`); + ensure(fs.existsSync(path.join(repoRoot, 'whisk/patterns/.gitkeep')), 'whisk/patterns should remain as an empty optional pattern placeholder.'); + ensure(starterPatternFiles.length === 0, `Whisk should not ship concrete starter pattern files: ${starterPatternFiles.join(', ')}.`); for (const entryFile of ['foundation.scss', 'layout.scss', 'tokens.scss']) { - ensure(fs.existsSync(path.join(repoRoot, 'whisk/src', entryFile)), `whisk/src/${entryFile} should exist for Core 4 Vite global entries.`); + ensure(!fs.existsSync(path.join(repoRoot, 'whisk/src', entryFile)), `whisk/src/${entryFile} should not assume a selected component library.`); } - ensure(buttonTwig.includes("bem('button'"), 'Whisk example button Twig should use the bem() helper.'); - ensure(buttonScss.includes('.button'), 'Whisk example button should include component Sass.'); - ensure(buttonStory.includes("renderTwig(template)"), 'Whisk example button story should render the Twig template with Core Storybook.'); - ensure(buttonStory.includes("import data from './button.data.json'"), 'Whisk example button story should use fixture data.'); - ensure(buttonData.text === 'Read more', 'Whisk example button data should provide default text.'); - ensure(buttonComponent.title === 'Example Button', 'Whisk example button component metadata should be clearly marked as an example.'); - ensure(buttonComponent.description.includes('Example ACF/Twig block metadata'), 'Whisk example component metadata should document the ACF/Twig block purpose.'); - ensure(buttonComponent.name === 'emulsify-example-button', 'Whisk example ACF block name should use an ACF-safe un-namespaced slug.'); + ensure(editorEntrypoint.includes('registerColumnsEqualHeight'), 'Whisk editor entrypoint should register the columns enhancement module.'); + ensure(editorEntrypoint.includes('registerFileCaption'), 'Whisk editor entrypoint should register the file caption enhancement module.'); + ensure(editorEntrypoint.includes('registerEmbedVariations'), 'Whisk editor entrypoint should register the embed variation enhancement module.'); + ensure(editorEntrypoint.includes('registerPlacementEnforcement'), 'Whisk editor entrypoint should register the placement enhancement module.'); + ensure(editorConfig.includes('enabled: false'), 'Whisk editor enhancements should be disabled by default.'); ensure(whiskPackage.dependencies && whiskPackage.dependencies['@emulsify/core'], 'whisk/package.json must declare @emulsify/core.'); ensure(whiskPackage.dependencies['@emulsify/core'] === '^4.1.0', 'whisk/package.json should target Emulsify Core ^4.1.0.'); ensure(scripts.build && scripts.build.includes('vite build --config node_modules/@emulsify/core/config/vite/vite.config.js'), 'whisk/package.json build script should use the Emulsify Core Vite config.'); @@ -584,16 +641,16 @@ function runStaticChecks() { ensure(childComponentLegacyPathIndex !== -1, 'Twig integration should register child legacy @components path.'); ensure(parentComponentSrcPathIndex !== -1, 'Twig integration should register parent src @components path.'); ensure(parentComponentLegacyPathIndex !== -1, 'Twig integration should register parent legacy @components path.'); - ensure(childComponentSrcPathIndex < childComponentLegacyPathIndex, 'Twig integration should prefer child src/components before child components.'); + ensure(childComponentSrcPathIndex < childComponentLegacyPathIndex, 'Twig integration should check child src/components compatibility roots before child components roots.'); ensure(childComponentLegacyPathIndex < parentComponentSrcPathIndex, 'Twig integration should register child @components paths before parent @components paths.'); - ensure(parentComponentSrcPathIndex < parentComponentLegacyPathIndex, 'Twig integration should prefer parent src/components before parent components.'); + ensure(parentComponentSrcPathIndex < parentComponentLegacyPathIndex, 'Twig integration should check parent src/components compatibility roots before parent components roots.'); ensure(twigIntegration.includes('project.emulsify.json'), 'Twig integration should read active child project.emulsify.json metadata.'); ensure(twigIntegration.includes('emulsify_theme_project_component_roots'), 'Twig integration should expose a focused project component roots filter.'); ensure(twigIntegration.includes('implements \\Twig\\Loader\\LoaderInterface'), 'Twig integration should wrap the loader for machineName:component references.'); ensure(twigIntegration.includes('machineName:component'), 'Twig integration should document the project component reference intent in code comments.'); ensure(!fs.existsSync(path.join(repoRoot, 'whisk/includes/twig-namespaces.php')), 'Whisk should rely on the parent Twig namespace integration by default.'); ensure(!childFunctions.includes('twig-namespaces.php'), 'whisk/functions.php should not require a duplicate Twig namespace file.'); - ensure(childFunctions.includes('whisk:button') && childFunctions.includes('@components for existing shared or migration includes'), 'whisk/functions.php should document project machine-name component references while preserving @components.'); + ensure(childFunctions.includes('project_machine_name:component_name') && childFunctions.includes('@components for compatible component libraries'), 'whisk/functions.php should document generic project machine-name component references while preserving @components compatibility.'); for (const route of ['home', 'page', 'single', 'archive', 'search', 'author', '404']) { ensure(wordpressFixtureSmoke.includes(`name: '${route}'`), `WordPress fixture smoke should render the ${route} route.`); } @@ -696,9 +753,13 @@ function runStaticChecks() { ensure(prValidationScript.includes('composer') && prValidationScript.includes('validate'), 'PR validation should validate Composer metadata.'); ensure(prValidationScript.includes('composer') && prValidationScript.includes('install'), 'PR validation should install Composer dependencies for Twig smoke coverage.'); ensure(prValidationScript.includes('lint:php'), 'PR validation should run PHP lint.'); + ensure(prValidationScript.includes('smoke:acf-json'), 'PR validation should run the ACF Local JSON smoke test.'); ensure(prValidationScript.includes('smoke:attributes'), 'PR validation should run the attribute helper smoke test.'); ensure(prValidationScript.includes('smoke:child-theme-generator'), 'PR validation should run the child theme generator smoke test.'); ensure(prValidationScript.includes('smoke:component-locator'), 'PR validation should run the component locator smoke test.'); + ensure(prValidationScript.includes('smoke:editor-enhancements'), 'PR validation should run the editor enhancements smoke test.'); + ensure(prValidationScript.includes('smoke:editor-policy'), 'PR validation should run the editor policy smoke test.'); + ensure(prValidationScript.includes('smoke:patterns'), 'PR validation should run the pattern registry smoke test.'); ensure(prValidationScript.includes('smoke:theme-filters'), 'PR validation should run the parent theme filter smoke test.'); ensure(prValidationScript.includes('smoke:twig-project-namespace'), 'PR validation should run the Twig project namespace smoke test.'); ensure(prValidationScript.includes('whisk:install'), 'PR validation should install Whisk dependencies.'); @@ -713,8 +774,12 @@ function runStaticChecks() { 'docs/parent-child-architecture.md', 'docs/timber-and-twig-authoring.md', 'docs/core-4-vite-workflow.md', + 'docs/acf-local-json.md', 'docs/acf-twig-blocks.md', 'docs/native-gutenberg-blocks.md', + 'docs/block-patterns.md', + 'docs/editor-enhancements.md', + 'docs/editor-policy.md', 'docs/asset-loading.md', 'docs/wp-cli-child-theme-generation.md', 'docs/release-process.md', @@ -755,24 +820,41 @@ function runStaticChecks() { ensure(docs.parity.includes('Native Gutenberg blocks use WordPress `block.json` metadata'), 'Sister-project parity doc should document native block.json blocks.'); ensure(docs.parity.includes("WordPress project generation is handled by the parent theme's WP-CLI command"), 'Sister-project parity doc should document WP-CLI generation.'); ensure(docs.parity.includes('`project.emulsify.json` uses `"platform": "wordpress"`'), 'Sister-project parity doc should document the WordPress platform adapter.'); - ensure(docs.parity.includes('{% include "whisk:button" %}') && docs.parity.includes('The legacy `@components/button/button.twig` namespace remains supported'), 'Sister-project parity doc should promote project machine-name component includes while preserving @components.'); + ensure(docs.parity.includes('{% include "project_machine_name:component_name" %}') && docs.parity.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Sister-project parity doc should promote generic project machine-name component includes while preserving @components compatibility.'); ensure(docs.architecture.includes('The parent theme owns reusable runtime behavior'), 'Architecture doc should explain parent responsibilities.'); ensure(docs.architecture.includes('@emulsify-tpl'), 'Architecture doc should document the parent-only template namespace.'); - ensure(docs.architecture.includes('{% include "whisk:button" %}') && docs.architecture.includes('The legacy `@components/button/button.twig` namespace remains supported'), 'Architecture doc should document project machine-name component includes while preserving @components.'); + ensure(docs.architecture.includes('{% include "project_machine_name:component_name" %}') && docs.architecture.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Architecture doc should document generic project machine-name component includes while preserving @components compatibility.'); ensure(docs.twig.includes('@templates') && docs.twig.includes('@components'), 'Twig doc should document core namespaces.'); - ensure(docs.twig.includes('For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`: `{% include "whisk:button" %}`'), 'Twig doc should prefer project machine-name component includes.'); + ensure(docs.twig.includes('Install or author project components in the structure defined by the selected Emulsify component system'), 'Twig doc should avoid prescribing a component source structure.'); ensure(docs.twig.includes('The general form is `project_machine_name:component_name`'), 'Twig doc should document the generic project component include form.'); - ensure(docs.twig.includes('The legacy `@components/button/button.twig` namespace remains supported for existing projects, shared templates, and migration work'), 'Twig doc should preserve @components support language.'); + ensure(docs.twig.includes('The legacy `@components/example-card/example-card.twig` namespace remains supported for compatible component libraries'), 'Twig doc should preserve @components compatibility language.'); ensure(docs.twig.includes('emulsify_theme_context'), 'Twig doc should document context extension.'); ensure(docs.workflow.includes('Core 4, Vite, and Storybook commands'), 'Workflow doc should use the expected command heading.'); ensure(docs.workflow.includes('"platform": "wordpress"'), 'Workflow doc should explain the WordPress platform adapter.'); - ensure(docs.workflow.includes('{% include "whisk:button" %}') && docs.workflow.includes('The legacy `@components/button/button.twig` namespace remains supported'), 'Core 4 workflow doc should promote project machine-name component includes while preserving @components.'); - ensure(docs.acfBlocks.includes('The starter button includes an active example metadata file'), 'ACF/Twig blocks doc should explain the starter button metadata.'); + ensure(docs.workflow.includes('does not ship a concrete component library'), 'Core 4 workflow doc should describe Whisk as component-system agnostic.'); + ensure(docs.workflow.includes('The following shape is an example of a compatible component, not files shipped by Whisk'), 'Core 4 workflow doc should keep component examples documentation-only.'); + ensure(docs.workflow.includes('{% include "project_machine_name:component_name" %}') && docs.workflow.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Core 4 workflow doc should promote generic project machine-name component includes while preserving @components compatibility.'); + ensure(docs.acfJson.includes('config/acf-json'), 'ACF Local JSON doc should document the child theme JSON path.'); + ensure(docs.acfJson.includes('emulsify_theme_acf_json_save_path'), 'ACF Local JSON doc should document the save path filter.'); + ensure(docs.acfJson.includes('commit ACF JSON files'), 'ACF Local JSON doc should tell project teams to commit ACF JSON.'); + ensure(docs.acfBlocks.includes('Whisk does not include an active ACF/Twig block example'), 'ACF/Twig blocks doc should explain that starter metadata is documentation-only.'); ensure(docs.acfBlocks.includes('emulsify_theme_acf_block_args'), 'ACF/Twig blocks doc should document the block args filter.'); ensure(docs.nativeBlocks.includes('The starter does not include an active native block example'), 'Native blocks doc should avoid over-claiming a native example.'); ensure(docs.nativeBlocks.includes('emulsify_theme_native_block_directories'), 'Native blocks doc should document the native block directories filter.'); + ensure(docs.blockPatterns.includes('patterns/*.json'), 'Block patterns doc should document JSON pattern discovery.'); + ensure(docs.blockPatterns.includes('emulsify_theme_pattern_directories'), 'Block patterns doc should document directory filtering.'); + ensure(docs.blockPatterns.includes('emulsify_theme_pattern_args'), 'Block patterns doc should document final args filtering.'); + ensure(docs.editorEnhancements.includes('emulsify_theme_editor_enhancements_config'), 'Editor enhancements doc should document the config filter.'); + ensure(docs.editorEnhancements.includes('columnsEqualHeight'), 'Editor enhancements doc should document the columns module.'); + ensure(docs.editorEnhancements.includes('fileCaption'), 'Editor enhancements doc should document the file caption module.'); + ensure(docs.editorEnhancements.includes('embedVariations'), 'Editor enhancements doc should document the embed variation module.'); + ensure(docs.editorEnhancements.includes('placement'), 'Editor enhancements doc should document the placement module.'); + ensure(docs.editorPolicy.includes('emulsify_theme_editor_policy_options'), 'Editor policy doc should document the options filter.'); + ensure(docs.editorPolicy.includes('auto_allow_pattern_blocks'), 'Editor policy doc should document pattern JSON auto-allow behavior.'); + ensure(docs.editorPolicy.includes('emulsify_theme_block_support_overrides'), 'Editor policy doc should document block support overrides.'); ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); + ensure(docs.cli.includes('Ignored dependency, cache, Storybook, and Vite output directories are not copied'), 'WP-CLI doc should explain that generated themes do not inherit build output.'); ensure(docs.release.includes('release-2.x') && docs.release.includes('2.0.0'), 'Release process doc should document the release-2.x target release.'); ensure(docs.release.includes('WordPress Theme Readiness workflow'), 'Release process doc should document the theme readiness workflow.'); ensure(docs.release.includes('Manual and scheduled runs execute the full WordPress fixture smoke test'), 'Release process doc should explain when the full fixture runs.'); diff --git a/.github/scripts/theme-filters-smoke.php b/.github/scripts/theme-filters-smoke.php index bf9e5a4..91f24d2 100644 --- a/.github/scripts/theme-filters-smoke.php +++ b/.github/scripts/theme-filters-smoke.php @@ -297,6 +297,7 @@ function emulsify_filter_smoke_remove( string $path ): void { try { emulsify_filter_smoke_write( $child . '/dist/global/child.css', 'body { color: black; }' ); + emulsify_filter_smoke_write( $child . '/dist/global/editor/css/index.css', '.editor-only { display: block; }' ); emulsify_filter_smoke_write( $extra . '/assets/extra.css', 'body { color: red; }' ); emulsify_filter_smoke_write( $extra . '/twig/example.twig', 'Example' ); emulsify_filter_smoke_write( $extra . '/components/filter-card/filter-card.component.json', '{"title":"Filter Card"}' ); @@ -440,6 +441,17 @@ static function ( array $directories ) use ( $extra ): array { isset( $GLOBALS['emulsify_filter_smoke_styles']['emulsify-global-filtered'] ), 'Asset files filter should add a filtered CSS asset.' ); + emulsify_filter_smoke_assert( + empty( + array_filter( + array_keys( $GLOBALS['emulsify_filter_smoke_styles'] ), + static function ( string $handle ): bool { + return false !== strpos( $handle, 'editor' ); + } + ) + ), + 'Generic asset loading should skip editor-only global assets.' + ); $loader = new class() { public $paths = array(); diff --git a/.github/scripts/twig-project-namespace-smoke.php b/.github/scripts/twig-project-namespace-smoke.php index a32567c..2b5b92c 100644 --- a/.github/scripts/twig-project-namespace-smoke.php +++ b/.github/scripts/twig-project-namespace-smoke.php @@ -202,11 +202,11 @@ function emulsify_twig_project_smoke_render( \Twig\Environment $environment, str try { emulsify_twig_project_smoke_write( $child . '/project.emulsify.json', '{"project":{"machineName":"whisk"}}' ); - emulsify_twig_project_smoke_write( $child . '/src/components/button/button.twig', 'Child button' ); + emulsify_twig_project_smoke_write( $child . '/src/components/example-card/example-card.twig', 'Child example card' ); emulsify_twig_project_smoke_write( $child . '/src/components/badge.twig', 'Child badge' ); emulsify_twig_project_smoke_write( $child . '/src/components/ui/heading/heading.twig', 'Child heading' ); emulsify_twig_project_smoke_write( $child . '/components/link/link.twig', 'Child legacy link' ); - emulsify_twig_project_smoke_write( $parent . '/src/components/button/button.twig', 'Parent button' ); + emulsify_twig_project_smoke_write( $parent . '/src/components/example-card/example-card.twig', 'Parent example card' ); emulsify_twig_project_smoke_write( $extra . '/filtered/filtered.twig', 'Filtered root' ); add_filter( @@ -225,14 +225,14 @@ static function ( array $roots, string $machine_name ) use ( $extra ): array { $environment = emulsify_twig_project_smoke_environment( $child, $parent ); emulsify_twig_project_smoke_assert_same( - 'Child button', - emulsify_twig_project_smoke_render( $environment, '{% include "whisk:button" %}' ), + 'Child example card', + emulsify_twig_project_smoke_render( $environment, '{% include "whisk:example-card" %}' ), 'machineName:component should resolve to the child src component template.' ); emulsify_twig_project_smoke_assert_same( - 'Child button', - emulsify_twig_project_smoke_render( $environment, '{% include "@components/button/button.twig" %}' ), + 'Child example card', + emulsify_twig_project_smoke_render( $environment, '{% include "@components/example-card/example-card.twig" %}' ), '@components should still resolve child components.' ); @@ -261,33 +261,33 @@ static function ( array $roots, string $machine_name ) use ( $extra ): array { ); emulsify_twig_project_smoke_assert( - false === $environment->getLoader()->exists( 'whisk:../button' ), + false === $environment->getLoader()->exists( 'whisk:../example-card' ), 'Unsafe project component references should be rejected.' ); - emulsify_twig_project_smoke_write( $missing_child . '/src/components/button/button.twig', 'Missing project button' ); + emulsify_twig_project_smoke_write( $missing_child . '/src/components/example-card/example-card.twig', 'Missing project example card' ); $missing_environment = emulsify_twig_project_smoke_environment( $missing_child, $parent ); emulsify_twig_project_smoke_assert_same( - 'Missing project button', - emulsify_twig_project_smoke_render( $missing_environment, '{% include "@components/button/button.twig" %}' ), + 'Missing project example card', + emulsify_twig_project_smoke_render( $missing_environment, '{% include "@components/example-card/example-card.twig" %}' ), 'Missing project.emulsify.json should not break @components.' ); emulsify_twig_project_smoke_assert( - false === $missing_environment->getLoader()->exists( 'whisk:button' ), + false === $missing_environment->getLoader()->exists( 'whisk:example-card' ), 'Missing project.emulsify.json should not register a project component loader.' ); emulsify_twig_project_smoke_write( $invalid_child . '/project.emulsify.json', '{"project":' ); - emulsify_twig_project_smoke_write( $invalid_child . '/src/components/button/button.twig', 'Invalid project button' ); + emulsify_twig_project_smoke_write( $invalid_child . '/src/components/example-card/example-card.twig', 'Invalid project example card' ); $invalid_environment = emulsify_twig_project_smoke_environment( $invalid_child, $parent ); emulsify_twig_project_smoke_assert_same( - 'Invalid project button', - emulsify_twig_project_smoke_render( $invalid_environment, '{% include "@components/button/button.twig" %}' ), + 'Invalid project example card', + emulsify_twig_project_smoke_render( $invalid_environment, '{% include "@components/example-card/example-card.twig" %}' ), 'Invalid project.emulsify.json should not break @components.' ); diff --git a/.github/scripts/wordpress-fixture-smoke.cjs b/.github/scripts/wordpress-fixture-smoke.cjs index 08c05b8..bccc4f9 100755 --- a/.github/scripts/wordpress-fixture-smoke.cjs +++ b/.github/scripts/wordpress-fixture-smoke.cjs @@ -232,16 +232,9 @@ function ensureWhiskBuild() { run('Build Whisk child theme assets', 'npm', ['run', 'build'], { cwd: whiskDir }); - for (const file of ['foundation.css', 'layout.css', 'tokens.css']) { - const builtFile = path.join(whiskDir, 'dist', 'global', file); - if (!fs.existsSync(builtFile)) { - throw new Error(`Expected built Whisk global CSS is missing: ${builtFile}`); - } - } - - const buttonCss = path.join(whiskDir, 'dist', 'components', 'button', 'css', 'button.css'); - if (!fs.existsSync(buttonCss)) { - throw new Error(`Expected built Whisk button CSS is missing: ${buttonCss}`); + const editorAsset = path.join(whiskDir, 'dist', 'global', 'editor', 'js', 'index.js'); + if (!fs.existsSync(editorAsset)) { + throw new Error(`Expected built Whisk editor asset is missing: ${editorAsset}`); } } @@ -256,12 +249,10 @@ function assertGeneratedChildTheme(themePath, slug) { const project = JSON.parse(fs.readFileSync(path.join(themePath, 'project.emulsify.json'), 'utf8')); const packageJson = JSON.parse(fs.readFileSync(path.join(themePath, 'package.json'), 'utf8')); const requiredFiles = [ - 'dist/global/foundation.css', - 'dist/global/layout.css', - 'dist/global/tokens.css', - 'dist/components/button/button.component.json', - 'dist/components/button/button.twig', - 'dist/components/button/css/button.css', + 'config/acf-json/.gitkeep', + 'patterns/.gitkeep', + 'src/components/.gitkeep', + 'src/editor/index.js', 'templates/page.twig', ]; @@ -287,6 +278,53 @@ function assertGeneratedChildTheme(themePath, slug) { throw new Error(`Generated child theme is missing expected Whisk file: ${file}`); } } + + for (const file of ['src/foundation.scss', 'src/layout.scss', 'src/tokens.scss']) { + if (fs.existsSync(path.join(themePath, file))) { + throw new Error(`Generated child theme should not include assumed Sass entrypoint: ${file}`); + } + } + + for (const directory of ['dist', '.out']) { + if (fs.existsSync(path.join(themePath, directory))) { + throw new Error(`Generated child theme should not copy ignored build output directory: ${directory}`); + } + } +} + +function installGeneratedChildFixtures(themePath) { + const globalDirectory = path.join(themePath, 'dist', 'global'); + const acfDirectory = path.join(themePath, 'dist', 'components', 'smoke-acf'); + + fs.mkdirSync(globalDirectory, { recursive: true }); + fs.mkdirSync(path.join(acfDirectory, 'css'), { recursive: true }); + fs.writeFileSync( + path.join(globalDirectory, 'smoke.css'), + '.smoke-generated-global { color: inherit; }\n' + ); + fs.writeFileSync( + path.join(acfDirectory, 'css', 'smoke-acf.css'), + '.smoke-acf { display: block; }\n' + ); + fs.writeFileSync( + path.join(acfDirectory, 'smoke-acf.twig'), + '
    {{ title|default("Smoke ACF") }}
    \n' + ); + fs.writeFileSync( + path.join(acfDirectory, 'smoke-acf.component.json'), + JSON.stringify( + { + name: 'emulsify-smoke-acf', + title: 'Smoke ACF', + description: 'Fixture component for WordPress smoke coverage.', + category: 'widgets', + icon: 'admin-generic', + keywords: ['smoke'], + }, + null, + 2 + ) + '\n' + ); } function installNativeBlockFixture(themePath) { @@ -355,21 +393,21 @@ if ( function_exists( 'acf_register_block_type' ) ) { $locator = new \Emulsify\Theme\Blocks\Component_Locator(); $components = $locator->acf_components(); -$button = null; +$fixture = null; foreach ( $components as $component ) { - if ( isset( $component['relative'], $component['source'] ) && 'button' === $component['relative'] && 'child' === $component['source'] ) { - $button = $component; + if ( isset( $component['relative'], $component['source'] ) && 'smoke-acf' === $component['relative'] && 'child' === $component['source'] ) { + $fixture = $component; break; } } -if ( null === $button ) { - emulsify_smoke_fail( 'Generated child ACF/Twig button component was not discovered without ACF.' ); +if ( null === $fixture ) { + emulsify_smoke_fail( 'Generated child ACF/Twig fixture component was not discovered without ACF.' ); } -if ( 'dist/components/button/button.twig' !== $button['template'] ) { - emulsify_smoke_fail( 'Generated child ACF/Twig button template was not resolved.' ); +if ( 'dist/components/smoke-acf/smoke-acf.twig' !== $fixture['template'] ) { + emulsify_smoke_fail( 'Generated child ACF/Twig fixture template was not resolved.' ); } ( new \Emulsify\Theme\Blocks\Acf_Blocks( $locator ) )->register_blocks(); @@ -394,25 +432,25 @@ do_action( 'acf/init' ); $registered = isset( $GLOBALS['emulsify_smoke_acf_registered'] ) && is_array( $GLOBALS['emulsify_smoke_acf_registered'] ) ? $GLOBALS['emulsify_smoke_acf_registered'] : array(); -$button = null; +$fixture = null; foreach ( $registered as $block ) { - if ( isset( $block['name'] ) && 'emulsify-example-button' === $block['name'] ) { - $button = $block; + if ( isset( $block['name'] ) && 'emulsify-smoke-acf' === $block['name'] ) { + $fixture = $block; break; } } -if ( null === $button ) { - emulsify_smoke_fail( 'Generated child ACF/Twig button block was not registered with the ACF stub.' ); +if ( null === $fixture ) { + emulsify_smoke_fail( 'Generated child ACF/Twig fixture block was not registered with the ACF stub.' ); } -if ( 'dist/components/button/button.twig' !== $button['twig_template'] ) { - emulsify_smoke_fail( 'Generated child ACF/Twig block did not keep its Twig template.' ); +if ( 'dist/components/smoke-acf/smoke-acf.twig' !== $fixture['twig_template'] ) { + emulsify_smoke_fail( 'Generated child ACF/Twig fixture did not keep its Twig template.' ); } -if ( empty( $button['data']['twig_template'] ) || 'dist/components/button/button.twig' !== $button['data']['twig_template'] ) { - emulsify_smoke_fail( 'Generated child ACF/Twig block data did not include its Twig template.' ); +if ( empty( $fixture['data']['twig_template'] ) || 'dist/components/smoke-acf/smoke-acf.twig' !== $fixture['data']['twig_template'] ) { + emulsify_smoke_fail( 'Generated child ACF/Twig fixture data did not include its Twig template.' ); } `; @@ -613,10 +651,8 @@ async function checkGeneratedAsset(baseUrl, themeSlug, relativePath, expectedTex } async function checkGeneratedAssets(baseUrl, themeSlug) { - await checkGeneratedAsset(baseUrl, themeSlug, 'dist/global/foundation.css', 'box-sizing'); - await checkGeneratedAsset(baseUrl, themeSlug, 'dist/global/layout.css', 'max-width'); - await checkGeneratedAsset(baseUrl, themeSlug, 'dist/global/tokens.css', '--emulsify-color-text'); - await checkGeneratedAsset(baseUrl, themeSlug, 'dist/components/button/css/button.css', '.button'); + await checkGeneratedAsset(baseUrl, themeSlug, 'dist/global/smoke.css', '.smoke-generated-global'); + await checkGeneratedAsset(baseUrl, themeSlug, 'dist/components/smoke-acf/css/smoke-acf.css', '.smoke-acf'); } async function renderRoutes(wpPath, content, baseUrl, port, themeSlug) { @@ -627,8 +663,8 @@ async function renderRoutes(wpPath, content, baseUrl, port, themeSlug) { 'Smoke Page', 'Smoke page content', `${themeSlug}-page`, - `/wp-content/themes/${themeSlug}/dist/global/foundation.css`, - `/wp-content/themes/${themeSlug}/dist/components/button/css/button.css`, + `/wp-content/themes/${themeSlug}/dist/global/smoke.css`, + `/wp-content/themes/${themeSlug}/dist/components/smoke-acf/css/smoke-acf.css`, ]; const routes = [ { name: 'home', path: '/', includes: ['Emulsify Smoke', 'Smoke Post'] }, @@ -734,6 +770,7 @@ async function main() { wp(wpPath, ['theme', 'activate', 'whisk']); wp(wpPath, ['emulsify', 'Smoke Generated', `--machine-name=${generatedChildSlug}`, '--activate']); assertGeneratedChildTheme(generatedChild, generatedChildSlug); + installGeneratedChildFixtures(generatedChild); wp(wpPath, ['theme', 'is-installed', generatedChildSlug]); const activeStylesheet = wp(wpPath, ['option', 'get', 'stylesheet']); diff --git a/README.md b/README.md index 38e75ba..e0264d0 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ The `emulsify` parent theme owns reusable runtime behavior: The generated `whisk` child theme owns project implementation: -- `whisk/src/components` is the primary component source directory. -- `whisk/src/tokens.scss`, `whisk/src/foundation.scss`, and `whisk/src/layout.scss` are the Core 4 global style entry points. +- `whisk/src/components` is an empty placeholder until a project installs the component system it wants to use. +- Source Sass, JavaScript, stories, data fixtures, and component metadata are defined by the selected Emulsify component system, not by this parent theme starter. - `whisk/templates/page.twig` is a small example override. -- `whisk/dist/global` and `whisk/dist/components` contain built assets after running Vite. +- `whisk/dist/global` and `whisk/dist/components` are runtime build output conventions when a component system emits them. - `whisk/project.emulsify.json` uses `"platform": "wordpress"` so Core and CLI tooling can load the WordPress platform adapter. ## Bedrock and Timber @@ -121,8 +121,13 @@ The generator copies `emulsify/whisk` to a sibling child theme directory, update - [Parent and child theme architecture](docs/parent-child-architecture.md) - [Timber and Twig authoring](docs/timber-and-twig-authoring.md) - [Emulsify Core 4 and Vite workflow](docs/core-4-vite-workflow.md) +- [ACF Local JSON](docs/acf-local-json.md) - [ACF/Twig blocks](docs/acf-twig-blocks.md) +- [Core block Twig rendering](docs/core-block-twig-rendering.md) - [Native Gutenberg blocks](docs/native-gutenberg-blocks.md) +- [Block patterns](docs/block-patterns.md) +- [Editor enhancements](docs/editor-enhancements.md) +- [Editor policy](docs/editor-policy.md) - [Asset loading](docs/asset-loading.md) - [WP-CLI child theme generation](docs/wp-cli-child-theme-generation.md) - [Release process](docs/release-process.md) diff --git a/docs/acf-local-json.md b/docs/acf-local-json.md new file mode 100644 index 0000000..c06487a --- /dev/null +++ b/docs/acf-local-json.md @@ -0,0 +1,65 @@ +# ACF Local JSON + +The parent theme supports ACF Local JSON as an optional child-theme convention. When ACF is active and the active child theme contains `config/acf-json`, Emulsify: + +- uses `config/acf-json` as the ACF JSON save path; +- adds `config/acf-json` to ACF JSON load paths; +- keeps ACF's default load path unless a project explicitly removes it. + +If ACF is not active, or the child theme does not contain `config/acf-json`, the service exits without changing ACF settings. + +## Project Workflow + +Generated child themes include `config/acf-json/.gitkeep` so the directory exists before field groups are created. + +Project teams should commit ACF JSON files in this directory whenever field groups, option pages, post types, or taxonomies are managed through ACF. Treat these files as configuration: + +- Create or edit field groups in the WordPress admin. +- Let ACF write JSON to `config/acf-json`. +- Review the changed JSON files in Git before committing. +- Commit the JSON with the code that depends on those fields. +- Pull the latest code and use ACF's sync UI when another environment needs updates. + +Do not place project field-group JSON in the parent theme. The parent owns the convention; the child theme owns project configuration. + +## Filters + +Disable the integration: + +```php +add_filter( 'emulsify_theme_acf_json_enabled', '__return_false' ); +``` + +Change the save path: + +```php +add_filter( + 'emulsify_theme_acf_json_save_path', + function ( string $save_path, string $default_path ): string { + return get_stylesheet_directory() . '/config/acf-json'; + }, + 10, + 2 +); +``` + +Remove ACF's default load path only when a project wants one source of truth: + +```php +add_filter( 'emulsify_theme_acf_json_remove_default_load_path', '__return_true' ); +``` + +Alter final load paths: + +```php +add_filter( + 'emulsify_theme_acf_json_load_paths', + function ( array $paths, string $save_path, array $incoming_paths ): array { + $paths[] = WP_CONTENT_DIR . '/shared-acf-json'; + + return $paths; + }, + 10, + 3 +); +``` diff --git a/docs/acf-twig-blocks.md b/docs/acf-twig-blocks.md index 30d0a7a..cea08c9 100644 --- a/docs/acf-twig-blocks.md +++ b/docs/acf-twig-blocks.md @@ -13,15 +13,20 @@ The Twig template can match the metadata filename or the component directory nam ## Example -The starter button includes an active example metadata file: +Whisk does not include an active ACF/Twig block example. A project component system can add metadata like this and build it into `dist/components`: ```text -whisk/src/components/button/button.component.json +src/components/example-card/example-card.component.json ``` -After the child theme build, that metadata is available under `dist/components` and can be discovered by the parent theme. +After the child theme build, that metadata might be available at: -Remove or rename the example metadata file if the project should not register the starter button as an ACF block. +```text +dist/components/example-card/example-card.component.json +dist/components/example-card/example-card.twig +``` + +This documentation example proves the shape, but it does not ship project field groups or content model assumptions. ## Registration @@ -35,7 +40,7 @@ The final registration arguments can be adjusted with: add_filter( 'emulsify_theme_acf_block_args', function ( array $args, array $component ): array { - if ( 'button' === $component['relative'] ) { + if ( 'example-card' === $component['relative'] ) { $args['category'] = 'design'; } diff --git a/docs/asset-loading.md b/docs/asset-loading.md index 06e17c3..d043c69 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -11,6 +11,8 @@ The default built asset directories are: Global CSS is loaded from `dist/global`. Component CSS and JavaScript are loaded from `dist/components`. +`dist/global/editor` is reserved for block editor enhancement assets. Those files are loaded by the editor enhancement service on `enqueue_block_editor_assets` and are skipped by the generic global style loader so editor-only CSS is not sent to normal frontend visitors. + ## Enqueue behavior The parent theme enqueues styles for both the frontend and block editor previews through WordPress block asset hooks. Frontend component JavaScript is loaded from built component output. diff --git a/docs/block-patterns.md b/docs/block-patterns.md new file mode 100644 index 0000000..c3eac7b --- /dev/null +++ b/docs/block-patterns.md @@ -0,0 +1,101 @@ +# Block patterns + +The parent theme registers JSON block patterns from active child and parent theme `patterns` directories. Discovery is child-first and scans direct `patterns/*.json` files only, so a child theme can override a parent pattern file by using the same JSON filename. + +If no pattern directory exists, or if WordPress pattern registration functions are unavailable, the service exits without changing editor behavior. + +## JSON shape + +Each pattern JSON file should include: + +```json +{ + "name": "project/example-pattern", + "title": "Example Pattern", + "description": "Short editor-facing summary.", + "categories": ["starter"], + "keywords": ["example", "starter"], + "postTypes": ["page"], + "viewportWidth": 1200, + "content": "

    Pattern content.

    " +} +``` + +Required fields: + +- `name`: Pattern name in `namespace/slug` form. +- `title`: Editor-facing pattern title. +- `content`: Serialized block markup. + +Optional fields: + +- `description`: Editor-facing summary. +- `categories`: Pattern category slugs. The parent registers discovered categories when WordPress supports `register_block_pattern_category()`. +- `keywords`: Inserter search terms. +- `postTypes`: Post type slugs where the pattern should appear. +- `viewportWidth`: Preview width used by the inserter. + +Invalid JSON files, missing required fields, duplicate filenames, and duplicate pattern names are skipped safely. Diagnostics are logged only when `WP_DEBUG` is enabled. + +## Filters + +Use `emulsify_theme_pattern_directories` to add or replace scanned directories: + +```php +add_filter( + 'emulsify_theme_pattern_directories', + function ( array $directories ): array { + $directories[] = array( + 'path' => get_stylesheet_directory() . '/project-patterns', + 'source' => 'project', + ); + + return $directories; + } +); +``` + +Use `emulsify_theme_pattern_data` to alter decoded JSON before validation: + +```php +add_filter( + 'emulsify_theme_pattern_data', + function ( array $data, array $file ): array { + if ( 'landing.json' === $file['relative'] ) { + $data['title'] = 'Landing Page'; + } + + return $data; + }, + 10, + 2 +); +``` + +Use `emulsify_theme_pattern_categories` to alter discovered category registration args: + +```php +add_filter( + 'emulsify_theme_pattern_categories', + function ( array $categories ): array { + $categories['starter']['label'] = 'Starter Layouts'; + + return $categories; + } +); +``` + +Use `emulsify_theme_pattern_args` to alter final registration args before `register_block_pattern()` runs: + +```php +add_filter( + 'emulsify_theme_pattern_args', + function ( array $args ): array { + $args['keywords'][] = 'project'; + + return $args; + } +); +``` + +Generated child themes include an empty `patterns` placeholder, but no content patterns by default. If a project adds JSON patterns under `whisk/patterns`, the generator rewrites pattern names from the `whisk/` namespace to the generated child theme machine name. diff --git a/docs/core-4-vite-workflow.md b/docs/core-4-vite-workflow.md index 42ed5c0..3bb02f6 100644 --- a/docs/core-4-vite-workflow.md +++ b/docs/core-4-vite-workflow.md @@ -8,14 +8,14 @@ The generated child theme uses Emulsify Core 4, Vite, Storybook, Twig stories, S ## Source directories -The generated child theme uses: +The generated child theme intentionally does not ship a concrete component library. Emulsify CLI can install the component system a project chooses, and that system should own the source tree, Sass entrypoints, stories, and fixture data. -- `src/components` for component source. -- `src/tokens.scss` for design tokens. -- `src/foundation.scss` for base styles. -- `src/layout.scss` for layout styles. -- `dist/global` for built global CSS. -- `dist/components` for built component assets and block metadata. +Whisk keeps `src/components/.gitkeep` only as a placeholder for compatible systems. It does not include default `tokens.scss`, `foundation.scss`, or `layout.scss` files. + +The parent WordPress runtime has only two default build-output conventions: + +- `dist/global` for built global CSS and JavaScript. +- `dist/components` for built component assets, ACF/Twig block metadata, and native `block.json` metadata. ## Core 4, Vite, and Storybook commands @@ -34,22 +34,26 @@ Run these from the generated child theme directory: | `npm run a11y` | Build Storybook and run the Core accessibility check. | | `npm run test` | Run Jest with `--passWithNoTests` for starter projects. | -## Example component +## Documentation-only component example -The Whisk starter includes a minimal button component: +The following shape is an example of a compatible component, not files shipped by Whisk: ```text -whisk/src/components/button/ - button.twig - button.scss - button.data.json - button.stories.js - button.component.json +src/components/example-card/ + example-card.twig + example-card.scss + example-card.data.json + example-card.stories.js + example-card.component.json ``` -This is intentionally small. It demonstrates the workflow without trying to be a complete design system. +Project teams should add, rename, or remove component files according to the selected component system and project naming model. This parent theme does not require a button component or any specific foundation, layout, or token Sass files. + +When a component system builds `*.component.json` files into `dist/components`, the parent theme can discover those files for optional ACF/Twig block registration. Documentation examples prove the shape, but Whisk does not register any starter ACF/Twig blocks by default. + +For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`. The general form is `{% include "project_machine_name:component_name" %}`. The legacy `@components/component-name/component-name.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work. -For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`: `{% include "whisk:button" %}`. The legacy `@components/button/button.twig` namespace remains supported for existing projects, shared templates, and migration work. +Whisk keeps `patterns/.gitkeep` only as a placeholder. If a project adds JSON patterns, the child theme generator updates copied pattern namespaces from `whisk/*` to the generated machine name. ## Build output diff --git a/docs/core-block-twig-rendering.md b/docs/core-block-twig-rendering.md new file mode 100644 index 0000000..3a8be18 --- /dev/null +++ b/docs/core-block-twig-rendering.md @@ -0,0 +1,79 @@ +# Core block Twig rendering + +The parent theme includes an experimental `Core_Block_Twig_Renderer` service that can render explicitly mapped WordPress blocks through Twig templates. It is disabled by default and does not change frontend output unless a child theme or project plugin opts in. + +Use this carefully. Replacing core block output can affect block validation, style supports, accessibility attributes, plugin integrations, and future WordPress markup changes. Keep Twig output compatible with the saved block attributes and test editing, saving, reloading, and frontend rendering for every mapped block. + +## Enable the service + +Enable rendering and provide an explicit block-to-template map: + +```php +add_filter( 'emulsify_theme_core_block_twig_rendering_enabled', '__return_true' ); + +add_filter( + 'emulsify_theme_core_block_twig_template_map', + function ( array $map ): array { + $map['core/paragraph'] = 'dist/components/paragraph/paragraph.twig'; + $map['core/heading'] = 'dist/components/heading/heading.twig'; + + return $map; + } +); +``` + +Mapped template paths are child/parent theme relative. The active child theme is checked first, then the parent theme. If a block is not mapped, or the mapped Twig file is not readable, WordPress' original rendered block content is returned unchanged. + +## Twig context + +Mapped templates receive the normal Timber context plus these values: + +- `block`: parsed block metadata from `render_block`. +- `block_metadata`: alias for `block`. +- `block_name`: block name such as `core/paragraph`. +- `attributes`: parsed block attributes. +- `content`: WordPress' original rendered block content. +- `inner_content`: rendered inner content fallback for templates. +- `inner_blocks`: parsed inner block records when available. +- `wp_block`: the `WP_Block` instance when WordPress passes one. +- `twig_template`: the mapped Twig template. + +Use `emulsify_theme_core_block_twig_context` to add project-specific values: + +```php +add_filter( + 'emulsify_theme_core_block_twig_context', + function ( array $context, array $block ): array { + if ( 'core/heading' === ( $block['blockName'] ?? '' ) ) { + $context['heading_level'] = $context['attributes']['level'] ?? 2; + } + + return $context; + }, + 10, + 2 +); +``` + +## Optional class cleanup + +The renderer does not strip `wp-block` classes by default. If a project needs class cleanup, opt in separately: + +```php +add_filter( 'emulsify_theme_core_block_twig_cleanup_classes_enabled', '__return_true' ); +``` + +Cleanup uses `WP_HTML_Tag_Processor` when available and otherwise leaves Twig output unchanged. Use `emulsify_theme_core_block_twig_cleanup_class_names` to control exactly which classes are removed. + +## Error handling + +If a mapped template throws an exception or Timber is unavailable, normal frontend visitors receive WordPress' original block output. Diagnostics are only appended for users who can edit posts or edit theme options, and debug logging only runs when `WP_DEBUG` is enabled. + +## Filters + +- `emulsify_theme_core_block_twig_rendering_enabled` +- `emulsify_theme_core_block_twig_template_map` +- `emulsify_theme_core_block_twig_template` +- `emulsify_theme_core_block_twig_context` +- `emulsify_theme_core_block_twig_cleanup_classes_enabled` +- `emulsify_theme_core_block_twig_cleanup_class_names` diff --git a/docs/editor-enhancements.md b/docs/editor-enhancements.md new file mode 100644 index 0000000..25edc7d --- /dev/null +++ b/docs/editor-enhancements.md @@ -0,0 +1,81 @@ +# Editor enhancements + +The Whisk starter includes optional block editor enhancement modules under `whisk/src/editor`. The parent theme enqueues built editor assets from `dist/global/editor` when a child theme builds them, but every module is disabled by default. + +Enable modules from a child theme or project plugin with `emulsify_theme_editor_enhancements_config`: + +```php +add_filter( + 'emulsify_theme_editor_enhancements_config', + function ( array $config ): array { + $config['columnsEqualHeight']['enabled'] = true; + $config['fileCaption']['enabled'] = true; + $config['embedVariations']['enabled'] = true; + $config['placement'] = array( + 'enabled' => true, + 'blocks' => array( 'acf/project-hero' ), + 'singleton' => true, + 'requireTop' => true, + ); + + return $config; + } +); +``` + +Run the child theme build after changing editor source: + +```sh +npm --prefix web/app/themes/your-child-theme run build +``` + +## Columns equal height + +`columnsEqualHeight` adds an inspector toggle to `core/columns`. When enabled, it stores the configured boolean attribute and adds the configured class to saved markup and the editor preview. + +Useful options: + +- `enabled`: turns the module on. +- `attribute`: stored block attribute. Default: `emulsifyEqualizeHeights`. +- `className`: saved/editor class. Default: `is-equal-height`. +- `label`, `panelTitle`, `helpEnabled`, `helpDisabled`: editor control text. + +## File media captions + +`fileCaption` adds an inspector toggle to `core/file`. The JavaScript stores a boolean attribute, and the parent PHP render hook appends the media library caption during rendering when the toggle is enabled. + +Useful options: + +- `enabled`: turns the module on. +- `attribute`: stored block attribute. Default: `emulsifyShowMediaCaption`. +- `blockClassName`: class added to the parsed File block attributes. Default: `has-media-caption`. +- `captionClassName`: class used on the injected caption paragraph. Default: `wp-block-file__media-caption`. + +## Embed variations + +`embedVariations` keeps `core/embed` available while hiding provider-specific embed variations from the inserter. Existing variations are unregistered after editor boot because WordPress does not expose a public API for mutating variation scope after registration. + +Useful options: + +- `enabled`: turns the module on. +- `allowedVariationNames`: optional variation names to keep visible. + +## Singleton top placement + +`placement` enforces one configured top-level block, optionally moving it to the top of the post and removing duplicates. This is useful for project-specific hero, alert, or page-header blocks without hard-coding those names in the parent theme. + +Useful options: + +- `enabled`: turns the module on. +- `blocks`: block names that represent the singleton block. +- `singleton`: prevents duplicates and sets block support `multiple` to false. +- `requireTop`: moves the first configured block to the top level at index 0. +- `notice`: snackbar text shown when enforcement changes the block list. + +## Asset filters + +The parent service discovers child assets first, then parent fallbacks. These filters are available for projects with custom build locations: + +- `emulsify_theme_editor_enhancements_config` +- `emulsify_theme_editor_asset_directories` +- `emulsify_theme_editor_asset_files` diff --git a/docs/editor-policy.md b/docs/editor-policy.md new file mode 100644 index 0000000..184ef68 --- /dev/null +++ b/docs/editor-policy.md @@ -0,0 +1,108 @@ +# Editor policy + +The parent theme includes an `Editor_Policy` service for project-specific block editor governance. It is intentionally no-op by default, so activating the parent theme does not change allowed blocks, visible patterns, user-created pattern behavior, `wp_block` capabilities, or registered block supports unless a child theme or project plugin configures the policy with filters. + +## Configure policy options + +Use `emulsify_theme_editor_policy_options` from a child theme or project plugin: + +```php +add_filter( + 'emulsify_theme_editor_policy_options', + function ( array $options ): array { + $options['allowed_block_types'] = array( + 'default' => array( + 'core/heading', + 'core/paragraph', + 'core/image', + ), + 'by_post_type' => array( + 'landing_page' => array( + 'acf/project-hero', + 'acf/project-card-grid', + ), + ), + ); + + $options['auto_allow_pattern_blocks'] = true; + $options['pattern_namespaces'] = array( 'project' ); + $options['disable_user_patterns_for_non_admins'] = true; + $options['restrict_wp_block_creation'] = true; + $options['wp_block_create_capability'] = 'manage_options'; + $options['block_support_overrides'] = array( + '*' => array( + 'supports' => array( + 'styles' => false, + ), + ), + ); + + return $options; + } +); +``` + +`allowed_block_types` may be a single list for every post type, or an array with `default` and `by_post_type` keys. Post-type entries are merged with the default list. If WordPress or another plugin has already returned an allowed-block array, Emulsify merges with it by default; set `merge_allowed_block_types` to `false` to use only the configured list. + +When `auto_allow_pattern_blocks` is enabled, block names found in JSON files under `patterns/` are added to the configured allow list. Comment shorthand such as `` is normalized to `core/paragraph`. + +## Fine-tune allowed blocks + +Use `emulsify_theme_allowed_block_types` when the final list needs request-aware logic: + +```php +add_filter( + 'emulsify_theme_allowed_block_types', + function ( ?array $blocks, string $post_type ): ?array { + if ( 'event' === $post_type ) { + $blocks[] = 'core/embed'; + } + + return $blocks; + }, + 10, + 2 +); +``` + +Return `null` to keep WordPress default behavior for that editor context. + +## Limit visible patterns + +Use `emulsify_theme_pattern_namespaces` to keep only project-owned pattern namespaces in the inserter: + +```php +add_filter( + 'emulsify_theme_pattern_namespaces', + function ( array $namespaces ): array { + $namespaces[] = 'project'; + + return $namespaces; + } +); +``` + +If no namespaces are configured, Emulsify leaves the pattern list untouched. + +## Override block supports and styles + +Use `emulsify_theme_block_support_overrides` or the `block_support_overrides` option to change supports/styles for all blocks or a specific block. Overrides run for both `block_type_metadata_settings` and `register_block_type_args`. + +```php +add_filter( + 'emulsify_theme_block_support_overrides', + function ( array $overrides, string $block_name, array $settings, string $source ): array { + $overrides['*']['supports']['styles'] = false; + + if ( 'core/button' === $block_name && 'register_block_type_args' === $source ) { + $overrides['core/button']['styles'] = array(); + } + + return $overrides; + }, + 10, + 4 +); +``` + +Keep role and capability grants in the child theme or a project plugin. The parent theme can point `wp_block` creation at a capability, but it does not mutate site roles. diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index 99bfaf2..3447680 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -11,8 +11,11 @@ The parent theme owns reusable runtime behavior: - Timber integration and global context. - Twig namespace registration and helper functions. - Asset loading for built child and parent files. +- Optional ACF Local JSON save/load path support. - Optional ACF/Twig block registration. - Optional native Gutenberg block registration. +- Optional JSON block pattern registration. +- Optional block editor governance when configured by a child theme. - Minimal Timber fallback templates. Most parent runtime code lives in `includes/`. The root `functions.php` should stay thin. @@ -21,8 +24,8 @@ Most parent runtime code lives in `includes/`. The root `functions.php` should s Generated child themes own: -- Project components. -- Project Sass and JavaScript. +- Project components in the structure supplied by the selected Emulsify component system. +- Project Sass and JavaScript defined by that component system or by the project. - Storybook stories and data fixtures. - Built Vite output under `dist/`. - Intentional template overrides. @@ -47,7 +50,7 @@ Built child theme components are discovered before built parent theme components Discovery is memoized for the current PHP request. ACF/Twig and native block registration share one filesystem scan without adding persistent cache invalidation problems. -For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`: `{% include "whisk:button" %}`. The legacy `@components/button/button.twig` namespace remains supported for existing projects, shared templates, and migration work. +For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`, using the general form `{% include "project_machine_name:component_name" %}`. The legacy `@components/component-name/component-name.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work. ## Runtime filters @@ -58,11 +61,23 @@ Child themes and project plugins can extend parent behavior with focused WordPre - `emulsify_theme_twig_namespaces` - `emulsify_theme_project_component_roots` - `emulsify_theme_context` +- `emulsify_theme_acf_json_enabled` +- `emulsify_theme_acf_json_save_path` +- `emulsify_theme_acf_json_load_paths` +- `emulsify_theme_acf_json_remove_default_load_path` - `emulsify_theme_component_roots` - `emulsify_theme_acf_block_metadata` - `emulsify_theme_acf_block_args` - `emulsify_theme_native_block_directories` +- `emulsify_theme_pattern_directories` +- `emulsify_theme_pattern_data` +- `emulsify_theme_pattern_categories` +- `emulsify_theme_pattern_args` - `emulsify_theme_setup_options` +- `emulsify_theme_editor_policy_options` +- `emulsify_theme_allowed_block_types` +- `emulsify_theme_pattern_namespaces` +- `emulsify_theme_block_support_overrides` Use these filters for project-specific behavior before editing a parent class. Keep broad application logic in a project plugin when it is not theme-specific. diff --git a/docs/release-process.md b/docs/release-process.md index 6fba4ba..4851008 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -29,6 +29,7 @@ npm run publish-test -- --no-ci - Composer metadata validation. - Composer dependency install for Twig smoke coverage. - PHP linting. +- ACF Local JSON smoke test. - Twig attribute helper smoke test. - Child theme generator smoke test. - Component locator smoke test. @@ -36,7 +37,7 @@ npm run publish-test -- --no-ci - Whisk dependency installation. - Whisk Core 4/Vite build. -`release:check` adds static release-readiness checks and the full WordPress fixture smoke path. The fixture installs the parent theme in an isolated WordPress site, generates and activates a child theme from Whisk, renders frontend routes through Timber, fetches built Whisk assets, and checks ACF/Twig and native `block.json` discovery. It skips gracefully when WP-CLI or database settings are unavailable unless `WP_SMOKE_REQUIRED=1` is set. +`release:check` adds static release-readiness checks and the full WordPress fixture smoke path. The fixture installs the parent theme in an isolated WordPress site, generates and activates a child theme from Whisk, adds neutral built asset and block fixtures, renders frontend routes through Timber, fetches those built child assets, and checks ACF/Twig and native `block.json` discovery. It skips gracefully when WP-CLI or database settings are unavailable unless `WP_SMOKE_REQUIRED=1` is set. ## CI diff --git a/docs/sister-project-parity.md b/docs/sister-project-parity.md index 499cf64..2155d90 100644 --- a/docs/sister-project-parity.md +++ b/docs/sister-project-parity.md @@ -21,11 +21,11 @@ The parent runtime should stay reusable and predictable. It provides CMS integra ## Generated source and assets -Generated child themes are expected to own component source and built output: +Generated child themes are expected to own component source and built output. The selected Emulsify component system defines the project source structure: -- `src/components` is the primary component source directory. -- `src/tokens.scss`, `src/foundation.scss`, and `src/layout.scss` are the Core 4 global style entry points. -- Storybook stories and data fixtures live with the component source. +- Whisk keeps an empty `src/components` placeholder for compatible systems, but it does not prescribe a component tree. +- Whisk does not ship default `tokens.scss`, `foundation.scss`, or `layout.scss` entrypoints. +- Storybook stories and data fixtures live with the component source chosen by the project. - Built global assets are emitted under `dist/global`. - Built component assets and block metadata are emitted under `dist/components`. @@ -39,7 +39,7 @@ Emulsify WordPress is not a Drupal runtime port. It keeps parity at the Emulsify Frontend rendering uses Timber. The parent theme owns Timber bootstrapping, global context, Twig namespace registration, route fallbacks, and Twig helper functions. Child themes supply project Twig templates and component source. -For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`, such as `{% include "whisk:button" %}`. The legacy `@components/button/button.twig` namespace remains supported for existing projects, shared templates, and migration work. +For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`, using the general form `{% include "project_machine_name:component_name" %}`. The legacy `@components/component-name/component-name.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work. ### Parent and child theme headers diff --git a/docs/timber-and-twig-authoring.md b/docs/timber-and-twig-authoring.md index 6f89234..92ac6ba 100644 --- a/docs/timber-and-twig-authoring.md +++ b/docs/timber-and-twig-authoring.md @@ -37,38 +37,38 @@ Use `@templates` for normal includes. Use `@emulsify-tpl` when a child override ## Component includes -Author project components under `src/components` in the generated child theme: +Install or author project components in the structure defined by the selected Emulsify component system. For compatible component roots, new project includes can use the generated child theme machine name from `project.emulsify.json`: ```twig -{% include "whisk:button" with { - text: post.title +{% include "project_machine_name:example-card" with { + heading: post.title } only %} ``` -For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`: `{% include "whisk:button" %}`. The general form is `project_machine_name:component_name`. +The general form is `project_machine_name:component_name`. -This reference resolves child-first component roots, including `src/components/button/button.twig`, `src/components/button.twig`, `components/button/button.twig`, and `components/button.twig`. One-level grouped names such as `whisk:ui/heading` resolve to paths like `src/components/ui/heading/heading.twig`. +This reference resolves child-first compatibility roots, including `src/components/example-card/example-card.twig`, `src/components/example-card.twig`, `components/example-card/example-card.twig`, and `components/example-card.twig`. One-level grouped names such as `project_machine_name:ui/heading` resolve to paths like `src/components/ui/heading/heading.twig`. -The legacy `@components/button/button.twig` namespace remains supported for existing projects, shared templates, and migration work: +The legacy `@components/example-card/example-card.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work: ```twig -{% include "@components/button/button.twig" with { - text: post.title +{% include "@components/example-card/example-card.twig" with { + heading: post.title } only %} ``` Use `emulsify_theme_project_component_roots` to adjust roots for `project_machine_name:component_name` references. Use `emulsify_theme_twig_namespaces` for normal `@namespace/path.twig` paths. -Root-level `components` directories remain a compatibility path for older projects. New work should use `src/components`. Do not remove existing `@components` includes during migration; both forms are supported. +Root-level `components` and `src/components` directories are compatibility paths, not starter requirements. Do not remove existing `@components` includes during migration; both forms are supported. ## Attribute helpers The parent theme registers Core-style helpers for class and attribute handling: ```twig - +
    + {{ content }} +
    {{ content }} diff --git a/docs/upgrading-1x-to-2x.md b/docs/upgrading-1x-to-2x.md index 0fdd494..ff6031e 100644 --- a/docs/upgrading-1x-to-2x.md +++ b/docs/upgrading-1x-to-2x.md @@ -7,6 +7,7 @@ Emulsify WordPress 2.x changes the project model. The repository is now a Timber - The parent theme owns WordPress runtime behavior in `includes/`. - The parent theme owns minimal Timber fallback templates in `templates/`. - Generated child themes own project components, template overrides, source Sass and JavaScript, and built assets. +- The selected Emulsify component system defines the component source structure; Whisk does not ship default foundation, layout, token, or button files. - The starter child theme uses Emulsify Core 4, Vite, Storybook, and Twig. - The generated child theme keeps `project.emulsify.json` set to `"platform": "wordpress"` for Emulsify Core and Emulsify CLI WordPress adapter support. - Release metadata is aligned around the `2.0.0` stable release. @@ -28,7 +29,7 @@ Do not move everything into the parent theme. The parent should stay reusable; p 1. Install the 2.x parent theme as `emulsify`. 2. Generate or update a child theme from the Whisk starter. -3. Move project components into the child theme `src/components` directory. +3. Install or configure the chosen Emulsify component system, then move project components into that system's source structure. 4. Move only intentional template overrides into the child theme `templates` directory. 5. Rebuild child theme assets with Vite. 6. Confirm WordPress is activating the child theme, not the parent. diff --git a/docs/wp-cli-child-theme-generation.md b/docs/wp-cli-child-theme-generation.md index 65c7e8a..05d4b97 100644 --- a/docs/wp-cli-child-theme-generation.md +++ b/docs/wp-cli-child-theme-generation.md @@ -35,6 +35,8 @@ The generator copies `emulsify/whisk` to a sibling child theme directory and upd The generator avoids broad blind string replacement. It targets known metadata files and starter labels. +Ignored dependency, cache, Storybook, and Vite output directories are not copied. A generated child theme should install its own dependencies, install or configure the chosen Emulsify component system, and create its own `dist` output. + ## After generation Install dependencies and build assets in the generated child theme: diff --git a/includes/class-acf-local-json.php b/includes/class-acf-local-json.php new file mode 100644 index 0000000..26c8c82 --- /dev/null +++ b/includes/class-acf-local-json.php @@ -0,0 +1,200 @@ +acf_available() ) { + return; + } + + add_filter( 'acf/settings/save_json', array( $this, 'save_path' ) ); + add_filter( 'acf/settings/load_json', array( $this, 'load_paths' ) ); + } + + /** + * Filters the ACF JSON save path. + * + * @param string $path Incoming ACF save path. + * @return string Filtered save path. + */ + public function save_path( string $path ): string { + if ( ! $this->enabled() ) { + return $path; + } + + $save_path = $this->configured_save_path(); + + return '' === $save_path ? $path : $save_path; + } + + /** + * Filters ACF JSON load paths. + * + * @param array $paths Incoming ACF load paths. + * @return array Filtered load paths. + */ + public function load_paths( array $paths ): array { + if ( ! $this->enabled() ) { + return $paths; + } + + $save_path = $this->configured_save_path(); + + if ( '' === $save_path ) { + return $paths; + } + + $load_paths = $paths; + + /** + * Filters whether ACF's default load path should be removed. + * + * Defaults to false so the parent theme does not remove ACF's built-in + * `acf-json` path unless a project explicitly opts into that behavior. + * + * @param bool $remove_default Whether to remove the first incoming load path. + * @param string $save_path Configured Emulsify ACF JSON save path. + * @param array $paths Incoming ACF load paths. + */ + $remove_default = (bool) apply_filters( 'emulsify_theme_acf_json_remove_default_load_path', false, $save_path, $paths ); + + if ( $remove_default && isset( $load_paths[0] ) ) { + unset( $load_paths[0] ); + } + + $load_paths[] = $save_path; + $load_paths = $this->normalize_paths( $load_paths ); + + /** + * Filters final ACF JSON load paths. + * + * @param array $load_paths Filtered ACF JSON load paths. + * @param string $save_path Configured Emulsify ACF JSON save path. + * @param array $paths Incoming ACF load paths. + */ + $filtered = apply_filters( 'emulsify_theme_acf_json_load_paths', $load_paths, $save_path, $paths ); + + return is_array( $filtered ) ? $this->normalize_paths( $filtered ) : $load_paths; + } + + /** + * Checks whether the Local JSON integration is enabled. + * + * @return bool TRUE when enabled. + */ + private function enabled(): bool { + $default_path = $this->default_path(); + $enabled = '' !== $default_path && is_dir( $default_path ); + + /** + * Filters whether Emulsify should configure ACF Local JSON paths. + * + * Defaults to true only when the active child theme has a config/acf-json + * directory. + * + * @param bool $enabled Whether ACF Local JSON path support is enabled. + * @param string $default_path Default active child theme ACF JSON path. + */ + return (bool) apply_filters( 'emulsify_theme_acf_json_enabled', $enabled, $default_path ); + } + + /** + * Gets the configured ACF JSON save path. + * + * @return string Existing save path, or an empty string. + */ + private function configured_save_path(): string { + $default_path = $this->default_path(); + $save_path = $default_path; + + /** + * Filters the ACF JSON save path. + * + * @param string $save_path ACF JSON save path. + * @param string $default_path Default active child theme ACF JSON path. + */ + $filtered = apply_filters( 'emulsify_theme_acf_json_save_path', $save_path, $default_path ); + + if ( is_scalar( $filtered ) ) { + $save_path = (string) $filtered; + } + + $save_path = rtrim( $save_path, '/\\' ); + + return '' !== $save_path && is_dir( $save_path ) ? $save_path : ''; + } + + /** + * Gets the active child theme ACF JSON path. + * + * @return string Default path, or an empty string when unavailable. + */ + private function default_path(): string { + if ( ! function_exists( 'get_stylesheet_directory' ) ) { + return ''; + } + + return rtrim( get_stylesheet_directory(), '/\\' ) . '/config/acf-json'; + } + + /** + * Normalizes and deduplicates filesystem paths. + * + * @param array $paths Path candidates. + * @return array Normalized paths. + */ + private function normalize_paths( array $paths ): array { + $normalized = array(); + $seen = array(); + + foreach ( $paths as $path ) { + if ( ! is_scalar( $path ) ) { + continue; + } + + $path = rtrim( (string) $path, '/\\' ); + + if ( '' === $path ) { + continue; + } + + $key = realpath( $path ) ?: $path; + + if ( isset( $seen[ $key ] ) ) { + continue; + } + + $seen[ $key ] = true; + $normalized[] = $path; + } + + return $normalized; + } + + /** + * Checks whether ACF is available. + * + * @return bool TRUE when an ACF API marker exists. + */ + private function acf_available(): bool { + return function_exists( 'acf' ) + || function_exists( 'acf_get_setting' ) + || function_exists( 'acf_register_block_type' ) + || class_exists( '\ACF' ); + } +} diff --git a/includes/class-assets.php b/includes/class-assets.php index c2f1dae..dc7bf1f 100644 --- a/includes/class-assets.php +++ b/includes/class-assets.php @@ -117,6 +117,10 @@ private function asset_files( string $directory, array $extensions ): array { $relative = $this->relative_path( $root['path'], $file->getPathname() ); + if ( $this->is_reserved_editor_asset( $directory, $relative ) ) { + continue; + } + if ( isset( $seen[ $relative ] ) ) { continue; } @@ -247,6 +251,18 @@ private function relative_path( string $base_path, string $path ): string { return str_replace( '\\', '/', $relative ); } + /** + * Checks whether a built asset is reserved for editor-only loading. + * + * @param string $directory Theme-relative asset directory being scanned. + * @param string $relative Asset path relative to the built directory. + * @return bool TRUE when the editor service should own the asset. + */ + private function is_reserved_editor_asset( string $directory, string $relative ): bool { + return 'dist/global' === trim( $directory, '/\\' ) + && 0 === strpos( ltrim( $relative, '/\\' ), 'editor/' ); + } + /** * Gets a filemtime-based asset version. * diff --git a/includes/class-bootstrap.php b/includes/class-bootstrap.php index 9715e39..0b77e1c 100644 --- a/includes/class-bootstrap.php +++ b/includes/class-bootstrap.php @@ -50,6 +50,11 @@ private function register(): void { ( new Setup() )->register(); ( new Assets() )->register(); + ( new Acf_Local_JSON() )->register(); + ( new Core_Block_Twig_Renderer() )->register(); + ( new Editor_Enhancements() )->register(); + ( new Editor_Policy() )->register(); + ( new Patterns() )->register(); ( new Blocks\Registry() )->register(); ( new Cli() )->register(); @@ -84,6 +89,7 @@ private function load_vendor_autoload(): void { */ private function load_classes(): void { $files = array( + 'class-acf-local-json.php', 'class-attribute-bag.php', 'class-assets.php', 'Blocks/class-component-locator.php', @@ -92,7 +98,11 @@ private function load_classes(): void { 'Blocks/class-registry.php', 'class-cli.php', 'class-context.php', + 'class-core-block-twig-renderer.php', + 'class-editor-enhancements.php', + 'class-editor-policy.php', 'class-missing-timber.php', + 'class-patterns.php', 'class-setup.php', 'class-timber-integration.php', 'class-twig.php', diff --git a/includes/class-cli.php b/includes/class-cli.php index 33ed5e7..9687668 100644 --- a/includes/class-cli.php +++ b/includes/class-cli.php @@ -16,6 +16,9 @@ final class Cli { private const EXCLUDED_COPY_PATHS = array( '.git', + '.coverage', + '.out', + 'dist', 'node_modules', ); @@ -262,9 +265,52 @@ function ( string $contents ) use ( $machine_name ): string { } ); + $this->collect_pattern_updates( $updates, $root, $machine_name ); + return $updates; } + /** + * Adds starter pattern metadata updates. + * + * @param array $updates Update accumulator. + * @param string $root Theme root. + * @param string $machine_name Generated child theme machine name. + * @return void + */ + private function collect_pattern_updates( array &$updates, string $root, string $machine_name ): void { + $pattern_dir = $this->join_path( $root, 'patterns' ); + + if ( ! is_dir( $pattern_dir ) ) { + return; + } + + $files = glob( $pattern_dir . '/*.json' ); + + if ( ! is_array( $files ) ) { + return; + } + + sort( $files ); + + foreach ( $files as $path ) { + $relative = 'patterns/' . basename( $path ); + + $this->collect_json_update( + $updates, + $root, + $relative, + function ( array $data ) use ( $machine_name ): array { + if ( isset( $data['name'] ) && is_string( $data['name'] ) && 0 === strpos( $data['name'], self::STARTER_SLUG . '/' ) ) { + $data['name'] = $machine_name . '/' . substr( $data['name'], strlen( self::STARTER_SLUG ) + 1 ); + } + + return $data; + } + ); + } + } + /** * Adds a text file update when the callback changes the contents. * diff --git a/includes/class-core-block-twig-renderer.php b/includes/class-core-block-twig-renderer.php new file mode 100644 index 0000000..ddbe252 --- /dev/null +++ b/includes/class-core-block-twig-renderer.php @@ -0,0 +1,459 @@ +enabled() ) { + return; + } + + add_filter( 'render_block', array( $this, 'render_block' ), 20, 3 ); + } + + /** + * Renders a mapped block with Twig, falling back to WordPress output. + * + * @param string $block_content Rendered WordPress block content. + * @param array $block Parsed block metadata. + * @param mixed $instance WP_Block instance when available. + * @return string Rendered block content. + */ + public function render_block( string $block_content, array $block, $instance = null ): string { + $block_name = $this->block_name( $block ); + + if ( '' === $block_name ) { + return $block_content; + } + + $template = $this->template_for_block( $block_name, $block ); + + if ( '' === $template ) { + return $block_content; + } + + if ( ! class_exists( '\Timber\Timber' ) || ! method_exists( '\Timber\Timber', 'compile' ) ) { + return $this->render_error( $block_content, $this->translate( 'Timber is not available for mapped block rendering.', 'emulsify' ), $block, $template ); + } + + try { + $context = $this->context( $block, $block_content, $instance, $template ); + $html = \Timber\Timber::compile( $template, $context ); + } catch ( \Throwable $throwable ) { + return $this->render_error( $block_content, $throwable->getMessage(), $block, $template ); + } + + if ( ! is_string( $html ) || '' === $html ) { + return $block_content; + } + + return $this->cleanup_classes_enabled( $block, $template ) + ? $this->cleanup_classes( $html, $block ) + : $html; + } + + /** + * Checks whether Twig rendering is enabled. + * + * @return bool TRUE when enabled. + */ + private function enabled(): bool { + /** + * Filters whether mapped core block Twig rendering should be enabled. + * + * Defaults to false so the parent theme never changes frontend block + * output unless a child theme or project plugin opts in. + * + * @param bool $enabled Whether mapped block Twig rendering is enabled. + */ + return (bool) apply_filters( 'emulsify_theme_core_block_twig_rendering_enabled', false ); + } + + /** + * Builds Twig context for a mapped block. + * + * @param array $block Parsed block metadata. + * @param string $block_content Rendered WordPress block content. + * @param mixed $instance WP_Block instance when available. + * @param string $template Twig template being rendered. + * @return array Twig context. + */ + private function context( array $block, string $block_content, $instance, string $template ): array { + $context = array(); + + if ( class_exists( '\Timber\Timber' ) && method_exists( '\Timber\Timber', 'context' ) ) { + $timber_context = \Timber\Timber::context(); + $context = is_array( $timber_context ) ? $timber_context : array(); + } + + $attributes = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array(); + + $context['block'] = $block; + $context['block_metadata'] = $block; + $context['block_name'] = $this->block_name( $block ); + $context['attributes'] = $attributes; + $context['content'] = $block_content; + $context['inner_content'] = $this->inner_content( $block, $block_content ); + $context['inner_blocks'] = isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ? $block['innerBlocks'] : array(); + $context['wp_block'] = $instance; + $context['twig_template'] = $template; + + /** + * Filters Twig context for mapped block rendering. + * + * @param array $context Twig context. + * @param array $block Parsed block metadata. + * @param string $block_content Rendered WordPress block content. + * @param string $template Twig template being rendered. + * @param mixed $instance WP_Block instance when available. + */ + $filtered = apply_filters( 'emulsify_theme_core_block_twig_context', $context, $block, $block_content, $template, $instance ); + + return is_array( $filtered ) ? $filtered : $context; + } + + /** + * Gets rendered inner content for Twig convenience. + * + * @param array $block Parsed block metadata. + * @param string $block_content Rendered WordPress block content. + * @return string Inner content. + */ + private function inner_content( array $block, string $block_content ): string { + if ( empty( $block['innerContent'] ) || ! is_array( $block['innerContent'] ) ) { + return $block_content; + } + + $content = ''; + + foreach ( $block['innerContent'] as $piece ) { + if ( is_scalar( $piece ) ) { + $content .= (string) $piece; + } + } + + return '' === $content ? $block_content : $content; + } + + /** + * Gets the mapped Twig template for a block. + * + * @param string $block_name Block name. + * @param array $block Parsed block metadata. + * @return string Twig template path, or an empty string when unavailable. + */ + private function template_for_block( string $block_name, array $block ): string { + $map = $this->template_map(); + + if ( ! isset( $map[ $block_name ] ) ) { + return ''; + } + + $template = $this->resolve_template( $map[ $block_name ] ); + + /** + * Filters the resolved Twig template for a block render. + * + * Return an empty string to fall back to WordPress' original block HTML. + * + * @param string $template Resolved Twig template path. + * @param string $block_name Block name. + * @param array $block Parsed block metadata. + * @param array $map Normalized template map. + */ + $filtered = apply_filters( 'emulsify_theme_core_block_twig_template', $template, $block_name, $block, $map ); + + return is_scalar( $filtered ) ? (string) $filtered : $template; + } + + /** + * Gets the configured block-to-template map. + * + * @return array Block template map. + */ + private function template_map(): array { + /** + * Filters block-to-Twig-template mappings. + * + * Keys should be block names such as "core/paragraph". Values should be + * child/parent theme relative Twig paths such as + * "dist/components/paragraph/paragraph.twig". + * + * @param array $map Block template map. + */ + $filtered = apply_filters( 'emulsify_theme_core_block_twig_template_map', array() ); + + if ( ! is_array( $filtered ) ) { + return array(); + } + + $map = array(); + + foreach ( $filtered as $block_name => $template ) { + if ( ! is_scalar( $block_name ) || ! is_scalar( $template ) ) { + continue; + } + + $block_name = strtolower( trim( (string) $block_name ) ); + $template = trim( (string) $template ); + + if ( '' !== $block_name && '' !== $template && preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $block_name ) ) { + $map[ $block_name ] = $template; + } + } + + return $map; + } + + /** + * Resolves a mapped template path when a readable child/parent file exists. + * + * @param string $template Mapped template path. + * @return string Template path for Timber, or an empty string. + */ + private function resolve_template( string $template ): string { + $template = str_replace( '\\', '/', trim( $template ) ); + $template = ltrim( $template, '/' ); + + if ( '' === $template || false !== strpos( $template, '../' ) || '..' === $template ) { + return ''; + } + + foreach ( $this->theme_roots() as $root ) { + if ( is_readable( rtrim( $root, '/\\' ) . '/' . $template ) ) { + return $template; + } + } + + return ''; + } + + /** + * Gets child and parent theme roots. + * + * @return array Theme root directories. + */ + private function theme_roots(): array { + $roots = array(); + + if ( function_exists( 'get_stylesheet_directory' ) ) { + $roots[] = get_stylesheet_directory(); + } + + if ( function_exists( 'get_template_directory' ) ) { + $roots[] = get_template_directory(); + } + + return array_values( array_unique( array_filter( $roots ) ) ); + } + + /** + * Checks whether class cleanup is enabled for rendered Twig output. + * + * @param array $block Parsed block metadata. + * @param string $template Twig template being rendered. + * @return bool TRUE when cleanup is enabled. + */ + private function cleanup_classes_enabled( array $block, string $template ): bool { + /** + * Filters whether mapped block Twig output should remove base wp-block classes. + * + * Defaults to false. Cleanup uses WP_HTML_Tag_Processor when available + * and otherwise leaves rendered markup unchanged. + * + * @param bool $enabled Whether class cleanup is enabled. + * @param array $block Parsed block metadata. + * @param string $template Twig template being rendered. + */ + return (bool) apply_filters( 'emulsify_theme_core_block_twig_cleanup_classes_enabled', false, $block, $template ); + } + + /** + * Removes configured classes from Twig output using WP_HTML_Tag_Processor. + * + * @param string $html Rendered Twig output. + * @param array $block Parsed block metadata. + * @return string Filtered HTML. + */ + private function cleanup_classes( string $html, array $block ): string { + if ( ! class_exists( '\WP_HTML_Tag_Processor' ) ) { + return $html; + } + + $classes = $this->cleanup_class_names( $block ); + + if ( empty( $classes ) ) { + return $html; + } + + $processor = new \WP_HTML_Tag_Processor( $html ); + + while ( $processor->next_tag() ) { + foreach ( $classes as $class_name ) { + $processor->remove_class( $class_name ); + } + } + + return $processor->get_updated_html(); + } + + /** + * Gets class names to remove from Twig output when cleanup is enabled. + * + * @param array $block Parsed block metadata. + * @return array Class names. + */ + private function cleanup_class_names( array $block ): array { + $block_name = $this->block_name( $block ); + $slug = false === strpos( $block_name, '/' ) ? $block_name : substr( $block_name, strpos( $block_name, '/' ) + 1 ); + $classes = array_filter( + array( + 'wp-block', + 'wp-block-' . $this->sanitize_html_class( $slug ), + 'wp-block-' . $this->sanitize_html_class( str_replace( '/', '-', $block_name ) ), + ) + ); + + /** + * Filters class names removed from mapped block Twig output. + * + * This filter is only used when class cleanup is enabled separately. + * + * @param array $classes Class names to remove. + * @param array $block Parsed block metadata. + */ + $filtered = apply_filters( 'emulsify_theme_core_block_twig_cleanup_class_names', array_values( array_unique( $classes ) ), $block ); + + if ( ! is_array( $filtered ) ) { + return array_values( array_unique( $classes ) ); + } + + $normalized = array(); + + foreach ( $filtered as $class_name ) { + if ( is_scalar( $class_name ) ) { + $class_name = trim( (string) $class_name ); + + if ( '' !== $class_name ) { + $normalized[] = $class_name; + } + } + } + + return array_values( array_unique( $normalized ) ); + } + + /** + * Gets a parsed block name. + * + * @param array $block Parsed block metadata. + * @return string Block name. + */ + private function block_name( array $block ): string { + return isset( $block['blockName'] ) && is_scalar( $block['blockName'] ) + ? strtolower( trim( (string) $block['blockName'] ) ) + : ''; + } + + /** + * Returns original content and appends diagnostics for editors/admins only. + * + * @param string $block_content Original block content. + * @param string $message Diagnostic message. + * @param array $block Parsed block metadata. + * @param string $template Twig template being rendered. + * @return string Original content plus optional diagnostic markup. + */ + private function render_error( string $block_content, string $message, array $block, string $template ): string { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) { + error_log( + sprintf( + '[Emulsify] Core block Twig render failed for %s with %s: %s', + $this->block_name( $block ), + $template, + $message + ) + ); + } + + if ( ! $this->can_show_diagnostics() ) { + return $block_content; + } + + return $block_content . sprintf( + '

    %s %s

    ', + $this->esc_html__( 'Emulsify block render error:', 'emulsify' ), + $this->esc_html( $message ) + ); + } + + /** + * Checks whether diagnostics can be shown to the current user. + * + * @return bool TRUE for editors/admins. + */ + private function can_show_diagnostics(): bool { + return function_exists( 'current_user_can' ) + && ( current_user_can( 'edit_posts' ) || current_user_can( 'edit_theme_options' ) ); + } + + /** + * Sanitizes an HTML class. + * + * @param string $class_name Raw class name. + * @return string Safe class name. + */ + private function sanitize_html_class( string $class_name ): string { + if ( function_exists( 'sanitize_html_class' ) ) { + return sanitize_html_class( $class_name ); + } + + return trim( preg_replace( '/[^A-Za-z0-9_-]+/', '-', $class_name ), '-' ); + } + + /** + * Escapes translated text. + * + * @param string $text Text. + * @param string $domain Text domain. + * @return string Escaped translated text. + */ + private function esc_html__( string $text, string $domain ): string { + return function_exists( 'esc_html__' ) ? esc_html__( $text, $domain ) : $this->esc_html( $text ); + } + + /** + * Translates text when WordPress is available. + * + * @param string $text Text. + * @param string $domain Text domain. + * @return string Translated text. + */ + private function translate( string $text, string $domain ): string { + return function_exists( '__' ) ? __( $text, $domain ) : $text; + } + + /** + * Escapes HTML text. + * + * @param string $text Text. + * @return string Escaped text. + */ + private function esc_html( string $text ): string { + return function_exists( 'esc_html' ) ? esc_html( $text ) : htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' ); + } +} diff --git a/includes/class-editor-enhancements.php b/includes/class-editor-enhancements.php new file mode 100644 index 0000000..7977b6f --- /dev/null +++ b/includes/class-editor-enhancements.php @@ -0,0 +1,555 @@ +config(); + + foreach ( $this->asset_files( 'dist/global/editor', array( 'css' ) ) as $asset ) { + wp_enqueue_style( + $this->handle( 'emulsify-editor', $asset['relative'] ), + $asset['uri'], + array(), + $asset['version'] + ); + } + + foreach ( $this->asset_files( 'dist/global/editor', array( 'js' ) ) as $asset ) { + $handle = $this->handle( 'emulsify-editor', $asset['relative'] ); + + wp_enqueue_script( + $handle, + $asset['uri'], + $this->editor_script_dependencies(), + $asset['version'], + array( + 'in_footer' => true, + ) + ); + + wp_add_inline_script( + $handle, + 'window.emulsifyEditorEnhancements = ' . $this->json_encode( $config ) . ';', + 'before' + ); + } + } + + /** + * Adds a file-caption class to parsed File blocks when configured. + * + * @param array $parsed_block Parsed block data. + * @param array $source_block Source block data. + * @param mixed $parent_block Parent block instance. + * @return array Filtered parsed block data. + */ + public function render_block_data( array $parsed_block, array $source_block = array(), $parent_block = null ): array { + unset( $source_block, $parent_block ); + + $config = $this->config(); + + if ( ! $this->file_caption_enabled( $config ) || ! $this->is_file_block( $parsed_block ) ) { + return $parsed_block; + } + + $settings = $config['fileCaption']; + $attribute = $this->string_option( $settings['attribute'] ?? '', 'emulsifyShowMediaCaption' ); + + if ( empty( $parsed_block['attrs'][ $attribute ] ) ) { + return $parsed_block; + } + + $class = $this->string_option( $settings['blockClassName'] ?? '', 'has-media-caption' ); + $parsed_block['attrs'] = isset( $parsed_block['attrs'] ) && is_array( $parsed_block['attrs'] ) ? $parsed_block['attrs'] : array(); + $parsed_block['attrs']['className'] = $this->append_class( $parsed_block['attrs']['className'] ?? '', $class ); + + return $parsed_block; + } + + /** + * Appends the media caption to rendered File blocks when configured. + * + * @param string $block_content Rendered block content. + * @param array $block Parsed block data. + * @param mixed $instance Block instance. + * @return string Filtered block content. + */ + public function render_block( string $block_content, array $block, $instance = null ): string { + unset( $instance ); + + $config = $this->config(); + + if ( ! $this->file_caption_enabled( $config ) || ! $this->is_file_block( $block ) ) { + return $block_content; + } + + $settings = $config['fileCaption']; + $attribute = $this->string_option( $settings['attribute'] ?? '', 'emulsifyShowMediaCaption' ); + + if ( empty( $block['attrs'][ $attribute ] ) || false !== strpos( $block_content, 'wp-block-file__media-caption' ) ) { + return $block_content; + } + + $attachment_id = $this->attachment_id( $block['attrs'] ?? array() ); + + if ( $attachment_id <= 0 || ! function_exists( 'wp_get_attachment_caption' ) ) { + return $block_content; + } + + $caption = wp_get_attachment_caption( $attachment_id ); + + if ( ! is_scalar( $caption ) || '' === trim( (string) $caption ) ) { + return $block_content; + } + + $caption_class = $this->string_option( $settings['captionClassName'] ?? '', 'wp-block-file__media-caption' ); + $caption_html = sprintf( + '

    %s

    ', + $this->esc_attr( $caption_class ), + $this->esc_html( (string) $caption ) + ); + + if ( preg_match( '/<\/div>\s*$/i', $block_content ) ) { + $filtered = preg_replace( '/<\/div>\s*$/i', $caption_html . '
    ', $block_content, 1 ); + + return is_string( $filtered ) ? $filtered : $block_content; + } + + return $block_content . $caption_html; + } + + /** + * Gets editor enhancement configuration. + * + * @return array Editor enhancement config. + */ + private function config(): array { + $config = array( + 'columnsEqualHeight' => array( + 'enabled' => false, + 'attribute' => 'emulsifyEqualizeHeights', + 'className' => 'is-equal-height', + ), + 'fileCaption' => array( + 'enabled' => false, + 'attribute' => 'emulsifyShowMediaCaption', + 'blockClassName' => 'has-media-caption', + 'captionClassName' => 'wp-block-file__media-caption', + ), + 'embedVariations' => array( + 'enabled' => false, + 'allowedVariationNames' => array(), + ), + 'placement' => array( + 'enabled' => false, + 'blocks' => array(), + 'singleton' => true, + 'requireTop' => true, + ), + ); + + /** + * Filters optional editor enhancement configuration. + * + * Defaults keep every starter module disabled. Child themes can enable + * individual modules by returning nested configuration changes. + * + * @param array $config Editor enhancement configuration. + */ + $filtered = apply_filters( 'emulsify_theme_editor_enhancements_config', $config ); + + if ( is_array( $filtered ) ) { + $config = $this->merge_config( $config, $filtered ); + } + + return $this->normalize_config( $config ); + } + + /** + * Recursively merges editor configuration. + * + * @param array $base Base config. + * @param array $override Override config. + * @return array Merged config. + */ + private function merge_config( array $base, array $override ): array { + foreach ( $override as $key => $value ) { + if ( + array_key_exists( $key, $base ) + && is_array( $base[ $key ] ) + && is_array( $value ) + && ! array_is_list( $base[ $key ] ) + && ! array_is_list( $value ) + ) { + $base[ $key ] = $this->merge_config( $base[ $key ], $value ); + continue; + } + + $base[ $key ] = $value; + } + + return $base; + } + + /** + * Normalizes editor enhancement configuration. + * + * @param array $config Raw config. + * @return array Normalized config. + */ + private function normalize_config( array $config ): array { + foreach ( array( 'columnsEqualHeight', 'fileCaption', 'embedVariations', 'placement' ) as $module ) { + $config[ $module ] = isset( $config[ $module ] ) && is_array( $config[ $module ] ) ? $config[ $module ] : array(); + $config[ $module ]['enabled'] = ! empty( $config[ $module ]['enabled'] ); + } + + $config['embedVariations']['allowedVariationNames'] = $this->string_list( $config['embedVariations']['allowedVariationNames'] ?? array() ); + $config['placement']['blocks'] = $this->string_list( $config['placement']['blocks'] ?? array() ); + $config['placement']['singleton'] = ! empty( $config['placement']['singleton'] ); + $config['placement']['requireTop'] = ! empty( $config['placement']['requireTop'] ); + + return $config; + } + + /** + * Finds editor assets below a theme-relative directory. + * + * @param string $directory Theme-relative asset directory. + * @param array $extensions Allowed file extensions. + * @return array Built asset records. + */ + private function asset_files( string $directory, array $extensions ): array { + $assets = array(); + $seen = array(); + + foreach ( $this->asset_roots( $directory ) as $root ) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $root['path'], \RecursiveDirectoryIterator::SKIP_DOTS ) + ); + + foreach ( $iterator as $file ) { + if ( ! $file->isFile() || ! in_array( strtolower( $file->getExtension() ), $extensions, true ) ) { + continue; + } + + $relative = $this->relative_path( $root['path'], $file->getPathname() ); + + if ( isset( $seen[ $relative ] ) ) { + continue; + } + + $seen[ $relative ] = true; + $assets[] = array( + 'path' => $file->getPathname(), + 'priority' => $root['priority'], + 'relative' => $relative, + 'uri' => $root['uri'] . '/' . $relative, + 'version' => $this->version( $file->getPathname() ), + ); + } + } + + usort( + $assets, + static function ( array $left, array $right ): int { + $priority = $left['priority'] <=> $right['priority']; + + return 0 === $priority ? strcmp( $left['relative'], $right['relative'] ) : $priority; + } + ); + + /** + * Filters built editor asset files before they are enqueued. + * + * Asset records should include path, relative, uri, and version keys. + * + * @param array $assets Built editor asset records. + * @param string $directory Theme-relative asset directory being scanned. + * @param array $extensions Allowed file extensions for the current enqueue pass. + */ + $filtered = apply_filters( 'emulsify_theme_editor_asset_files', $assets, $directory, $extensions ); + + return is_array( $filtered ) ? $filtered : $assets; + } + + /** + * Gets child theme editor asset roots first, then parent fallbacks. + * + * @param string $directory Theme-relative asset directory. + * @return array Asset root records. + */ + private function asset_roots( string $directory ): array { + $roots = array(); + $seen_paths = array(); + $directories = array( + array( + 'path' => rtrim( get_stylesheet_directory(), '/\\' ) . '/' . ltrim( $directory, '/\\' ), + 'priority' => 0, + 'source' => 'child', + 'uri' => rtrim( get_stylesheet_directory_uri(), '/' ) . '/' . trim( $directory, '/' ), + ), + array( + 'path' => rtrim( get_template_directory(), '/\\' ) . '/' . ltrim( $directory, '/\\' ), + 'priority' => 1, + 'source' => 'parent', + 'uri' => rtrim( get_template_directory_uri(), '/' ) . '/' . trim( $directory, '/' ), + ), + ); + + /** + * Filters built editor asset directories before discovery. + * + * Root records should include absolute path, public uri, and priority + * keys. Lower priority values are enqueued first after duplicate relative + * paths are resolved child-first. + * + * @param array $directories Built editor asset directory records. + * @param string $directory Theme-relative asset directory being scanned. + */ + $filtered = apply_filters( 'emulsify_theme_editor_asset_directories', $directories, $directory ); + + if ( is_array( $filtered ) ) { + $directories = $filtered; + } + + foreach ( $directories as $priority => $candidate ) { + if ( ! is_array( $candidate ) || empty( $candidate['path'] ) || empty( $candidate['uri'] ) ) { + continue; + } + + $path = rtrim( (string) $candidate['path'], '/\\' ); + $uri = rtrim( (string) $candidate['uri'], '/' ); + $key = realpath( $path ); + + if ( false === $key || isset( $seen_paths[ $key ] ) || ! is_dir( $path ) || ! is_readable( $path ) ) { + continue; + } + + $seen_paths[ $key ] = true; + $roots[] = array( + 'path' => rtrim( $path, '/\\' ), + 'priority' => isset( $candidate['priority'] ) ? (int) $candidate['priority'] : (int) $priority, + 'uri' => $uri, + ); + } + + return $roots; + } + + /** + * Gets WordPress script dependencies for editor modules. + * + * @return array Script handles. + */ + private function editor_script_dependencies(): array { + return array( + 'wp-block-editor', + 'wp-blocks', + 'wp-components', + 'wp-compose', + 'wp-data', + 'wp-dom-ready', + 'wp-element', + 'wp-hooks', + 'wp-i18n', + 'wp-notices', + ); + } + + /** + * Checks whether the File block caption module is enabled. + * + * @param array $config Editor enhancement config. + * @return bool TRUE when enabled. + */ + private function file_caption_enabled( array $config ): bool { + return ! empty( $config['fileCaption']['enabled'] ); + } + + /** + * Checks for a core/file parsed block. + * + * @param array $block Parsed block data. + * @return bool TRUE when the block is core/file. + */ + private function is_file_block( array $block ): bool { + return isset( $block['blockName'] ) && 'core/file' === $block['blockName']; + } + + /** + * Gets the attachment ID from File block attributes. + * + * @param array $attributes Block attributes. + * @return int Attachment ID. + */ + private function attachment_id( array $attributes ): int { + foreach ( array( 'id', 'fileId' ) as $key ) { + if ( isset( $attributes[ $key ] ) && is_numeric( $attributes[ $key ] ) ) { + return (int) $attributes[ $key ]; + } + } + + return 0; + } + + /** + * Appends a CSS class to an existing class string. + * + * @param mixed $current Existing class value. + * @param string $class Class to append. + * @return string Class string. + */ + private function append_class( $current, string $class ): string { + $classes = is_scalar( $current ) ? preg_split( '/\s+/', (string) $current ) : array(); + $classes = is_array( $classes ) ? $classes : array(); + $classes[] = $class; + + return trim( implode( ' ', array_unique( array_filter( $classes ) ) ) ); + } + + /** + * Normalizes a string option with a fallback. + * + * @param mixed $value Option value. + * @param string $fallback Fallback value. + * @return string Normalized string. + */ + private function string_option( $value, string $fallback ): string { + if ( ! is_scalar( $value ) ) { + return $fallback; + } + + $value = trim( (string) $value ); + + return '' === $value ? $fallback : $value; + } + + /** + * Normalizes a list of strings. + * + * @param mixed $values Value list. + * @return array Normalized strings. + */ + private function string_list( $values ): array { + if ( ! is_array( $values ) ) { + return array(); + } + + $strings = array(); + + foreach ( $values as $value ) { + if ( ! is_scalar( $value ) ) { + continue; + } + + $value = trim( (string) $value ); + + if ( '' !== $value ) { + $strings[] = $value; + } + } + + return array_values( array_unique( $strings ) ); + } + + /** + * Builds a WordPress-safe asset handle. + * + * @param string $prefix Handle prefix. + * @param string $relative Asset path relative to its built directory. + * @return string Asset handle. + */ + private function handle( string $prefix, string $relative ): string { + $name = preg_replace( '/\.(css|js)$/', '', $relative ); + $name = preg_replace( '/[^A-Za-z0-9_-]+/', '-', (string) $name ); + + return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); + } + + /** + * Builds a POSIX relative path. + * + * @param string $base_path Base directory. + * @param string $path Absolute file path. + * @return string Relative path. + */ + private function relative_path( string $base_path, string $path ): string { + $relative = ltrim( str_replace( rtrim( $base_path, '/\\' ), '', $path ), '/\\' ); + + return str_replace( '\\', '/', $relative ); + } + + /** + * Gets a filemtime-based asset version. + * + * @param string $path Absolute file path. + * @return string|null Asset version. + */ + private function version( string $path ): ?string { + $modified = filemtime( $path ); + + return false === $modified ? null : (string) $modified; + } + + /** + * JSON encodes a value for inline script output. + * + * @param mixed $value Value to encode. + * @return string JSON string. + */ + private function json_encode( $value ): string { + if ( function_exists( 'wp_json_encode' ) ) { + $encoded = wp_json_encode( $value ); + } else { + $encoded = json_encode( $value ); + } + + return is_string( $encoded ) ? $encoded : '{}'; + } + + /** + * Escapes an HTML attribute. + * + * @param string $value Raw attribute. + * @return string Escaped attribute. + */ + private function esc_attr( string $value ): string { + return function_exists( 'esc_attr' ) ? esc_attr( $value ) : htmlspecialchars( $value, ENT_QUOTES, 'UTF-8' ); + } + + /** + * Escapes HTML text. + * + * @param string $value Raw text. + * @return string Escaped text. + */ + private function esc_html( string $value ): string { + return function_exists( 'esc_html' ) ? esc_html( $value ) : htmlspecialchars( $value, ENT_QUOTES, 'UTF-8' ); + } +} diff --git a/includes/class-editor-policy.php b/includes/class-editor-policy.php new file mode 100644 index 0000000..4055932 --- /dev/null +++ b/includes/class-editor-policy.php @@ -0,0 +1,606 @@ +options( $context ); + $post_type = $this->post_type( $context ); + $blocks = $this->configured_allowed_block_types( $options['allowed_block_types'] ?? null, $post_type ); + + /** + * Filters the resolved allowed block type list before WordPress receives it. + * + * Return null to preserve the incoming WordPress value. Return an array + * of block names to enable an allow list for the current editor context. + * + * @param array|null $blocks Resolved configured block names, or null for no policy. + * @param string $post_type Current post type when available. + * @param mixed $context Block editor context. + * @param array|bool $allowed_block_types Incoming WordPress allowed block types value. + * @param array $options Editor policy options. + */ + $filtered = apply_filters( 'emulsify_theme_allowed_block_types', $blocks, $post_type, $context, $allowed_block_types, $options ); + + if ( null === $filtered || ! is_array( $filtered ) ) { + return $allowed_block_types; + } + + $blocks = $this->normalize_block_names( $filtered ); + + if ( ! empty( $options['auto_allow_pattern_blocks'] ) ) { + $blocks = array_merge( $blocks, $this->pattern_block_names( $options ) ); + } + + if ( is_array( $allowed_block_types ) && ! empty( $options['merge_allowed_block_types'] ) ) { + $blocks = array_merge( $allowed_block_types, $blocks ); + } + + return array_values( array_unique( $this->normalize_block_names( $blocks ) ) ); + } + + /** + * Filters block editor settings for optional pattern governance. + * + * @param array $settings Block editor settings. + * @param mixed $context Block editor context. + * @return array Filtered settings. + */ + public function block_editor_settings( array $settings, $context = null ): array { + $options = $this->options( $context ); + $namespaces = $this->pattern_namespaces( $options, $settings, $context ); + + if ( ! empty( $namespaces ) ) { + $settings['blockPatterns'] = $this->filter_block_patterns( $settings['blockPatterns'] ?? null, $namespaces ); + } + + $disable_user_patterns = ! empty( $options['disable_user_patterns_for_non_admins'] ); + + if ( $disable_user_patterns && ! $this->current_user_can( (string) $options['admin_capability'] ) ) { + $settings['enableUserPatterns'] = false; + } + + return $settings; + } + + /** + * Optionally changes the creation capability for user-created patterns. + * + * @param array $args Post type registration arguments. + * @param string $post_type Post type slug. + * @return array Filtered post type arguments. + */ + public function post_type_args( array $args, string $post_type ): array { + if ( 'wp_block' !== $post_type ) { + return $args; + } + + $options = $this->options(); + + if ( empty( $options['restrict_wp_block_creation'] ) ) { + return $args; + } + + $capability = $this->capability( $options['wp_block_create_capability'] ?? '' ); + + if ( '' === $capability ) { + return $args; + } + + $args['map_meta_cap'] = true; + $args['capabilities'] = isset( $args['capabilities'] ) && is_array( $args['capabilities'] ) ? $args['capabilities'] : array(); + $args['capabilities']['create_posts'] = $capability; + + return $args; + } + + /** + * Applies configured support/style overrides to metadata-derived settings. + * + * @param array $settings Block type metadata settings. + * @param array $metadata Block metadata. + * @return array Filtered metadata settings. + */ + public function block_type_metadata_settings( array $settings, array $metadata ): array { + $block_name = isset( $metadata['name'] ) && is_scalar( $metadata['name'] ) ? (string) $metadata['name'] : ''; + + return $this->apply_block_support_overrides( $settings, $block_name, 'block_type_metadata_settings' ); + } + + /** + * Applies configured support/style overrides to final block registration args. + * + * @param array $args Block type registration arguments. + * @param string $block_type Block type name. + * @return array Filtered block type registration arguments. + */ + public function register_block_type_args( array $args, string $block_type ): array { + return $this->apply_block_support_overrides( $args, $block_type, 'register_block_type_args' ); + } + + /** + * Gets editor policy options. + * + * @param mixed $context Optional editor context. + * @return array Editor policy options. + */ + private function options( $context = null ): array { + $options = array( + 'allowed_block_types' => null, + 'merge_allowed_block_types' => true, + 'auto_allow_pattern_blocks' => false, + 'pattern_directories' => null, + 'pattern_namespaces' => array(), + 'disable_user_patterns_for_non_admins' => false, + 'admin_capability' => 'manage_options', + 'restrict_wp_block_creation' => false, + 'wp_block_create_capability' => 'manage_options', + 'block_support_overrides' => array(), + ); + + /** + * Filters block editor policy options. + * + * Defaults preserve the parent theme's current behavior. Child themes can + * opt into individual policies by returning changed option values. + * + * @param array $options Editor policy options. + * @param mixed $context Optional editor context. + */ + $filtered = apply_filters( 'emulsify_theme_editor_policy_options', $options, $context ); + + if ( is_array( $filtered ) ) { + $options = array_merge( $options, $filtered ); + } + + $options['admin_capability'] = $this->capability( $options['admin_capability'] ?? 'manage_options' ); + $options['wp_block_create_capability'] = $this->capability( $options['wp_block_create_capability'] ?? 'manage_options' ); + + return $options; + } + + /** + * Resolves configured allowed blocks for a post type. + * + * @param mixed $config Allowed block type configuration. + * @param string $post_type Post type when available. + * @return array|null Block names, or null when no policy is configured. + */ + private function configured_allowed_block_types( $config, string $post_type ): ?array { + if ( ! is_array( $config ) ) { + return null; + } + + if ( array_is_list( $config ) ) { + return $config; + } + + $blocks = array(); + $configured = false; + + foreach ( array( 'default', '*' ) as $key ) { + if ( array_key_exists( $key, $config ) && is_array( $config[ $key ] ) ) { + $blocks = array_merge( $blocks, $config[ $key ] ); + $configured = true; + } + } + + foreach ( array( 'by_post_type', 'post_types' ) as $group_key ) { + if ( '' === $post_type || ! isset( $config[ $group_key ] ) || ! is_array( $config[ $group_key ] ) ) { + continue; + } + + $post_type_blocks = array_key_exists( $post_type, $config[ $group_key ] ) ? $config[ $group_key ][ $post_type ] : null; + + if ( is_array( $post_type_blocks ) ) { + $blocks = array_merge( $blocks, $post_type_blocks ); + $configured = true; + } + } + + if ( '' !== $post_type && array_key_exists( $post_type, $config ) && is_array( $config[ $post_type ] ) ) { + $blocks = array_merge( $blocks, $config[ $post_type ] ); + $configured = true; + } + + return $configured ? $blocks : null; + } + + /** + * Gets the post type from a block editor context. + * + * @param mixed $context Block editor context. + * @return string Post type or an empty string. + */ + private function post_type( $context ): string { + if ( is_object( $context ) && isset( $context->post ) ) { + if ( function_exists( 'get_post_type' ) ) { + $post_type = get_post_type( $context->post ); + + return is_string( $post_type ) ? $post_type : ''; + } + + if ( is_object( $context->post ) && isset( $context->post->post_type ) && is_scalar( $context->post->post_type ) ) { + return (string) $context->post->post_type; + } + } + + if ( is_object( $context ) && isset( $context->post_type ) && is_scalar( $context->post_type ) ) { + return (string) $context->post_type; + } + + return ''; + } + + /** + * Gets configured block pattern namespaces. + * + * @param array $options Editor policy options. + * @param array $settings Block editor settings. + * @param mixed $context Block editor context. + * @return array Pattern namespaces. + */ + private function pattern_namespaces( array $options, array $settings, $context ): array { + $namespaces = isset( $options['pattern_namespaces'] ) && is_array( $options['pattern_namespaces'] ) + ? $options['pattern_namespaces'] + : array(); + + /** + * Filters the block pattern namespaces that should stay visible. + * + * Return an empty array to preserve the default pattern list. + * + * @param array $namespaces Pattern namespaces, without trailing slashes. + * @param array $settings Block editor settings. + * @param mixed $context Block editor context. + * @param array $options Editor policy options. + */ + $filtered = apply_filters( 'emulsify_theme_pattern_namespaces', $namespaces, $settings, $context, $options ); + + if ( is_array( $filtered ) ) { + $namespaces = $filtered; + } + + return array_values( + array_unique( + array_filter( + array_map( + static function ( $namespace ): string { + return is_scalar( $namespace ) ? trim( strtolower( (string) $namespace ), " \t\n\r\0\x0B/" ) : ''; + }, + $namespaces + ) + ) + ) + ); + } + + /** + * Filters visible block pattern records by namespace. + * + * @param mixed $patterns Block pattern records. + * @param array $namespaces Allowed namespaces. + * @return array Filtered block pattern records. + */ + private function filter_block_patterns( $patterns, array $namespaces ): array { + if ( ! is_array( $patterns ) && class_exists( '\WP_Block_Patterns_Registry' ) ) { + $patterns = \WP_Block_Patterns_Registry::get_instance()->get_all_registered(); + } + + if ( ! is_array( $patterns ) ) { + return array(); + } + + $keep = array(); + + foreach ( $patterns as $key => $pattern ) { + $name = is_string( $key ) ? $key : ''; + + if ( is_array( $pattern ) && isset( $pattern['name'] ) && is_scalar( $pattern['name'] ) ) { + $name = (string) $pattern['name']; + } + + if ( $this->matches_namespace( $name, $namespaces ) ) { + $keep[] = $pattern; + } + } + + return $keep; + } + + /** + * Checks whether a pattern name is in an allowed namespace. + * + * @param string $name Pattern name. + * @param array $namespaces Allowed namespaces. + * @return bool TRUE when the namespace is allowed. + */ + private function matches_namespace( string $name, array $namespaces ): bool { + $name = strtolower( trim( $name ) ); + + foreach ( $namespaces as $namespace ) { + if ( 0 === strpos( $name, $namespace . '/' ) ) { + return true; + } + } + + return false; + } + + /** + * Gets block names referenced by configured pattern JSON files. + * + * @param array $options Editor policy options. + * @return array Block names. + */ + private function pattern_block_names( array $options ): array { + $names = array(); + + foreach ( $this->pattern_json_files( $options ) as $file ) { + $contents = file_get_contents( $file ); + + if ( ! is_string( $contents ) || '' === trim( $contents ) ) { + continue; + } + + $data = json_decode( $contents, true ); + + if ( ! is_array( $data ) || empty( $data['content'] ) || ! is_string( $data['content'] ) ) { + continue; + } + + $names = array_merge( $names, $this->block_names_from_content( $data['content'] ) ); + } + + return array_values( array_unique( $names ) ); + } + + /** + * Gets pattern JSON files from configured pattern directories. + * + * @param array $options Editor policy options. + * @return array Absolute file paths. + */ + private function pattern_json_files( array $options ): array { + $directories = isset( $options['pattern_directories'] ) && is_array( $options['pattern_directories'] ) + ? $options['pattern_directories'] + : $this->default_pattern_directories(); + $files = array(); + $seen = array(); + + foreach ( $directories as $directory ) { + if ( ! is_scalar( $directory ) ) { + continue; + } + + $path = rtrim( (string) $directory, '/\\' ); + + if ( '' === $path || ! is_dir( $path ) || ! is_readable( $path ) ) { + continue; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $path, \RecursiveDirectoryIterator::SKIP_DOTS ) + ); + + foreach ( $iterator as $file ) { + if ( ! $file->isFile() || 'json' !== strtolower( $file->getExtension() ) ) { + continue; + } + + $file_path = $file->getPathname(); + $key = realpath( $file_path ) ?: $file_path; + + if ( isset( $seen[ $key ] ) ) { + continue; + } + + $seen[ $key ] = true; + $files[] = $file_path; + } + } + + sort( $files ); + + return $files; + } + + /** + * Gets child and parent pattern directories. + * + * @return array Pattern directories. + */ + private function default_pattern_directories(): array { + $directories = array(); + + if ( function_exists( 'get_stylesheet_directory' ) ) { + $directories[] = rtrim( get_stylesheet_directory(), '/\\' ) . '/patterns'; + } + + if ( function_exists( 'get_template_directory' ) ) { + $directories[] = rtrim( get_template_directory(), '/\\' ) . '/patterns'; + } + + return array_values( array_unique( $directories ) ); + } + + /** + * Extracts block names from block comment markup. + * + * @param string $content Pattern block content. + * @return array Block names. + */ + private function block_names_from_content( string $content ): array { + if ( ! preg_match_all( '/

    Smoke pattern content

    ', + }); + + writeJson(path.join(target, 'package-lock.json'), { + name: 'whisk', + version: '2.0.0', + lockfileVersion: 3, + requires: true, + packages: { + '': { + name: 'whisk', + version: '2.0.0', + dependencies: { + '@emulsify/core': '^4.1.0', + }, + }, + }, + }); + + runNodeScript(path.join(target, '.cli/init.js')); + + const style = parseThemeHeader(fs.readFileSync(path.join(target, 'style.css'), 'utf8')); + const packageJson = readJson(path.join(target, 'package.json')); + const packageLock = readJson(path.join(target, 'package-lock.json')); + const project = readJson(path.join(target, 'project.emulsify.json')); + const pattern = readJson(path.join(target, 'patterns/smoke-pattern.json')); + const functionsPhp = fs.readFileSync(path.join(target, 'functions.php'), 'utf8'); + const pageTwig = fs.readFileSync(path.join(target, 'templates/page.twig'), 'utf8'); + + assert(style['Theme Name'] === 'Acme Theme', 'style.css should update Theme Name.'); + assert(style['Text Domain'] === 'acme-theme', 'style.css should update Text Domain.'); + assert(style.Template === 'emulsify', 'style.css should keep Template: emulsify.'); + assert(packageJson.name === 'acme-theme', 'package.json should update name.'); + assert(packageLock.name === 'acme-theme', 'package-lock.json should update the root name.'); + assert(packageLock.packages[''].name === 'acme-theme', 'package-lock.json packages[""].name should update.'); + assert(project.project.platform === 'wordpress', 'project.emulsify.json should keep project.platform: wordpress.'); + assert(project.project.name === 'Acme Theme', 'project.emulsify.json should update project.name.'); + assert(project.project.machineName === 'acme-theme', 'project.emulsify.json should update project.machineName.'); + assert( + project.starter.repository === 'https://github.com/emulsify-ds/emulsify-wordpress-starter', + 'project.emulsify.json should keep the standalone starter repository.', + ); + assert(functionsPhp.includes('Acme Theme child theme hooks.'), 'functions.php should update visible Whisk labels.'); + assert(pageTwig.includes('acme-theme-page'), 'templates/page.twig should update the page class.'); + assert(!pageTwig.includes('whisk-page'), 'templates/page.twig should not keep the starter page class.'); + assert(pattern.name === 'acme-theme/smoke-pattern', 'JSON pattern names should update the whisk namespace.'); + assert(fs.existsSync(path.join(target, '.cli/init.js')), 'Starter should include the emulsify-cli init hook.'); + assert(fs.existsSync(path.join(target, '.gitignore')), 'Starter should include standalone ignore rules.'); + assert(fs.existsSync(path.join(target, '.nvmrc')), 'Starter should use .nvmrc for Node tooling.'); + assert(!fs.existsSync(path.join(target, '.nvm')), 'Starter should not keep the legacy .nvm file.'); + + for (const generatedPath of ['node_modules', 'dist', '.out', '.coverage', '.cache', '.git']) { + assert( + !fs.existsSync(path.join(target, generatedPath)), + `Cloned starter fixture should not contain copied ${generatedPath} output.`, + ); + } + + runWhiskTestScript(); + + console.log('WordPress starter init smoke checks passed.'); +} catch (error) { + console.error(error.message); + process.exitCode = 1; +} finally { + removePath(workRoot); +} diff --git a/README.md b/README.md index 7d13820..02bba4c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ![Emulsify Design System](https://github.com/emulsify-ds/.github/blob/6bd435be881bd820bddfa05d88905efe29176a0a/assets/images/header.png) -# Emulsify WordPress +# Emulsify Wordpress -Emulsify WordPress 2.0.0 is a Timber-first WordPress parent theme for teams building component-driven sites with Emulsify Core 4, Vite, Storybook, and Twig. +Emulsify Wordpress 2.0.0 is a Timber-first WordPress parent theme for teams building component-driven sites with Emulsify Core 4, Vite, Storybook, and Twig. The parent theme provides the WordPress runtime: theme setup, Timber bootstrapping, Twig namespaces and helpers, template fallbacks, asset loading, and optional block registration. Generated child themes provide the project layer: components, templates, source Sass and JavaScript, compiled assets, and site-specific overrides. @@ -115,6 +115,8 @@ wp emulsify "Acme Site" --machine-name=acme-site --activate The generator copies `emulsify/whisk` to a sibling child theme directory, updates WordPress theme headers, package metadata, Emulsify project metadata, and visible starter labels, then optionally activates the generated child theme. +For Emulsify CLI integration, the standalone starter repository is `https://github.com/emulsify-ds/emulsify-wordpress-starter`. It represents the generated child theme layer from `whisk/`, not the parent runtime theme root, and generated projects still declare `Template: emulsify` so WordPress loads the installed parent theme. + ## Documentation - [Upgrading from 1.x to 2.x](docs/upgrading-1x-to-2x.md) @@ -135,11 +137,11 @@ The generator copies `emulsify/whisk` to a sibling child theme directory, update ## License -Emulsify WordPress is licensed under GPL-2.0-only. See [LICENSE](LICENSE). +Emulsify Wordpress is licensed under GPL-2.0-only. See [LICENSE](LICENSE). ## Contributing -Read the [Code of Conduct](https://github.com/emulsify-ds/emulsify-wordpress-theme/blob/main/CODE_OF_CONDUCT.md) before contributing. File bugs and feature requests at [emulsify-ds/emulsify-wordpress-theme](https://github.com/emulsify-ds/emulsify-wordpress-theme/issues). +Read the [Code of Conduct](https://github.com/emulsify-ds/emulsify-wordpress/blob/main/CODE_OF_CONDUCT.md) before contributing. File bugs and feature requests at [emulsify-ds/emulsify-wordpress](https://github.com/emulsify-ds/emulsify-wordpress/issues). ## Author diff --git a/composer.json b/composer.json index 46431e3..0f4bc86 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "emulsify-ds/emulsify-wordpress-theme", + "name": "emulsify-ds/emulsify-wordpress", "description": "Timber-first WordPress parent theme for Emulsify that generates child themes with Emulsify Core 4, Vite, Storybook, and Twig", "type": "wordpress-theme", "homepage": "https://www.emulsify.info", diff --git a/docs/core-4-vite-workflow.md b/docs/core-4-vite-workflow.md index 25e6d55..afa8f08 100644 --- a/docs/core-4-vite-workflow.md +++ b/docs/core-4-vite-workflow.md @@ -29,6 +29,8 @@ The WordPress runtime also reads that shape so `{% include "@atoms/example/examp The generated child theme intentionally does not ship a concrete component library. Emulsify CLI can install the component system a project chooses, and that system should own the source tree, Sass entrypoints, stories, and fixture data. +For Emulsify CLI initialization, `whisk/` is also the source for the standalone `emulsify-wordpress-starter` repository. The starter hook runs after CLI dependency installation and updates package metadata, WordPress theme headers, project metadata, and optional JSON pattern namespaces for the generated project. + Whisk keeps `src/components/.gitkeep` only as a placeholder for compatible systems. It does not include default `tokens.scss`, `foundation.scss`, or `layout.scss` files. Whisk keeps empty `assets/images` and `assets/icons` directories as project-owned theme asset placeholders. Use these for source files that belong to the theme repository, not for uploaded media library files. The optional `assets/fonts` placeholder is available when a project owns local font files. diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index f5d0bf3..2dfe4c2 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -1,6 +1,6 @@ # Parent and child theme architecture -Emulsify WordPress is split into a reusable parent theme and generated project child themes. The goal is to keep parent runtime behavior stable while allowing each project to own its implementation details. +Emulsify Wordpress is split into a reusable parent theme and generated project child themes. The goal is to keep parent runtime behavior stable while allowing each project to own its implementation details. ## Parent theme responsibilities diff --git a/docs/release-process.md b/docs/release-process.md index c66ea30..9e3e4d7 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -50,7 +50,7 @@ Pull requests run pragmatic checks: - Composer metadata validation. - Runtime and full npm audits. - PHP linting. -- The practical `pr:check` suite, including WP-CLI child theme generation smoke coverage and Whisk dependency installation. +- The practical `pr:check` suite, including WP-CLI child theme generation smoke coverage, Whisk dependency installation, and standalone starter init smoke coverage for Emulsify CLI. - Static release readiness checks through `release:check`. Normal pull requests do not start MySQL or run the full WordPress fixture. Manual and scheduled runs execute the full WordPress fixture smoke test with WP-CLI and MySQL so heavier runtime coverage stays available without slowing every pull request. diff --git a/docs/sister-project-parity.md b/docs/sister-project-parity.md index e00cbc6..add6164 100644 --- a/docs/sister-project-parity.md +++ b/docs/sister-project-parity.md @@ -1,12 +1,12 @@ # Sister-project parity contract -Emulsify WordPress is the WordPress sister project to Emulsify Drupal. The projects should feel familiar to teams moving between CMS platforms while preserving the runtime conventions each CMS expects. +Emulsify Wordpress is the WordPress sister project to Emulsify Drupal. The projects should feel familiar to teams moving between CMS platforms while preserving the runtime conventions each CMS expects. This contract defines the shared Emulsify model first, then the intentional WordPress differences. ## Shared Emulsify contract -Emulsify WordPress and Emulsify Drupal share the same project boundary: +Emulsify Wordpress and Emulsify Drupal share the same project boundary: - The parent theme owns reusable CMS runtime behavior. - The generated child theme owns project implementation. @@ -33,7 +33,7 @@ WordPress should load built child theme output before parent fallback output. A ## Intentional WordPress differences -Emulsify WordPress is not a Drupal runtime port. It keeps parity at the Emulsify project-model layer and uses WordPress-native integration where WordPress needs it. +Emulsify Wordpress is not a Drupal runtime port. It keeps parity at the Emulsify project-model layer and uses WordPress-native integration where WordPress needs it. ### Timber runtime @@ -73,6 +73,8 @@ wp emulsify "Acme Site" --machine-name=acme-site The command copies Whisk to a sibling child theme and performs targeted metadata updates. It avoids broad recursive string replacement so generated projects keep predictable WordPress headers, package metadata, and Emulsify project metadata. +Emulsify CLI uses the standalone `https://github.com/emulsify-ds/emulsify-wordpress-starter` repository for the same child theme layer. That starter is sourced from `whisk/`, runs `.cli/init.js` after CLI dependency installation, and keeps `Template: emulsify` so WordPress loads the parent runtime theme. + ### Emulsify platform metadata `project.emulsify.json` uses `"platform": "wordpress"` so Emulsify Core and Emulsify CLI tooling can load the WordPress platform adapter. WordPress runtime behavior still lives in this parent theme for the 2.x release line. diff --git a/docs/timber-and-twig-authoring.md b/docs/timber-and-twig-authoring.md index 2cb5047..873c5d7 100644 --- a/docs/timber-and-twig-authoring.md +++ b/docs/timber-and-twig-authoring.md @@ -1,6 +1,6 @@ # Timber and Twig authoring -Timber provides the bridge between WordPress data and Twig templates. Emulsify WordPress keeps Twig authoring predictable by registering a small set of namespaces and helper functions from the parent theme. +Timber provides the bridge between WordPress data and Twig templates. Emulsify Wordpress keeps Twig authoring predictable by registering a small set of namespaces and helper functions from the parent theme. ## Bedrock and Timber diff --git a/docs/upgrading-1x-to-2x.md b/docs/upgrading-1x-to-2x.md index ff6031e..0c9a5d1 100644 --- a/docs/upgrading-1x-to-2x.md +++ b/docs/upgrading-1x-to-2x.md @@ -1,6 +1,6 @@ # Upgrading from 1.x to 2.x -Emulsify WordPress 2.x changes the project model. The repository is now a Timber-first WordPress parent theme that ships a generated child theme starter. Projects should customize the child theme instead of editing parent runtime classes. +Emulsify Wordpress 2.x changes the project model. The repository is now a Timber-first WordPress parent theme that ships a generated child theme starter. Projects should customize the child theme instead of editing parent runtime classes. ## What changed diff --git a/docs/wp-cli-child-theme-generation.md b/docs/wp-cli-child-theme-generation.md index 05d4b97..9b2c7b1 100644 --- a/docs/wp-cli-child-theme-generation.md +++ b/docs/wp-cli-child-theme-generation.md @@ -37,6 +37,30 @@ The generator avoids broad blind string replacement. It targets known metadata f Ignored dependency, cache, Storybook, and Vite output directories are not copied. A generated child theme should install its own dependencies, install or configure the chosen Emulsify component system, and create its own `dist` output. +## Emulsify CLI starter hook + +Emulsify CLI uses the standalone starter repository at `https://github.com/emulsify-ds/emulsify-wordpress-starter`. That repository should be built from this branch's `whisk/` directory, which is the generated child theme layer rather than the parent runtime theme root. + +The starter includes `.cli/init.js` for CLI initialization. Current Emulsify CLI timing is: + +1. Clone the starter. +2. Write `project.emulsify.json`. +3. Run `npm install`. +4. Execute `.cli/init.js`. + +Because `npm install` runs before the hook, the hook updates package metadata and the generated `package-lock.json` when the lockfile exists. A pre-install CLI hook is not required for the current targeted metadata updates. + +The hook deliberately mirrors the parent WP-CLI generator's targeted updates: + +- `style.css` `Theme Name`, `Text Domain`, and `Template`. +- `package.json` name and generated package lockfile root name. +- `project.emulsify.json` project name and machine name, while keeping `"platform": "wordpress"`. +- Visible Whisk labels in known starter files. +- `templates/page.twig` class from `whisk-page` to the generated machine-name class. +- JSON pattern names from the `whisk/*` namespace to the generated machine-name namespace. + +The generated child theme must keep `Template: emulsify`; the parent theme remains installed separately as `emulsify`. + ## After generation Install dependencies and build assets in the generated child theme: diff --git a/package-lock.json b/package-lock.json index dfdfec5..e56737c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "emulsify-wordpress-theme", + "name": "emulsify-wordpress", "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "emulsify-wordpress-theme", + "name": "emulsify-wordpress", "version": "2.0.0", "license": "GPL-2.0-only", "devDependencies": { diff --git a/package.json b/package.json index 435978e..f45ea71 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "emulsify-wordpress-theme", + "name": "emulsify-wordpress", "version": "2.0.0", "description": "Timber-first WordPress parent theme that generates child themes with Emulsify Core 4, Vite, Storybook, and Twig", "engines": { @@ -17,10 +17,10 @@ "license": "GPL-2.0-only", "repository": { "type": "git", - "url": "git+https://github.com/emulsify-ds/emulsify-wordpress-theme.git" + "url": "git+https://github.com/emulsify-ds/emulsify-wordpress.git" }, "bugs": { - "url": "https://github.com/emulsify-ds/emulsify-wordpress-theme/issues" + "url": "https://github.com/emulsify-ds/emulsify-wordpress/issues" }, "homepage": "https://www.emulsify.info", "scripts": { @@ -40,6 +40,7 @@ "smoke:editor-enhancements": "php .github/scripts/editor-enhancements-smoke.php", "smoke:editor-policy": "php .github/scripts/editor-policy-smoke.php", "smoke:patterns": "php .github/scripts/pattern-registry-smoke.php", + "smoke:starter-init": "node .github/scripts/wordpress-starter-init-smoke.cjs", "smoke:theme-filters": "php .github/scripts/theme-filters-smoke.php", "smoke:twig-project-namespace": "php .github/scripts/twig-project-namespace-smoke.php", "whisk:build": "npm --prefix whisk run build", diff --git a/release.config.js b/release.config.js index 888dac3..8b3f296 100644 --- a/release.config.js +++ b/release.config.js @@ -62,7 +62,7 @@ module.exports = { expectedStableRelease, tagFormat: '${version}', branches: ['main'], - repositoryUrl: 'git@github.com:emulsify-ds/emulsify-wordpress-theme.git', + repositoryUrl: 'git@github.com:emulsify-ds/emulsify-wordpress.git', plugins: [ ['@semantic-release/commit-analyzer', { parserOpts }], ['@semantic-release/release-notes-generator', { parserOpts }], diff --git a/whisk/.cli/init.js b/whisk/.cli/init.js new file mode 100644 index 0000000..5ade121 --- /dev/null +++ b/whisk/.cli/init.js @@ -0,0 +1,176 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const STARTER_SLUG = 'whisk'; +const PARENT_THEME = 'emulsify'; +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); + +const projectConfigPath = path.join(ROOT, 'project.emulsify.json'); + +const isPlainObject = (value) => + value !== null && typeof value === 'object' && !Array.isArray(value); + +const readText = (filePath) => fs.readFileSync(filePath, 'utf8'); + +const writeTextIfChanged = (filePath, contents) => { + if (readText(filePath) !== contents) { + fs.writeFileSync(filePath, contents); + } +}; + +const readJson = (filePath) => JSON.parse(readText(filePath)); + +const writeJsonIfChanged = (filePath, data) => { + const contents = `${JSON.stringify(data, null, 2)}\n`; + writeTextIfChanged(filePath, contents); +}; + +const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +const replaceThemeHeader = (contents, field, value) => { + const pattern = new RegExp(`^(\\s*\\*\\s*${escapeRegExp(field)}:\\s*).*$`, 'mi'); + let found = false; + const updated = contents.replace(pattern, (_match, prefix) => { + found = true; + return `${prefix}${value}`; + }); + + if (!found) { + throw new Error(`Could not update "${field}" in style.css.`); + } + + return updated; +}; + +const getProjectConfig = () => { + const config = readJson(projectConfigPath); + + if (!isPlainObject(config.project)) { + throw new Error('project.emulsify.json must contain a project object.'); + } + + const { name, machineName } = config.project; + + if (typeof name !== 'string' || name.trim() === '') { + throw new Error('project.emulsify.json project.name must be a non-empty string.'); + } + + if (typeof machineName !== 'string' || machineName.trim() === '') { + throw new Error('project.emulsify.json project.machineName must be a non-empty string.'); + } + + return config; +}; + +const updateStyleCss = ({ name, machineName }) => { + const filePath = path.join(ROOT, 'style.css'); + let contents = readText(filePath); + + contents = replaceThemeHeader(contents, 'Theme Name', name); + contents = replaceThemeHeader(contents, 'Text Domain', machineName); + contents = replaceThemeHeader(contents, 'Template', PARENT_THEME); + + writeTextIfChanged(filePath, contents); +}; + +const updatePackageJson = ({ machineName }) => { + const filePath = path.join(ROOT, 'package.json'); + const data = readJson(filePath); + + data.name = machineName; + + writeJsonIfChanged(filePath, data); +}; + +const updateLockfile = (relativePath, { machineName }) => { + const filePath = path.join(ROOT, relativePath); + + if (!fs.existsSync(filePath)) { + return; + } + + const data = readJson(filePath); + + data.name = machineName; + + if (isPlainObject(data.packages) && isPlainObject(data.packages[''])) { + data.packages[''].name = machineName; + } + + writeJsonIfChanged(filePath, data); +}; + +const updateProjectConfig = (config, { name, machineName }) => { + config.project.platform = 'wordpress'; + config.project.name = name; + config.project.machineName = machineName; + + writeJsonIfChanged(projectConfigPath, config); +}; + +const updateFunctionsPhp = ({ name }) => { + const filePath = path.join(ROOT, 'functions.php'); + const contents = readText(filePath).replace( + 'Whisk child theme hooks.', + `${name} child theme hooks.`, + ); + + writeTextIfChanged(filePath, contents); +}; + +const updatePageTemplate = ({ machineName }) => { + const filePath = path.join(ROOT, 'templates/page.twig'); + const contents = readText(filePath).replaceAll( + `${STARTER_SLUG}-page`, + `${machineName}-page`, + ); + + writeTextIfChanged(filePath, contents); +}; + +const walkJsonFiles = (directory) => { + if (!fs.existsSync(directory)) { + return []; + } + + return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const filePath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + return walkJsonFiles(filePath); + } + + return entry.isFile() && entry.name.endsWith('.json') ? [filePath] : []; + }); +}; + +const updatePatternNamespaces = ({ machineName }) => { + for (const filePath of walkJsonFiles(path.join(ROOT, 'patterns'))) { + const data = readJson(filePath); + + if (typeof data.name === 'string' && data.name.startsWith(`${STARTER_SLUG}/`)) { + data.name = `${machineName}/${data.name.slice(STARTER_SLUG.length + 1)}`; + writeJsonIfChanged(filePath, data); + } + } +}; + +const main = () => { + const config = getProjectConfig(); + const project = { + name: config.project.name, + machineName: config.project.machineName, + }; + + updateStyleCss(project); + updatePackageJson(project); + updateLockfile('package-lock.json', project); + updateLockfile('npm-shrinkwrap.json', project); + updateProjectConfig(config, project); + updateFunctionsPhp(project); + updatePageTemplate(project); + updatePatternNamespaces(project); +}; + +main(); diff --git a/whisk/.gitignore b/whisk/.gitignore new file mode 100644 index 0000000..7cf6521 --- /dev/null +++ b/whisk/.gitignore @@ -0,0 +1,14 @@ +# Dependencies +node_modules + +# Build output +dist +.out + +# Test and cache output +.coverage +.cache + +# Local files +.DS_Store +.env diff --git a/whisk/.nvm b/whisk/.nvm deleted file mode 100644 index cabf43b..0000000 --- a/whisk/.nvm +++ /dev/null @@ -1 +0,0 @@ -24 \ No newline at end of file diff --git a/whisk/.nvmrc b/whisk/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/whisk/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/whisk/config/jest.config.js b/whisk/config/jest.config.js new file mode 100644 index 0000000..1a43401 --- /dev/null +++ b/whisk/config/jest.config.js @@ -0,0 +1,9 @@ +export default { + coverageDirectory: '/.coverage', + testEnvironment: 'node', + testMatch: [ + '/src/**/*.test.{js,cjs,mjs}', + '/components/**/*.test.{js,cjs,mjs}', + '/config/**/*.test.{js,cjs,mjs}', + ], +}; diff --git a/whisk/project.emulsify.json b/whisk/project.emulsify.json index b1f47c9..1470922 100644 --- a/whisk/project.emulsify.json +++ b/whisk/project.emulsify.json @@ -5,6 +5,6 @@ "machineName": "whisk" }, "starter": { - "repository": "https://github.com/emulsify-ds/emulsify-wordpress-theme/" + "repository": "https://github.com/emulsify-ds/emulsify-wordpress-starter" } } From 0ac719e400baf618593caa6e69914b2d26aea942 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 09:20:25 -0500 Subject: [PATCH 30/55] chore: adjust wordpress spelling and repo url references --- .github/ISSUE_TEMPLATE.md | 2 +- .github/scripts/release-check.cjs | 22 +++++++++------------- README.md | 6 +++--- docs/parent-child-architecture.md | 2 +- docs/sister-project-parity.md | 6 +++--- docs/timber-and-twig-authoring.md | 2 +- docs/upgrading-1x-to-2x.md | 2 +- 7 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 7e2fead..6ce0342 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,4 @@ -Emulsify Wordpress version (see [releases](https://github.com/emulsify-ds/emulsify-wordpress/releases)): +Emulsify WordPress version (see [releases](https://github.com/emulsify-ds/emulsify-wordpress/releases)): **What you did:** diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 32e079b..5cc26f3 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -178,15 +178,11 @@ function ensureNoTitleCaseBuildPhrase(label, value) { ensure(!/Webpack Build|Vite Build/.test(value), `${label} should not use title-case build workflow phrases.`); } -function ensureWordPressLanguage(label, value) { - const normalizedValue = normalizeProductTitle(value); - - ensure(normalizedValue.includes('WordPress'), `${label} should use the canonical WordPress spelling.`); - ensure(!/Wordpress/.test(normalizedValue), `${label} should not use Wordpress outside the Emulsify Wordpress product title.`); -} +const incorrectWordPressPattern = new RegExp('Word' + 'press'); -function normalizeProductTitle(value) { - return String(value).replace(/\bEmulsify Wordpress\b/g, 'Emulsify WordPress'); +function ensureWordPressLanguage(label, value) { + ensure(value.includes('WordPress'), `${label} should use the canonical WordPress spelling.`); + ensure(!incorrectWordPressPattern.test(value), `${label} should use the canonical WordPress spelling.`); } function ensureViteLanguage(label, value) { @@ -834,8 +830,8 @@ function runStaticChecks() { 'docs/release-process.md', ]; - ensure(readme.includes('Emulsify Wordpress 2.0.0 is a Timber-first WordPress parent theme'), 'README.md should describe the 2.0.0 Timber-first parent theme.'); - ensure(readme.includes('Emulsify Wordpress is licensed under GPL-2.0-only'), 'README.md should document the GPL-2.0-only license.'); + ensure(readme.includes('Emulsify WordPress 2.0.0 is a Timber-first WordPress parent theme'), 'README.md should describe the 2.0.0 Timber-first parent theme.'); + ensure(readme.includes('Emulsify WordPress is licensed under GPL-2.0-only'), 'README.md should document the GPL-2.0-only license.'); ensure(readme.includes('[LICENSE](LICENSE)'), 'README.md should link to the repository license file.'); ensure(readme.includes('## Requirements'), 'README.md should keep requirements visible.'); ensure(readme.includes('## Quick install'), 'README.md should keep quick install guidance visible.'); @@ -851,8 +847,8 @@ function runStaticChecks() { ensure(readme.includes(docLink), `README.md should link to ${docLink}.`); } - ensure(docs.upgrading.includes('Emulsify Wordpress 2.x changes the project model'), 'Upgrade doc should explain the 2.x project model.'); - ensure(docs.parity.includes('Emulsify Wordpress is the WordPress sister project to Emulsify Drupal'), 'Sister-project parity doc should name the Drupal sister project.'); + ensure(docs.upgrading.includes('Emulsify WordPress 2.x changes the project model'), 'Upgrade doc should explain the 2.x project model.'); + ensure(docs.parity.includes('Emulsify WordPress is the WordPress sister project to Emulsify Drupal'), 'Sister-project parity doc should name the Drupal sister project.'); ensure(docs.parity.includes('The parent theme owns reusable CMS runtime behavior'), 'Sister-project parity doc should define parent runtime ownership.'); ensure(docs.parity.includes('The generated child theme owns project implementation'), 'Sister-project parity doc should define child theme ownership.'); ensure(docs.parity.includes('Whisk is the starter'), 'Sister-project parity doc should define Whisk as the starter.'); @@ -918,7 +914,7 @@ function runStaticChecks() { ensure(/duplicate[\w\s/`.-]*skipped instead of being registered twice/i.test(docsText), 'Docs should document duplicate block handling.'); ensure(docsText.includes('normal frontend visitors') || docsText.includes('Normal frontend visitors'), 'Docs should document that duplicate diagnostics avoid frontend noise.'); ensure(!/Webpack/i.test(`${readme}\n${docsText}`), 'Docs should not mention Webpack.'); - ensure(!/Wordpress/.test(normalizeProductTitle(`${readme}\n${docsText}`)), 'Docs should use the canonical WordPress spelling outside the Emulsify Wordpress product title.'); + ensure(!incorrectWordPressPattern.test(`${readme}\n${docsText}`), 'Docs should use the canonical WordPress spelling.'); ensure(issueTemplate.includes('emulsify-wordpress/releases'), 'Issue template should link to WordPress theme releases.'); ensure(pullRequestTemplate.includes('emulsify-wordpress/issues/1'), 'Pull request template should link to WordPress theme issues.'); ensure(!/emulsify-drupal/.test(`${issueTemplate}\n${pullRequestTemplate}`), 'GitHub templates should not link to the Drupal repository.'); diff --git a/README.md b/README.md index 02bba4c..93dcfd2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ![Emulsify Design System](https://github.com/emulsify-ds/.github/blob/6bd435be881bd820bddfa05d88905efe29176a0a/assets/images/header.png) -# Emulsify Wordpress +# Emulsify WordPress -Emulsify Wordpress 2.0.0 is a Timber-first WordPress parent theme for teams building component-driven sites with Emulsify Core 4, Vite, Storybook, and Twig. +Emulsify WordPress 2.0.0 is a Timber-first WordPress parent theme for teams building component-driven sites with Emulsify Core 4, Vite, Storybook, and Twig. The parent theme provides the WordPress runtime: theme setup, Timber bootstrapping, Twig namespaces and helpers, template fallbacks, asset loading, and optional block registration. Generated child themes provide the project layer: components, templates, source Sass and JavaScript, compiled assets, and site-specific overrides. @@ -137,7 +137,7 @@ For Emulsify CLI integration, the standalone starter repository is `https://gith ## License -Emulsify Wordpress is licensed under GPL-2.0-only. See [LICENSE](LICENSE). +Emulsify WordPress is licensed under GPL-2.0-only. See [LICENSE](LICENSE). ## Contributing diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index 2dfe4c2..f5d0bf3 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -1,6 +1,6 @@ # Parent and child theme architecture -Emulsify Wordpress is split into a reusable parent theme and generated project child themes. The goal is to keep parent runtime behavior stable while allowing each project to own its implementation details. +Emulsify WordPress is split into a reusable parent theme and generated project child themes. The goal is to keep parent runtime behavior stable while allowing each project to own its implementation details. ## Parent theme responsibilities diff --git a/docs/sister-project-parity.md b/docs/sister-project-parity.md index add6164..fba5756 100644 --- a/docs/sister-project-parity.md +++ b/docs/sister-project-parity.md @@ -1,12 +1,12 @@ # Sister-project parity contract -Emulsify Wordpress is the WordPress sister project to Emulsify Drupal. The projects should feel familiar to teams moving between CMS platforms while preserving the runtime conventions each CMS expects. +Emulsify WordPress is the WordPress sister project to Emulsify Drupal. The projects should feel familiar to teams moving between CMS platforms while preserving the runtime conventions each CMS expects. This contract defines the shared Emulsify model first, then the intentional WordPress differences. ## Shared Emulsify contract -Emulsify Wordpress and Emulsify Drupal share the same project boundary: +Emulsify WordPress and Emulsify Drupal share the same project boundary: - The parent theme owns reusable CMS runtime behavior. - The generated child theme owns project implementation. @@ -33,7 +33,7 @@ WordPress should load built child theme output before parent fallback output. A ## Intentional WordPress differences -Emulsify Wordpress is not a Drupal runtime port. It keeps parity at the Emulsify project-model layer and uses WordPress-native integration where WordPress needs it. +Emulsify WordPress is not a Drupal runtime port. It keeps parity at the Emulsify project-model layer and uses WordPress-native integration where WordPress needs it. ### Timber runtime diff --git a/docs/timber-and-twig-authoring.md b/docs/timber-and-twig-authoring.md index 873c5d7..2cb5047 100644 --- a/docs/timber-and-twig-authoring.md +++ b/docs/timber-and-twig-authoring.md @@ -1,6 +1,6 @@ # Timber and Twig authoring -Timber provides the bridge between WordPress data and Twig templates. Emulsify Wordpress keeps Twig authoring predictable by registering a small set of namespaces and helper functions from the parent theme. +Timber provides the bridge between WordPress data and Twig templates. Emulsify WordPress keeps Twig authoring predictable by registering a small set of namespaces and helper functions from the parent theme. ## Bedrock and Timber diff --git a/docs/upgrading-1x-to-2x.md b/docs/upgrading-1x-to-2x.md index 0c9a5d1..ff6031e 100644 --- a/docs/upgrading-1x-to-2x.md +++ b/docs/upgrading-1x-to-2x.md @@ -1,6 +1,6 @@ # Upgrading from 1.x to 2.x -Emulsify Wordpress 2.x changes the project model. The repository is now a Timber-first WordPress parent theme that ships a generated child theme starter. Projects should customize the child theme instead of editing parent runtime classes. +Emulsify WordPress 2.x changes the project model. The repository is now a Timber-first WordPress parent theme that ships a generated child theme starter. Projects should customize the child theme instead of editing parent runtime classes. ## What changed From afd7d437f1cd9323d1804ba0f0e953790bed5629 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 09:22:56 -0500 Subject: [PATCH 31/55] fix: tighten composer stability settings --- .github/scripts/release-check.cjs | 3 +++ composer.json | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 5cc26f3..776d108 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -391,6 +391,9 @@ function runStaticChecks() { ensure(composer.type === 'wordpress-theme', 'composer.json type should be wordpress-theme.'); ensure(composer.license === 'GPL-2.0-only', 'composer.json license should be GPL-2.0-only.'); ensure(composer.homepage === 'https://www.emulsify.info', 'composer.json homepage should use the canonical HTTPS URL.'); + ensure(!Object.hasOwn(composer, 'minimum-stability'), 'composer.json should not lower release stability for a stable parent theme.'); + ensure(!Object.hasOwn(composer, 'prefer-stable'), 'composer.json should not keep prefer-stable when stable-only constraints are sufficient.'); + ensure(composer.require && composer.require['timber/timber'] === '^2.3', 'composer.json should keep the Timber 2 dependency constraint.'); ensureParentThemeLanguage('composer.json description', composer.description); return `Validated root package ${rootPackage.version} and composer metadata.`; }); diff --git a/composer.json b/composer.json index 0f4bc86..263a44e 100644 --- a/composer.json +++ b/composer.json @@ -14,8 +14,6 @@ "email": "mike.goulding@fourkitchens.com" } ], - "minimum-stability": "dev", - "prefer-stable": true, "require": { "timber/timber": "^2.3" }, From 4daaf960ee0183357844cccd08b35ba1503b17e7 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 09:27:39 -0500 Subject: [PATCH 32/55] docs: clarify wordpress install workflows --- .github/scripts/release-check.cjs | 9 +++-- README.md | 57 +++++++++++++++++-------------- docs/timber-and-twig-authoring.md | 4 +-- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 776d108..71f0d55 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -837,10 +837,15 @@ function runStaticChecks() { ensure(readme.includes('Emulsify WordPress is licensed under GPL-2.0-only'), 'README.md should document the GPL-2.0-only license.'); ensure(readme.includes('[LICENSE](LICENSE)'), 'README.md should link to the repository license file.'); ensure(readme.includes('## Requirements'), 'README.md should keep requirements visible.'); - ensure(readme.includes('## Quick install'), 'README.md should keep quick install guidance visible.'); + ensure(readme.includes('## Using Emulsify WordPress in a site project'), 'README.md should keep site project usage guidance visible.'); + ensure(readme.includes('## Working inside a generated child theme'), 'README.md should keep child theme workflow guidance visible.'); ensure(readme.includes('## Parent and child themes'), 'README.md should keep the parent/child overview visible.'); - ensure(readme.includes('## Basic commands'), 'README.md should keep root commands visible.'); + ensure(readme.includes('## Developing or releasing the parent theme'), 'README.md should keep parent maintainer commands visible.'); ensure(readme.includes('## Documentation'), 'README.md should link to deeper docs.'); + ensure(readme.includes('Do not run root npm commands in the parent theme for normal site implementation'), 'README.md should distinguish parent npm tooling from project runtime work.'); + ensure(readme.includes('Require `timber/timber` from the application-level Composer project'), 'README.md should document application-level Timber installation.'); + ensure(readme.includes('npm ci --ignore-scripts'), 'README.md should document parent maintainer npm install.'); + ensure(readme.includes('composer install'), 'README.md should document Composer install usage.'); ensure(readme.includes('whisk/project.emulsify.json') && readme.includes('"platform": "wordpress"'), 'README.md should explain the current project.emulsify.json platform setting.'); ensure(readme.includes('whisk/assets/images') && readme.includes('whisk/assets/icons'), 'README.md should document the generated child asset placeholders.'); ensure(readme.includes('wp emulsify "Acme Site" --machine-name=acme-site'), 'README.md should document child theme generator examples.'); diff --git a/README.md b/README.md index 93dcfd2..1a75ba6 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ The parent theme provides the WordPress runtime: theme setup, Timber bootstrappi - Timber 2, preferably installed with Composer. - WP-CLI when generating child themes or running the full WordPress fixture smoke test. -## Quick install +## Using Emulsify WordPress in a site project -Install the parent theme as `emulsify` and activate a child theme for project work. In Bedrock, that usually means: +Install the parent theme as `emulsify` and pair it with a child theme for project work. In Bedrock, that usually means: ```sh web/app/themes/emulsify @@ -31,22 +31,41 @@ wp-content/themes/emulsify wp-content/themes/whisk ``` -Install parent theme dependencies: +Timber 2 must be loaded before the theme renders. Site projects can satisfy that requirement in either place: + +- Require `timber/timber` from the application-level Composer project. +- Run Composer inside the parent theme when the parent theme owns its PHP dependencies: ```sh cd web/app/themes/emulsify composer install -npm ci --ignore-scripts ``` -Install the generated child theme frontend dependencies: +Activate the child theme, not the parent theme. The child theme header includes `Template: emulsify`, which tells WordPress to use Emulsify as the parent runtime. + +Do not run root npm commands in the parent theme for normal site implementation. Project frontend work happens in the generated child theme. + +## Working inside a generated child theme + +Run component, Vite, Storybook, and project lint commands from the generated child theme: ```sh cd web/app/themes/whisk npm install ``` -Activate the child theme, not the parent theme. The child theme header includes `Template: emulsify`, which tells WordPress to use Emulsify as the parent runtime. +| Command | Purpose | +| --- | --- | +| `npm run build` | Build Core 4 assets with Vite. | +| `npm run vite` | Watch and rebuild Vite assets. | +| `npm run storybook` | Start Storybook on port 6006. | +| `npm run develop` | Run the Vite watcher and Storybook together. | +| `npm run storybook-build` | Build assets and export a static Storybook. | +| `npm run lint` | Run JavaScript and Sass linting with the Core 4 config. | +| `npm run audit` | Run the Core migration/static audit. | +| `npm run audit:twig-stories` | Check Twig story compatibility. | +| `npm run a11y` | Build Storybook and run the Core accessibility check. | +| `npm run test` | Run Jest with `--passWithNoTests` for starter projects. | ## Parent and child themes @@ -72,9 +91,14 @@ Timber is required for frontend template rendering. This repository declares `ti For Bedrock applications, it is also valid to require Timber from the application-level Composer project as long as WordPress loads that Composer autoloader before the theme renders. If Timber is missing, the parent theme shows an actionable admin notice and stops frontend rendering with a clear runtime error. -## Basic commands +## Developing or releasing the parent theme -Run parent theme checks from the repository root: +Parent-theme root commands are for maintainers and release checks, not normal project frontend development. Run them from the parent theme repository root: + +```sh +composer install +npm ci --ignore-scripts +``` | Command | Purpose | | --- | --- | @@ -85,23 +109,6 @@ Run parent theme checks from the repository root: `release:check` includes the full WordPress fixture smoke path. It skips gracefully when WP-CLI or database settings are unavailable, and fails on missing fixture prerequisites when `WP_SMOKE_REQUIRED=1` is set. -## Core 4, Vite, and Storybook commands - -Run component development commands from the generated child theme: - -| Command | Purpose | -| --- | --- | -| `npm run build` | Build Core 4 assets with Vite. | -| `npm run vite` | Watch and rebuild Vite assets. | -| `npm run storybook` | Start Storybook on port 6006. | -| `npm run develop` | Run the Vite watcher and Storybook together. | -| `npm run storybook-build` | Build assets and export a static Storybook. | -| `npm run lint` | Run JavaScript and Sass linting with the Core 4 config. | -| `npm run audit` | Run the Core migration/static audit. | -| `npm run audit:twig-stories` | Check Twig story compatibility. | -| `npm run a11y` | Build Storybook and run the Core accessibility check. | -| `npm run test` | Run Jest with `--passWithNoTests` for starter projects. | - ## Generate a child theme Generate a project child theme from the bundled Whisk starter with WP-CLI: diff --git a/docs/timber-and-twig-authoring.md b/docs/timber-and-twig-authoring.md index 2cb5047..d75f899 100644 --- a/docs/timber-and-twig-authoring.md +++ b/docs/timber-and-twig-authoring.md @@ -4,14 +4,14 @@ Timber provides the bridge between WordPress data and Twig templates. Emulsify W ## Bedrock and Timber -Timber is required for frontend rendering. A standalone theme install can use the parent theme `composer.json`: +Timber is required for frontend rendering. Site projects can install Timber from the application-level Composer project or from the parent theme `composer.json`. If the parent theme owns the dependency, run: ```sh cd web/app/themes/emulsify composer install ``` -In Bedrock, it is also valid to install Timber from the application-level Composer project as long as WordPress loads that Composer autoloader before the theme renders. +In Bedrock, prefer the application-level Composer project when that is where PHP dependencies are managed. Either approach works as long as WordPress loads the Composer autoloader before the theme renders. If Timber is missing, the parent theme shows an admin notice and stops frontend rendering with a clear runtime error. From cd9249ed572927d0af3987d3e22fade32607a28c Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 09:34:51 -0500 Subject: [PATCH 33/55] fix: guard child theme force replacement --- .../scripts/child-theme-generator-smoke.php | 39 ++++++- .github/scripts/release-check.cjs | 5 + README.md | 2 + docs/wp-cli-child-theme-generation.md | 13 ++- includes/class-cli.php | 107 +++++++++++++++++- 5 files changed, 162 insertions(+), 4 deletions(-) diff --git a/.github/scripts/child-theme-generator-smoke.php b/.github/scripts/child-theme-generator-smoke.php index 7b57582..eed6df9 100644 --- a/.github/scripts/child-theme-generator-smoke.php +++ b/.github/scripts/child-theme-generator-smoke.php @@ -261,15 +261,50 @@ function emulsify_cli_smoke_json( string $path ): array { $cli( array( 'Acme Theme' ), array( 'machine-name' => 'acme-child', 'force' => true ) ); emulsify_cli_smoke_assert( ! file_exists( $destination . '/remove-me.txt' ), '--force should replace the existing destination.' ); + emulsify_cli_smoke_assert( is_file( $destination . '/project.emulsify.json' ), '--force should replace a generated Emulsify child theme with fresh project metadata.' ); + + $unrelated_destination = $theme_root . '/unrelated-theme'; + $unrelated_refused = false; + + if ( ! mkdir( $unrelated_destination, 0777, true ) ) { + throw new RuntimeException( sprintf( 'Could not create unrelated theme fixture: %s', $unrelated_destination ) ); + } + + file_put_contents( $unrelated_destination . '/style.css', "/*\n * Theme Name: Unrelated Theme\n * Template: twentytwentysix\n */\n" ); + file_put_contents( $unrelated_destination . '/keep-me.txt', 'unrelated' ); + + try { + $cli( array( 'Unrelated Theme' ), array( 'machine-name' => 'unrelated-theme', 'force' => true ) ); + } catch ( RuntimeException $exception ) { + $unrelated_refused = false !== strpos( $exception->getMessage(), 'Refusing to replace existing destination because it does not look like an Emulsify-generated child theme' ); + } + + emulsify_cli_smoke_assert( $unrelated_refused, '--force should refuse to replace an unrelated theme directory.' ); + emulsify_cli_smoke_assert( is_file( $unrelated_destination . '/keep-me.txt' ), '--force refusal should not delete unrelated theme files.' ); WP_CLI::$messages = array(); $GLOBALS['emulsify_activated_theme'] = null; $dry_run_destination = $theme_root . '/dry-run-child'; - $cli( array( 'Dry Run Theme' ), array( 'machine-name' => 'dry-run-child', 'dry-run' => true, 'activate' => true ) ); + if ( ! mkdir( $dry_run_destination, 0777, true ) ) { + throw new RuntimeException( sprintf( 'Could not create dry-run fixture: %s', $dry_run_destination ) ); + } - emulsify_cli_smoke_assert( ! file_exists( $dry_run_destination ), '--dry-run should not create a child theme directory.' ); + file_put_contents( $dry_run_destination . '/keep-me.txt', 'dry-run' ); + + $cli( array( 'Dry Run Theme' ), array( 'machine-name' => 'dry-run-child', 'dry-run' => true, 'force' => true, 'activate' => true ) ); + + emulsify_cli_smoke_assert( is_file( $dry_run_destination . '/keep-me.txt' ), '--dry-run --force should not delete an existing child theme directory.' ); emulsify_cli_smoke_assert( null === $GLOBALS['emulsify_activated_theme'], '--dry-run should not activate the child theme.' ); + emulsify_cli_smoke_assert( + (bool) array_filter( + WP_CLI::$messages, + static function ( array $message ): bool { + return false !== strpos( $message['message'], 'Would replace existing destination because --force was provided' ); + } + ), + '--dry-run --force should report that it would replace the existing destination.' + ); emulsify_cli_smoke_assert( (bool) array_filter( WP_CLI::$messages, diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 71f0d55..4731dac 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -453,11 +453,15 @@ function runStaticChecks() { ensure(cli.includes("data['project']['machineName']"), 'Child theme generator should update project.emulsify.json project.machineName.'); ensure(cli.includes("data['name'] = $machine_name"), 'Child theme generator should update package.json name.'); ensure(cli.includes('collect_pattern_updates'), 'Child theme generator should update starter pattern namespaces.'); + ensure(cli.includes('get_destination_replacement_error'), 'Child theme generator should verify existing destinations before force replacement.'); + ensure(cli.includes('project.platform: wordpress'), 'Child theme generator should require WordPress project metadata before force replacement.'); ensure(!cli.includes('rename_instances'), 'Child theme generator should not use blind recursive starter string replacement.'); ensure(smoke.includes("'machine-name' => 'acme-child'"), 'Child theme generator smoke should cover --machine-name.'); ensure(smoke.includes("'dry-run' => true"), 'Child theme generator smoke should cover --dry-run.'); ensure(smoke.includes("'force' => true"), 'Child theme generator smoke should cover --force.'); ensure(smoke.includes("'activate' => true"), 'Child theme generator smoke should cover --activate.'); + ensure(smoke.includes('unrelated-theme'), 'Child theme generator smoke should prove --force refuses unrelated theme directories.'); + ensure(smoke.includes('Would replace existing destination because --force was provided'), 'Child theme generator smoke should prove --dry-run --force reports replacement intent.'); ensure(smoke.includes('project.emulsify.json'), 'Child theme generator smoke should validate project.emulsify.json updates.'); ensure(smoke.includes("assets/images/.gitkeep"), 'Child theme generator smoke should validate copied image asset placeholders.'); ensure(smoke.includes("assets/icons/.gitkeep"), 'Child theme generator smoke should validate copied icon asset placeholders.'); @@ -911,6 +915,7 @@ function runStaticChecks() { ensure(docs.editorPolicy.includes('emulsify_theme_block_support_overrides'), 'Editor policy doc should document block support overrides.'); ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); + ensure(docs.cli.includes('Force replacement safety') && docs.cli.includes('Emulsify-generated child theme markers'), 'WP-CLI doc should document force replacement safety.'); ensure(docs.cli.includes('Ignored dependency, cache, Storybook, and Vite output directories are not copied'), 'WP-CLI doc should explain that generated themes do not inherit build output.'); ensure(docs.release.includes('release-2.x') && docs.release.includes('2.0.0'), 'Release process doc should document the release-2.x target release.'); ensure(docs.release.includes('WordPress Theme Readiness workflow'), 'Release process doc should document the theme readiness workflow.'); diff --git a/README.md b/README.md index 1a75ba6..9ae4b6e 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ wp emulsify "Acme Site" --machine-name=acme-site --activate The generator copies `emulsify/whisk` to a sibling child theme directory, updates WordPress theme headers, package metadata, Emulsify project metadata, and visible starter labels, then optionally activates the generated child theme. +`--force` only replaces an existing destination when it already looks like an Emulsify-generated child theme. It refuses unrelated theme directories; use `--dry-run --force` to inspect replacement intent without deleting files. + For Emulsify CLI integration, the standalone starter repository is `https://github.com/emulsify-ds/emulsify-wordpress-starter`. It represents the generated child theme layer from `whisk/`, not the parent runtime theme root, and generated projects still declare `Template: emulsify` so WordPress loads the installed parent theme. ## Documentation diff --git a/docs/wp-cli-child-theme-generation.md b/docs/wp-cli-child-theme-generation.md index 9b2c7b1..32a8d7c 100644 --- a/docs/wp-cli-child-theme-generation.md +++ b/docs/wp-cli-child-theme-generation.md @@ -17,7 +17,7 @@ wp emulsify "Acme Site" --machine-name=acme-site --activate | --- | --- | | `--machine-name=` | Override the generated slug used for paths and package metadata. | | `--dry-run` | Show what would be created or changed without writing files. | -| `--force` | Replace an existing destination. Use this only when replacement is intentional. | +| `--force` | Replace an existing destination only when it already looks like an Emulsify-generated child theme. | | `--activate` | Activate the generated child theme after creation. | ## What the generator updates @@ -37,6 +37,17 @@ The generator avoids broad blind string replacement. It targets known metadata f Ignored dependency, cache, Storybook, and Vite output directories are not copied. A generated child theme should install its own dependencies, install or configure the chosen Emulsify component system, and create its own `dist` output. +## Force replacement safety + +`--force` refuses to delete an existing destination unless the directory has Emulsify-generated child theme markers: + +- `style.css` declares `Template: emulsify` or the selected parent slug. +- `project.emulsify.json` exists. +- `project.emulsify.json` declares `"platform": "wordpress"`. +- `project.emulsify.json` includes a project `machineName`. + +Use `--dry-run --force` to inspect replacement intent without deleting files. If the destination is an unrelated theme, remove or rename it manually before generating a child theme with the same machine name. + ## Emulsify CLI starter hook Emulsify CLI uses the standalone starter repository at `https://github.com/emulsify-ds/emulsify-wordpress-starter`. That repository should be built from this branch's `whisk/` directory, which is the generated child theme layer rather than the parent runtime theme root. diff --git a/includes/class-cli.php b/includes/class-cli.php index a53deb5..6d4c5fe 100644 --- a/includes/class-cli.php +++ b/includes/class-cli.php @@ -58,7 +58,8 @@ public function register(): void { * : Show what would be created or changed without writing files. * * [--force] - * : Replace an existing destination directory. + * : Replace an existing destination directory only when it looks like an + * Emulsify-generated child theme. * * [--activate] * : Activate the generated child theme after creation. @@ -119,6 +120,18 @@ private function generate( array $args, array $assoc_args ): void { } if ( file_exists( $destination ) ) { + $replacement_error = $this->get_destination_replacement_error( $destination, $parent ); + + if ( null !== $replacement_error ) { + \WP_CLI::error( + sprintf( + 'Refusing to replace existing destination because it does not look like an Emulsify-generated child theme: %s (%s). Remove it manually or choose a different --machine-name.', + $destination, + $replacement_error + ) + ); + } + \WP_CLI::warning( sprintf( 'Replacing existing destination because --force was provided: %s', $destination ) ); if ( ! $this->remove_path( $destination ) ) { @@ -534,6 +547,98 @@ private function count_copyable_files( string $source ): int { return $count; } + /** + * Gets the reason an existing destination should not be force-replaced. + * + * @param string $destination Destination theme root. + * @param string $parent Selected parent theme slug. + * @return string|null Error reason, or NULL when replacement is allowed. + */ + private function get_destination_replacement_error( string $destination, string $parent ): ?string { + if ( ! is_dir( $destination ) ) { + return 'destination is not a theme directory'; + } + + $template = $this->read_theme_header_value( $this->join_path( $destination, 'style.css' ), 'Template' ); + + if ( null === $template ) { + return 'missing readable style.css Template header'; + } + + $allowed_templates = array_values( array_unique( array_filter( array( 'emulsify', $parent ) ) ) ); + + if ( ! in_array( $template, $allowed_templates, true ) ) { + return sprintf( 'style.css Template is "%s", expected "%s"', $template, implode( '" or "', $allowed_templates ) ); + } + + $project = $this->read_json_file( $this->join_path( $destination, 'project.emulsify.json' ) ); + + if ( null === $project ) { + return 'missing readable project.emulsify.json'; + } + + if ( ! isset( $project['project'] ) || ! is_array( $project['project'] ) ) { + return 'project.emulsify.json is missing project metadata'; + } + + if ( 'wordpress' !== ( $project['project']['platform'] ?? null ) ) { + return 'project.emulsify.json is missing project.platform: wordpress'; + } + + if ( ! isset( $project['project']['machineName'] ) || ! is_string( $project['project']['machineName'] ) || '' === trim( $project['project']['machineName'] ) ) { + return 'project.emulsify.json is missing project.machineName'; + } + + return null; + } + + /** + * Reads a WordPress theme header value. + * + * @param string $path File path. + * @param string $field Header field. + * @return string|null Header value, or NULL when unavailable. + */ + private function read_theme_header_value( string $path, string $field ): ?string { + if ( ! is_readable( $path ) ) { + return null; + } + + $contents = file_get_contents( $path ); + + if ( ! is_string( $contents ) ) { + return null; + } + + if ( ! preg_match( '/^\s*(?:\*\s*)?' . preg_quote( $field, '/' ) . ':\s*(.+?)\s*$/mi', $contents, $matches ) ) { + return null; + } + + return trim( $matches[1] ); + } + + /** + * Reads a JSON file. + * + * @param string $path File path. + * @return array|null Decoded JSON data, or NULL when unavailable. + */ + private function read_json_file( string $path ): ?array { + if ( ! is_readable( $path ) ) { + return null; + } + + $contents = file_get_contents( $path ); + + if ( ! is_string( $contents ) ) { + return null; + } + + $data = json_decode( $contents, true ); + + return is_array( $data ) ? $data : null; + } + /** * Removes a generated destination before force replacement. * From 46bf29a23a2fdcff4c5112a9a8d94d9d9746b06f Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 09:43:38 -0500 Subject: [PATCH 34/55] docs: document wordpress fixture release gate --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/scripts/release-check.cjs | 7 +++++++ .github/workflows/semantic-release.yml | 4 +++- .github/workflows/theme-readiness.yml | 6 ++++-- README.md | 6 ++++-- docs/release-process.md | 19 ++++++++++++++++--- 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6c6ce38..757c824 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,4 +10,4 @@ ### Functional Testing: - [ ] Document steps that allow someone to fully test your code changes. Include screenshot and links when appropriate. - +- [ ] For the 2.0 release branch merge, run the manual `WordPress Theme Readiness` workflow on `release-2.x` with `wordpress_fixture` enabled and confirm both `Practical theme readiness` and `WordPress fixture smoke` pass. Mark N/A for routine PRs. diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 4731dac..d229409 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -795,6 +795,7 @@ function runStaticChecks() { ensure(themeReadinessWorkflow.includes('npm run pr:check'), 'theme-readiness.yml should delegate project smoke checks to npm run pr:check.'); ensure(themeReadinessWorkflow.includes('npm run release:check'), 'theme-readiness.yml should run release readiness checks.'); ensure(themeReadinessWorkflow.includes("github.event_name != 'pull_request'"), 'theme-readiness.yml should keep the WordPress fixture off normal pull requests.'); + ensure(themeReadinessWorkflow.includes('wordpress_fixture enabled before merging'), 'theme-readiness.yml should document the 2.0 manual fixture gate.'); ensure(themeReadinessWorkflow.includes('mysql:'), 'theme-readiness.yml should provide MySQL for the WordPress fixture job.'); ensure(themeReadinessWorkflow.includes('wp-cli'), 'theme-readiness.yml should install WP-CLI for the WordPress fixture job.'); ensure(themeReadinessWorkflow.includes('WP_SMOKE_REQUIRED'), 'theme-readiness.yml should require the fixture smoke when the fixture job runs.'); @@ -853,6 +854,8 @@ function runStaticChecks() { ensure(readme.includes('whisk/project.emulsify.json') && readme.includes('"platform": "wordpress"'), 'README.md should explain the current project.emulsify.json platform setting.'); ensure(readme.includes('whisk/assets/images') && readme.includes('whisk/assets/icons'), 'README.md should document the generated child asset placeholders.'); ensure(readme.includes('wp emulsify "Acme Site" --machine-name=acme-site'), 'README.md should document child theme generator examples.'); + ensure(readme.includes('Normal PR checks do not start MySQL or run the full WordPress fixture'), 'README.md should distinguish practical PR checks from the full fixture.'); + ensure(readme.includes('GitHub Actions > `WordPress Theme Readiness`'), 'README.md should tell maintainers where to run the manual fixture workflow.'); ensure(readme.includes('WP_SMOKE_REQUIRED=1'), 'README.md should document required WordPress fixture smoke behavior.'); for (const docLink of expectedDocLinks) { @@ -920,10 +923,14 @@ function runStaticChecks() { ensure(docs.release.includes('release-2.x') && docs.release.includes('2.0.0'), 'Release process doc should document the release-2.x target release.'); ensure(docs.release.includes('WordPress Theme Readiness workflow'), 'Release process doc should document the theme readiness workflow.'); ensure(docs.release.includes('Manual and scheduled runs execute the full WordPress fixture smoke test'), 'Release process doc should explain when the full fixture runs.'); + ensure(docs.release.includes('Open GitHub Actions for `emulsify-ds/emulsify-wordpress`'), 'Release process doc should tell maintainers where to trigger the manual workflow.'); + ensure(docs.release.includes('Keep `wordpress_fixture` enabled'), 'Release process doc should document the manual workflow fixture input.'); + ensure(docs.release.includes('Success means both the `Practical theme readiness` job and the `WordPress fixture smoke` job pass'), 'Release process doc should define manual fixture success.'); ensure(docs.release.includes('generates and activates a child theme from Whisk'), 'Release process doc should document generated child fixture coverage.'); ensure(docs.release.includes('ACF/Twig and native `block.json` discovery'), 'Release process doc should document block discovery fixture coverage.'); ensure(docs.release.includes('WP_SMOKE_REQUIRED=1'), 'Release process doc should document required fixture smoke behavior.'); ensure(docs.release.includes('Manual dispatch can also run the Whisk Storybook build and accessibility audit'), 'Release process doc should document optional extended checks.'); + ensure(pullRequestTemplate.includes('2.0 release branch merge') && pullRequestTemplate.includes('wordpress_fixture'), 'PR template should include the 2.0 manual fixture checklist item.'); ensure(/duplicate[\w\s/`.-]*skipped instead of being registered twice/i.test(docsText), 'Docs should document duplicate block handling.'); ensure(docsText.includes('normal frontend visitors') || docsText.includes('Normal frontend visitors'), 'Docs should document that duplicate diagnostics avoid frontend noise.'); ensure(!/Webpack/i.test(`${readme}\n${docsText}`), 'Docs should not mention Webpack.'); diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 468f286..03e4f14 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -1,7 +1,9 @@ name: WordPress Release # Release publishing is gated by the same local readiness script used by -# maintainers, with the WordPress fixture required instead of optional. +# maintainers, with the WordPress fixture required instead of optional. The +# release-readiness job installs WP-CLI, starts MySQL, and runs release:check +# with WP_SMOKE_REQUIRED=1 before semantic-release can publish. on: push: branches: diff --git a/.github/workflows/theme-readiness.yml b/.github/workflows/theme-readiness.yml index 59d1070..9b9f8d1 100644 --- a/.github/workflows/theme-readiness.yml +++ b/.github/workflows/theme-readiness.yml @@ -1,7 +1,9 @@ name: WordPress Theme Readiness -# Pull requests run fast, stubbed checks. Manual and scheduled runs can also -# exercise the full WordPress fixture with WP-CLI and MySQL. +# Pull requests run practical checks without starting MySQL. Manual and +# scheduled runs can exercise the full WordPress fixture with WP-CLI and MySQL. +# For the 2.0 release branch, maintainers should run the manual workflow with +# wordpress_fixture enabled before merging. on: pull_request: branches: diff --git a/README.md b/README.md index 9ae4b6e..8c3daab 100644 --- a/README.md +++ b/README.md @@ -103,11 +103,13 @@ npm ci --ignore-scripts | Command | Purpose | | --- | --- | | `npm run lint:php` | Lint all PHP files with `php -l`. | -| `npm run pr:check` | Run the practical pull request validation suite. | +| `npm run pr:check` | Run the practical, stubbed pull request validation suite. | | `npm run release:check` | Run release-readiness checks. | | `npm run publish-test -- --no-ci` | Run a local semantic-release dry run. | -`release:check` includes the full WordPress fixture smoke path. It skips gracefully when WP-CLI or database settings are unavailable, and fails on missing fixture prerequisites when `WP_SMOKE_REQUIRED=1` is set. +Normal PR checks do not start MySQL or run the full WordPress fixture. `release:check` includes that fixture path, which requires WP-CLI and MySQL. It skips gracefully when WP-CLI or database settings are unavailable, and fails on missing fixture prerequisites when `WP_SMOKE_REQUIRED=1` is set. + +Before merging the 2.0 release branch, maintainers should run GitHub Actions > `WordPress Theme Readiness` on `release-2.x` with the `wordpress_fixture` input enabled. Success means both `Practical theme readiness` and `WordPress fixture smoke` pass. Release publishing also requires that full fixture path before semantic-release can publish. ## Generate a child theme diff --git a/docs/release-process.md b/docs/release-process.md index 9e3e4d7..85710bc 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -24,7 +24,7 @@ npm run release:check npm run publish-test -- --no-ci ``` -`pr:check` runs the practical pull request suite: +`pr:check` runs the practical, stubbed pull request suite: - Composer metadata validation. - Composer dependency install for Twig smoke coverage. @@ -38,7 +38,7 @@ npm run publish-test -- --no-ci `pr:check` does not build the empty Whisk starter. Whisk's build script delegates directly to Emulsify Core, and that build is expected to fail until a project installs or configures a component system with Vite input files. -`release:check` adds static release-readiness checks and the full WordPress fixture smoke path. The fixture installs the parent theme in an isolated WordPress site, generates and activates a child theme from Whisk, adds neutral built asset and block fixtures, renders frontend routes through Timber, fetches those built child assets, and checks ACF/Twig and native `block.json` discovery. It skips gracefully when WP-CLI or database settings are unavailable unless `WP_SMOKE_REQUIRED=1` is set. +`release:check` adds static release-readiness checks and the full WordPress fixture smoke path. The fixture installs the parent theme in an isolated WordPress site, generates and activates a child theme from Whisk, adds neutral built asset and block fixtures, renders frontend routes through Timber, fetches those built child assets, and checks ACF/Twig and native `block.json` discovery. It requires WP-CLI and MySQL. It skips gracefully when WP-CLI or database settings are unavailable unless `WP_SMOKE_REQUIRED=1` is set. ## CI @@ -57,7 +57,20 @@ Normal pull requests do not start MySQL or run the full WordPress fixture. Manua Manual dispatch can also run the Whisk Storybook build and accessibility audit for extended frontend confidence. -The semantic-release workflow remains release-gated. It still runs release readiness, the full WordPress fixture, a semantic-release dry run, and the final publish job only through the release workflow. +## 2.0 merge and release gate + +Before merging the 2.0 release branch, maintainers should run the full fixture through GitHub Actions: + +1. Open GitHub Actions for `emulsify-ds/emulsify-wordpress`. +2. Select the `WordPress Theme Readiness` workflow. +3. Choose `Run workflow`. +4. Select the `release-2.x` branch. +5. Keep `wordpress_fixture` enabled. It defaults to enabled for manual runs. +6. Leave `extended_checks` disabled unless Storybook and accessibility coverage is needed for that release decision. + +Success means both the `Practical theme readiness` job and the `WordPress fixture smoke` job pass. The fixture job installs WP-CLI, starts MySQL, sets `WP_SMOKE_REQUIRED=1`, and runs `npm run release:check` against the isolated WordPress fixture. Record the successful workflow run in the 2.0 release PR before merging. + +The semantic-release workflow remains release-gated. It runs release readiness, requires the full WordPress fixture smoke path with WP-CLI and MySQL, runs a semantic-release dry run, and then allows the final publish job only after the readiness job succeeds. Release publishing must not proceed when the full fixture path is skipped or failed. ## License From fa897b6ed155e7e86cb883a2ea2c7331b9a5daaf Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 14:18:39 -0500 Subject: [PATCH 35/55] feat: add generated child theme metadata --- .../scripts/child-theme-generator-smoke.php | 40 +++++++++++++ .github/scripts/release-check.cjs | 20 +++++++ .github/scripts/wordpress-fixture-smoke.cjs | 4 +- .../scripts/wordpress-starter-init-smoke.cjs | 2 + README.md | 2 +- docs/core-4-vite-workflow.md | 4 +- docs/parent-child-architecture.md | 2 + docs/sister-project-parity.md | 4 +- docs/upgrading-1x-to-2x.md | 1 + docs/wp-cli-child-theme-generation.md | 20 ++++++- includes/class-cli.php | 58 ++++++++++++++++++- whisk/.cli/init.js | 15 ++++- whisk/project.emulsify.json | 4 +- 13 files changed, 164 insertions(+), 12 deletions(-) diff --git a/.github/scripts/child-theme-generator-smoke.php b/.github/scripts/child-theme-generator-smoke.php index eed6df9..3d40d31 100644 --- a/.github/scripts/child-theme-generator-smoke.php +++ b/.github/scripts/child-theme-generator-smoke.php @@ -228,6 +228,8 @@ function emulsify_cli_smoke_json( string $path ): array { emulsify_cli_smoke_assert( 'wordpress' === $project['project']['platform'], 'project.emulsify.json should preserve the WordPress platform adapter.' ); emulsify_cli_smoke_assert( 'Acme Theme' === $project['project']['name'], 'project.emulsify.json should update project name.' ); emulsify_cli_smoke_assert( 'acme-child' === $project['project']['machineName'], 'project.emulsify.json should update machineName.' ); + emulsify_cli_smoke_assert( 'emulsify-wordpress' === $project['project']['generatedFrom'], 'project.emulsify.json should identify the generated child theme source.' ); + emulsify_cli_smoke_assert( '2.0.0' === $project['project']['generatedFromVersion'], 'project.emulsify.json should record the generated child theme source version.' ); emulsify_cli_smoke_assert( false !== strpos( $page, 'acme-child-page' ), 'Example template should update slug class.' ); emulsify_cli_smoke_assert( false === strpos( $page, 'whisk-page' ), 'Example template should not keep the whisk slug class.' ); emulsify_cli_smoke_assert( false !== strpos( $functions, 'Acme Theme child theme hooks.' ), 'functions.php should update visible Whisk label.' ); @@ -260,8 +262,12 @@ function emulsify_cli_smoke_json( string $path ): array { $cli( array( 'Acme Theme' ), array( 'machine-name' => 'acme-child', 'force' => true ) ); + $replaced_project = emulsify_cli_smoke_json( $destination . '/project.emulsify.json' ); + emulsify_cli_smoke_assert( ! file_exists( $destination . '/remove-me.txt' ), '--force should replace the existing destination.' ); emulsify_cli_smoke_assert( is_file( $destination . '/project.emulsify.json' ), '--force should replace a generated Emulsify child theme with fresh project metadata.' ); + emulsify_cli_smoke_assert( 'emulsify-wordpress' === $replaced_project['project']['generatedFrom'], '--force should keep generated child theme source metadata.' ); + emulsify_cli_smoke_assert( '2.0.0' === $replaced_project['project']['generatedFromVersion'], '--force should keep generated child theme source version metadata.' ); $unrelated_destination = $theme_root . '/unrelated-theme'; $unrelated_refused = false; @@ -282,6 +288,40 @@ function emulsify_cli_smoke_json( string $path ): array { emulsify_cli_smoke_assert( $unrelated_refused, '--force should refuse to replace an unrelated theme directory.' ); emulsify_cli_smoke_assert( is_file( $unrelated_destination . '/keep-me.txt' ), '--force refusal should not delete unrelated theme files.' ); + $foreign_destination = $theme_root . '/foreign-theme'; + $foreign_refused = false; + + if ( ! mkdir( $foreign_destination, 0777, true ) ) { + throw new RuntimeException( sprintf( 'Could not create foreign generated theme fixture: %s', $foreign_destination ) ); + } + + file_put_contents( $foreign_destination . '/style.css', "/*\n * Theme Name: Foreign Theme\n * Template: emulsify\n */\n" ); + file_put_contents( + $foreign_destination . '/project.emulsify.json', + json_encode( + array( + 'project' => array( + 'platform' => 'wordpress', + 'name' => 'Foreign Theme', + 'machineName' => 'foreign-theme', + 'generatedFrom' => 'foreign-generator', + 'generatedFromVersion' => '1.0.0', + ), + ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ) . "\n" + ); + file_put_contents( $foreign_destination . '/keep-me.txt', 'foreign' ); + + try { + $cli( array( 'Foreign Theme' ), array( 'machine-name' => 'foreign-theme', 'force' => true ) ); + } catch ( RuntimeException $exception ) { + $foreign_refused = false !== strpos( $exception->getMessage(), 'generatedFrom is "foreign-generator"' ); + } + + emulsify_cli_smoke_assert( $foreign_refused, '--force should refuse to replace a theme generated by a different source.' ); + emulsify_cli_smoke_assert( is_file( $foreign_destination . '/keep-me.txt' ), '--force generatedFrom refusal should not delete existing theme files.' ); + WP_CLI::$messages = array(); $GLOBALS['emulsify_activated_theme'] = null; $dry_run_destination = $theme_root . '/dry-run-child'; diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index d229409..f99c2e4 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -451,10 +451,13 @@ function runStaticChecks() { ensure(cli.includes("replace_theme_header( $contents, 'Template'"), 'Child theme generator should update Template explicitly.'); ensure(cli.includes("data['project']['name']"), 'Child theme generator should update project.emulsify.json project.name.'); ensure(cli.includes("data['project']['machineName']"), 'Child theme generator should update project.emulsify.json project.machineName.'); + ensure(cli.includes("data['project']['generatedFrom']"), 'Child theme generator should update project.emulsify.json generatedFrom.'); + ensure(cli.includes("data['project']['generatedFromVersion']"), 'Child theme generator should update project.emulsify.json generatedFromVersion.'); ensure(cli.includes("data['name'] = $machine_name"), 'Child theme generator should update package.json name.'); ensure(cli.includes('collect_pattern_updates'), 'Child theme generator should update starter pattern namespaces.'); ensure(cli.includes('get_destination_replacement_error'), 'Child theme generator should verify existing destinations before force replacement.'); ensure(cli.includes('project.platform: wordpress'), 'Child theme generator should require WordPress project metadata before force replacement.'); + ensure(cli.includes('project.generatedFrom'), 'Child theme generator should use generated source metadata for force replacement safety.'); ensure(!cli.includes('rename_instances'), 'Child theme generator should not use blind recursive starter string replacement.'); ensure(smoke.includes("'machine-name' => 'acme-child'"), 'Child theme generator smoke should cover --machine-name.'); ensure(smoke.includes("'dry-run' => true"), 'Child theme generator smoke should cover --dry-run.'); @@ -466,6 +469,9 @@ function runStaticChecks() { ensure(smoke.includes("assets/images/.gitkeep"), 'Child theme generator smoke should validate copied image asset placeholders.'); ensure(smoke.includes("assets/icons/.gitkeep"), 'Child theme generator smoke should validate copied icon asset placeholders.'); ensure(smoke.includes("'wordpress' === $project['project']['platform']"), 'Child theme generator smoke should validate the WordPress platform adapter.'); + ensure(smoke.includes("'emulsify-wordpress' === $project['project']['generatedFrom']"), 'Child theme generator smoke should validate generatedFrom metadata.'); + ensure(smoke.includes("'2.0.0' === $project['project']['generatedFromVersion']"), 'Child theme generator smoke should validate generatedFromVersion metadata.'); + ensure(smoke.includes('foreign-generator'), 'Child theme generator smoke should reject conflicting generatedFrom metadata.'); ensure(smoke.includes('smoke-pattern.json'), 'Child theme generator smoke should validate optional copied pattern namespace updates.'); ensure(smoke.includes("! is_dir( $destination . '/src/components/button' )"), 'Child theme generator smoke should prove removed starter components are not copied.'); ensure(smoke.includes("! is_dir( $destination . '/src/editor' )"), 'Child theme generator smoke should prove assumed editor modules are not copied.'); @@ -589,6 +595,8 @@ function runStaticChecks() { ensure(whiskProject.project.platform === 'wordpress', 'whisk/project.emulsify.json should use the WordPress platform adapter.'); ensure(whiskProject.project.name === 'whisk', 'whisk/project.emulsify.json project.name should remain whisk.'); ensure(whiskProject.project.machineName === 'whisk', 'whisk/project.emulsify.json project.machineName should remain whisk.'); + ensure(whiskProject.project.generatedFrom === 'emulsify-wordpress', 'whisk/project.emulsify.json should identify the generated child theme source.'); + ensure(whiskProject.project.generatedFromVersion === '2.0.0', 'whisk/project.emulsify.json should record the generated child theme source version.'); ensure(whiskProject.starter.repository === 'https://github.com/emulsify-ds/emulsify-wordpress-starter', 'whisk/project.emulsify.json should point to the standalone WordPress starter repository.'); ensure(fs.existsSync(path.join(repoRoot, 'whisk/.cli/init.js')), 'Whisk should ship an emulsify-cli init hook.'); ensure(fs.existsSync(path.join(repoRoot, 'whisk/.gitignore')), 'Whisk should ship standalone starter ignore rules.'); @@ -604,9 +612,13 @@ function runStaticChecks() { ensure(initHook.includes("data.name = machineName"), 'Whisk init hook should update package metadata names.'); ensure(initHook.includes("updateLockfile('package-lock.json'"), 'Whisk init hook should update the package lockfile created before the hook runs.'); ensure(initHook.includes("config.project.platform = 'wordpress'"), 'Whisk init hook should keep project.platform on wordpress.'); + ensure(initHook.includes("config.project.generatedFrom = GENERATED_FROM"), 'Whisk init hook should set generatedFrom metadata.'); + ensure(initHook.includes("config.project.generatedFromVersion = generatedFromVersion"), 'Whisk init hook should set generatedFromVersion metadata.'); ensure(initHook.includes('updatePatternNamespaces'), 'Whisk init hook should update JSON pattern namespaces.'); ensure(starterInitSmoke.includes('package-lock.json') && starterInitSmoke.includes('packages[""].name'), 'Starter init smoke should prove lockfile metadata is updated after npm install.'); ensure(starterInitSmoke.includes("project.project.platform === 'wordpress'"), 'Starter init smoke should validate the WordPress platform adapter.'); + ensure(starterInitSmoke.includes("project.project.generatedFrom === 'emulsify-wordpress'"), 'Starter init smoke should validate generatedFrom metadata.'); + ensure(starterInitSmoke.includes("project.project.generatedFromVersion === '2.0.0'"), 'Starter init smoke should validate generatedFromVersion metadata.'); ensure(starterInitSmoke.includes("style.Template === 'emulsify'"), 'Starter init smoke should validate Template: emulsify.'); ensure(starterInitSmoke.includes('node_modules') && starterInitSmoke.includes('dist'), 'Starter init smoke should validate copied build and dependency output is absent.'); ensure(starterInitSmoke.includes("['--prefix', 'whisk', 'run', 'test']"), 'Starter init smoke should verify the starter npm test script works after Whisk dependencies are installed.'); @@ -708,6 +720,8 @@ function runStaticChecks() { ensure(wordpressFixtureSmoke.includes("['emulsify', 'Smoke Generated'"), 'WordPress fixture smoke should generate a child theme from Whisk with WP-CLI.'); ensure(wordpressFixtureSmoke.includes("['option', 'get', 'stylesheet']"), 'WordPress fixture smoke should verify the generated child theme is active.'); ensure(wordpressFixtureSmoke.includes('assertGeneratedChildTheme'), 'WordPress fixture smoke should validate generated child theme metadata and copied Whisk files.'); + ensure(wordpressFixtureSmoke.includes("project.project?.generatedFrom !== 'emulsify-wordpress'"), 'WordPress fixture smoke should validate generatedFrom metadata.'); + ensure(wordpressFixtureSmoke.includes("project.project?.generatedFromVersion !== '2.0.0'"), 'WordPress fixture smoke should validate generatedFromVersion metadata.'); ensure(wordpressFixtureSmoke.includes('${themeSlug}-page'), 'WordPress fixture smoke should prove the generated child page template renders through Timber.'); ensure(wordpressFixtureSmoke.includes('checkGeneratedAssets'), 'WordPress fixture smoke should fetch generated child theme built assets.'); ensure(wordpressFixtureSmoke.includes('runAcfDiscoveryWithoutAcf'), 'WordPress fixture smoke should check ACF/Twig discovery when ACF is absent.'); @@ -852,6 +866,7 @@ function runStaticChecks() { ensure(readme.includes('npm ci --ignore-scripts'), 'README.md should document parent maintainer npm install.'); ensure(readme.includes('composer install'), 'README.md should document Composer install usage.'); ensure(readme.includes('whisk/project.emulsify.json') && readme.includes('"platform": "wordpress"'), 'README.md should explain the current project.emulsify.json platform setting.'); + ensure(readme.includes('generatedFrom') && readme.includes('generatedFromVersion'), 'README.md should explain generated child theme source metadata.'); ensure(readme.includes('whisk/assets/images') && readme.includes('whisk/assets/icons'), 'README.md should document the generated child asset placeholders.'); ensure(readme.includes('wp emulsify "Acme Site" --machine-name=acme-site'), 'README.md should document child theme generator examples.'); ensure(readme.includes('Normal PR checks do not start MySQL or run the full WordPress fixture'), 'README.md should distinguish practical PR checks from the full fixture.'); @@ -863,6 +878,7 @@ function runStaticChecks() { } ensure(docs.upgrading.includes('Emulsify WordPress 2.x changes the project model'), 'Upgrade doc should explain the 2.x project model.'); + ensure(docs.upgrading.includes('generatedFrom: "emulsify-wordpress"') && docs.upgrading.includes('Older generated child themes may not include these fields'), 'Upgrade doc should document generated child theme lineage metadata.'); ensure(docs.parity.includes('Emulsify WordPress is the WordPress sister project to Emulsify Drupal'), 'Sister-project parity doc should name the Drupal sister project.'); ensure(docs.parity.includes('The parent theme owns reusable CMS runtime behavior'), 'Sister-project parity doc should define parent runtime ownership.'); ensure(docs.parity.includes('The generated child theme owns project implementation'), 'Sister-project parity doc should define child theme ownership.'); @@ -882,8 +898,10 @@ function runStaticChecks() { ensure(docs.parity.includes('Native Gutenberg blocks use WordPress `block.json` metadata'), 'Sister-project parity doc should document native block.json blocks.'); ensure(docs.parity.includes("WordPress project generation is handled by the parent theme's WP-CLI command"), 'Sister-project parity doc should document WP-CLI generation.'); ensure(docs.parity.includes('`project.emulsify.json` uses `"platform": "wordpress"`'), 'Sister-project parity doc should document the WordPress platform adapter.'); + ensure(docs.parity.includes('generatedFrom: "emulsify-wordpress"'), 'Sister-project parity doc should document generated child theme lineage metadata.'); ensure(docs.parity.includes('{% include "project_machine_name:component_name" %}') && docs.parity.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Sister-project parity doc should promote generic project machine-name component includes while preserving @components compatibility.'); ensure(docs.architecture.includes('The parent theme owns reusable runtime behavior'), 'Architecture doc should explain parent responsibilities.'); + ensure(docs.architecture.includes('generatedFrom: "emulsify-wordpress"') && docs.architecture.includes('safer replacement checks'), 'Architecture doc should document generated child theme lineage metadata.'); ensure(docs.architecture.includes('@emulsify-tpl'), 'Architecture doc should document the parent-only template namespace.'); ensure(docs.architecture.includes('{% include "project_machine_name:component_name" %}') && docs.architecture.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Architecture doc should document generic project machine-name component includes while preserving @components compatibility.'); ensure(docs.twig.includes('@templates') && docs.twig.includes('@components'), 'Twig doc should document core namespaces.'); @@ -893,6 +911,7 @@ function runStaticChecks() { ensure(docs.twig.includes('emulsify_theme_context'), 'Twig doc should document context extension.'); ensure(docs.workflow.includes('Core 4, Vite, and Storybook commands'), 'Workflow doc should use the expected command heading.'); ensure(docs.workflow.includes('"platform": "wordpress"'), 'Workflow doc should explain the WordPress platform adapter.'); + ensure(docs.workflow.includes('generatedFrom: "emulsify-wordpress"') && docs.workflow.includes('support diagnostics'), 'Core 4 workflow doc should document generated child theme lineage metadata.'); ensure(docs.workflow.includes('does not ship a concrete component library'), 'Core 4 workflow doc should describe Whisk as component-system agnostic.'); ensure(docs.workflow.includes('assets/images') && docs.workflow.includes('assets/icons'), 'Core 4 workflow doc should document generic starter asset directories.'); ensure(docs.workflow.includes('does not include a child `theme.json` by default'), 'Core 4 workflow doc should document the child theme.json convention.'); @@ -919,6 +938,7 @@ function runStaticChecks() { ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); ensure(docs.cli.includes('Force replacement safety') && docs.cli.includes('Emulsify-generated child theme markers'), 'WP-CLI doc should document force replacement safety.'); + ensure(docs.cli.includes('Upgrade and support diagnostics') && docs.cli.includes('generatedFromVersion'), 'WP-CLI doc should document generated child theme lineage diagnostics.'); ensure(docs.cli.includes('Ignored dependency, cache, Storybook, and Vite output directories are not copied'), 'WP-CLI doc should explain that generated themes do not inherit build output.'); ensure(docs.release.includes('release-2.x') && docs.release.includes('2.0.0'), 'Release process doc should document the release-2.x target release.'); ensure(docs.release.includes('WordPress Theme Readiness workflow'), 'Release process doc should document the theme readiness workflow.'); diff --git a/.github/scripts/wordpress-fixture-smoke.cjs b/.github/scripts/wordpress-fixture-smoke.cjs index de6bcd6..e40ea5d 100755 --- a/.github/scripts/wordpress-fixture-smoke.cjs +++ b/.github/scripts/wordpress-fixture-smoke.cjs @@ -251,7 +251,9 @@ function assertGeneratedChildTheme(themePath, slug) { if ( project.project?.platform !== 'wordpress' || project.project?.name !== 'Smoke Generated' || - project.project?.machineName !== slug + project.project?.machineName !== slug || + project.project?.generatedFrom !== 'emulsify-wordpress' || + project.project?.generatedFromVersion !== '2.0.0' ) { throw new Error('Generated child theme project.emulsify.json metadata is incorrect.'); } diff --git a/.github/scripts/wordpress-starter-init-smoke.cjs b/.github/scripts/wordpress-starter-init-smoke.cjs index d67496d..27a8e8c 100644 --- a/.github/scripts/wordpress-starter-init-smoke.cjs +++ b/.github/scripts/wordpress-starter-init-smoke.cjs @@ -174,6 +174,8 @@ try { assert(project.project.platform === 'wordpress', 'project.emulsify.json should keep project.platform: wordpress.'); assert(project.project.name === 'Acme Theme', 'project.emulsify.json should update project.name.'); assert(project.project.machineName === 'acme-theme', 'project.emulsify.json should update project.machineName.'); + assert(project.project.generatedFrom === 'emulsify-wordpress', 'project.emulsify.json should identify the generated child theme source.'); + assert(project.project.generatedFromVersion === '2.0.0', 'project.emulsify.json should record the generated child theme source version.'); assert( project.starter.repository === 'https://github.com/emulsify-ds/emulsify-wordpress-starter', 'project.emulsify.json should keep the standalone starter repository.', diff --git a/README.md b/README.md index 8c3daab..48edf77 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The generated `whisk` child theme owns project implementation: - `whisk/assets/images` and `whisk/assets/icons` are empty placeholders for project-owned theme media and icon files. - `whisk/templates/page.twig` is a small example override. - `whisk/dist/global` and `whisk/dist/components` are runtime build output conventions when a component system emits them. -- `whisk/project.emulsify.json` uses `"platform": "wordpress"` so Core and CLI tooling can load the WordPress platform adapter. Projects can also use Core-supported metadata such as `variant.structureImplementations` there when a selected component system needs legacy Twig namespaces. +- `whisk/project.emulsify.json` uses `"platform": "wordpress"` so Core and CLI tooling can load the WordPress platform adapter. It also records `generatedFrom` and `generatedFromVersion` so future upgrades and support diagnostics can identify Emulsify-generated WordPress child themes. Projects can also use Core-supported metadata such as `variant.structureImplementations` there when a selected component system needs legacy Twig namespaces. ## Bedrock and Timber diff --git a/docs/core-4-vite-workflow.md b/docs/core-4-vite-workflow.md index afa8f08..9433bf9 100644 --- a/docs/core-4-vite-workflow.md +++ b/docs/core-4-vite-workflow.md @@ -4,9 +4,9 @@ The generated child theme uses Emulsify Core 4, Vite, Storybook, Twig stories, S ## Project metadata -`whisk/project.emulsify.json` uses `"platform": "wordpress"` so Emulsify Core and Emulsify CLI tooling can load the WordPress platform adapter while this parent theme owns the reusable WordPress runtime. +`whisk/project.emulsify.json` uses `"platform": "wordpress"` so Emulsify Core and Emulsify CLI tooling can load the WordPress platform adapter while this parent theme owns the reusable WordPress runtime. It also records `generatedFrom: "emulsify-wordpress"` and `generatedFromVersion` for future starter upgrades and support diagnostics. -Keep this file in generated child themes. The child theme generator updates `project.name` and `project.machineName`, and Emulsify Core uses the same metadata for component-library behavior. +Keep this file in generated child themes. The child theme generator updates `project.name`, `project.machineName`, and generated source metadata, and Emulsify Core uses the same metadata for component-library behavior. If a selected component system needs legacy Twig namespaces, use Core's existing `variant.structureImplementations` array: diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index f5d0bf3..8c462f3 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -87,4 +87,6 @@ Use these filters for project-specific behavior before editing a parent class. K The generated child theme includes `project.emulsify.json` with `"platform": "wordpress"`. Emulsify Core and Emulsify CLI tooling use that adapter value for WordPress-aware project behavior, while reusable WordPress runtime support remains in this parent theme. +Generated child themes also record `generatedFrom: "emulsify-wordpress"` and `generatedFromVersion`. These fields identify the starter lineage for future upgrades, support diagnostics, and safer replacement checks. + Use WordPress-native files for WordPress concerns: `style.css` headers for theme identity and `theme.json` for editor/global style settings. Use `project.emulsify.json` for Emulsify project metadata that Core and project tooling need to share. diff --git a/docs/sister-project-parity.md b/docs/sister-project-parity.md index fba5756..2934c82 100644 --- a/docs/sister-project-parity.md +++ b/docs/sister-project-parity.md @@ -77,7 +77,7 @@ Emulsify CLI uses the standalone `https://github.com/emulsify-ds/emulsify-wordpr ### Emulsify platform metadata -`project.emulsify.json` uses `"platform": "wordpress"` so Emulsify Core and Emulsify CLI tooling can load the WordPress platform adapter. WordPress runtime behavior still lives in this parent theme for the 2.x release line. +`project.emulsify.json` uses `"platform": "wordpress"` so Emulsify Core and Emulsify CLI tooling can load the WordPress platform adapter. It also records `generatedFrom: "emulsify-wordpress"` and `generatedFromVersion` so support and upgrade tooling can identify the WordPress starter lineage. WordPress runtime behavior still lives in this parent theme for the 2.x release line. This file may also hold Emulsify Core metadata such as `variant.structureImplementations`. `theme.json` remains the WordPress site and editor configuration surface, not a component-library or Twig namespace registry. @@ -90,4 +90,4 @@ Use this contract when evaluating future changes: - Keep Whisk small enough to work as a starter. - Keep generated child theme source aligned with Emulsify Core 4 conventions. - Keep WordPress-specific behavior explicit rather than hiding it behind Drupal naming or assumptions. -- Keep `project.emulsify.json` on `"platform": "wordpress"` so generated child themes advertise the WordPress platform adapter. +- Keep `project.emulsify.json` on `"platform": "wordpress"` and `generatedFrom: "emulsify-wordpress"` so generated child themes advertise the WordPress platform adapter and starter lineage. diff --git a/docs/upgrading-1x-to-2x.md b/docs/upgrading-1x-to-2x.md index ff6031e..57b11c7 100644 --- a/docs/upgrading-1x-to-2x.md +++ b/docs/upgrading-1x-to-2x.md @@ -10,6 +10,7 @@ Emulsify WordPress 2.x changes the project model. The repository is now a Timber - The selected Emulsify component system defines the component source structure; Whisk does not ship default foundation, layout, token, or button files. - The starter child theme uses Emulsify Core 4, Vite, Storybook, and Twig. - The generated child theme keeps `project.emulsify.json` set to `"platform": "wordpress"` for Emulsify Core and Emulsify CLI WordPress adapter support. +- New generated child themes also include `generatedFrom: "emulsify-wordpress"` and `generatedFromVersion` in `project.emulsify.json` so future upgrades and support checks can identify starter lineage. Older generated child themes may not include these fields until they are regenerated or updated intentionally. - Release metadata is aligned around the `2.0.0` stable release. ## Before upgrading diff --git a/docs/wp-cli-child-theme-generation.md b/docs/wp-cli-child-theme-generation.md index 32a8d7c..d2a105c 100644 --- a/docs/wp-cli-child-theme-generation.md +++ b/docs/wp-cli-child-theme-generation.md @@ -30,6 +30,7 @@ The generator copies `emulsify/whisk` to a sibling child theme directory and upd - `package.json` name. - `project.emulsify.json` project name. - `project.emulsify.json` machine name. +- `project.emulsify.json` generated source metadata. - Visible Whisk starter labels where appropriate. - Lowercase slug references where appropriate. @@ -45,9 +46,26 @@ Ignored dependency, cache, Storybook, and Vite output directories are not copied - `project.emulsify.json` exists. - `project.emulsify.json` declares `"platform": "wordpress"`. - `project.emulsify.json` includes a project `machineName`. +- New generated child themes include `generatedFrom: "emulsify-wordpress"` and `generatedFromVersion`; `--force` refuses a destination that declares a different generated source. Use `--dry-run --force` to inspect replacement intent without deleting files. If the destination is an unrelated theme, remove or rename it manually before generating a child theme with the same machine name. +## Upgrade and support diagnostics + +Generated child themes record their source in `project.emulsify.json`: + +```json +{ + "project": { + "platform": "wordpress", + "generatedFrom": "emulsify-wordpress", + "generatedFromVersion": "2.0.0" + } +} +``` + +Use these fields when diagnosing project lineage or planning starter upgrades. `generatedFrom` identifies the Emulsify WordPress starter lineage, while `generatedFromVersion` records the parent/starter release that generated or last regenerated the child theme. Older generated child themes may not have these fields; the generator still uses the existing WordPress platform and machine-name markers for compatibility. + ## Emulsify CLI starter hook Emulsify CLI uses the standalone starter repository at `https://github.com/emulsify-ds/emulsify-wordpress-starter`. That repository should be built from this branch's `whisk/` directory, which is the generated child theme layer rather than the parent runtime theme root. @@ -65,7 +83,7 @@ The hook deliberately mirrors the parent WP-CLI generator's targeted updates: - `style.css` `Theme Name`, `Text Domain`, and `Template`. - `package.json` name and generated package lockfile root name. -- `project.emulsify.json` project name and machine name, while keeping `"platform": "wordpress"`. +- `project.emulsify.json` project name, machine name, generated source metadata, and `"platform": "wordpress"`. - Visible Whisk labels in known starter files. - `templates/page.twig` class from `whisk-page` to the generated machine-name class. - JSON pattern names from the `whisk/*` namespace to the generated machine-name namespace. diff --git a/includes/class-cli.php b/includes/class-cli.php index 6d4c5fe..2a87985 100644 --- a/includes/class-cli.php +++ b/includes/class-cli.php @@ -17,6 +17,16 @@ final class Cli { */ private const STARTER_SLUG = 'whisk'; + /** + * Project metadata source identifier for generated child themes. + */ + private const GENERATED_FROM = 'emulsify-wordpress'; + + /** + * Fallback generated child theme source version. + */ + private const GENERATED_FROM_VERSION = '2.0.0'; + /** * Dependency/cache/build paths that should never be copied into a generated * child theme. @@ -88,6 +98,7 @@ private function generate( array $args, array $assoc_args ): void { $activate = $this->get_flag_value( $assoc_args, 'activate' ); $source = $this->join_path( get_theme_root(), $parent, self::STARTER_SLUG ); $destination = $this->join_path( get_theme_root(), $machine_name ); + $version = $this->get_generated_from_version( $source ); \WP_CLI::log( sprintf( 'Generating child theme "%s" (%s) from Emulsify.', $label, $machine_name ) ); \WP_CLI::log( sprintf( 'Source: %s', $source ) ); @@ -111,6 +122,7 @@ private function generate( array $args, array $assoc_args ): void { 'label' => $label, 'machine_name' => $machine_name, 'parent' => $parent, + 'version' => $version, ) ); @@ -149,6 +161,7 @@ private function generate( array $args, array $assoc_args ): void { 'label' => $label, 'machine_name' => $machine_name, 'parent' => $parent, + 'version' => $version, ) ); $this->apply_metadata_updates( $destination, $metadata_updates ); @@ -229,6 +242,7 @@ private function collect_metadata_updates( string $root, array $config ): array $theme_label = $config['label']; $machine_name = $config['machine_name']; $parent = $config['parent']; + $version = $config['version']; // Update known metadata surfaces deliberately. Avoid blind recursive text // replacement so example prose, generated assets, and project content are @@ -258,13 +272,16 @@ function ( array $data ) use ( $machine_name ): array { $updates, $root, 'project.emulsify.json', - function ( array $data ) use ( $theme_label, $machine_name ): array { + function ( array $data ) use ( $theme_label, $machine_name, $version ): array { if ( ! isset( $data['project'] ) || ! is_array( $data['project'] ) ) { $data['project'] = array(); } - $data['project']['name'] = $theme_label; - $data['project']['machineName'] = $machine_name; + $data['project']['platform'] = 'wordpress'; + $data['project']['name'] = $theme_label; + $data['project']['machineName'] = $machine_name; + $data['project']['generatedFrom'] = self::GENERATED_FROM; + $data['project']['generatedFromVersion'] = $version; return $data; } @@ -547,6 +564,22 @@ private function count_copyable_files( string $source ): int { return $count; } + /** + * Gets the version to record in generated child theme metadata. + * + * @param string $source Starter source path. + * @return string Version string. + */ + private function get_generated_from_version( string $source ): string { + $package = $this->read_json_file( $this->join_path( dirname( $source ), 'package.json' ) ); + + if ( isset( $package['version'] ) && is_string( $package['version'] ) && '' !== trim( $package['version'] ) ) { + return trim( $package['version'] ); + } + + return self::GENERATED_FROM_VERSION; + } + /** * Gets the reason an existing destination should not be force-replaced. * @@ -589,6 +622,25 @@ private function get_destination_replacement_error( string $destination, string return 'project.emulsify.json is missing project.machineName'; } + $has_generated_from = array_key_exists( 'generatedFrom', $project['project'] ); + $has_generated_from_version = array_key_exists( 'generatedFromVersion', $project['project'] ); + + if ( $has_generated_from && ( ! is_string( $project['project']['generatedFrom'] ) || '' === trim( $project['project']['generatedFrom'] ) ) ) { + return 'project.emulsify.json has an invalid project.generatedFrom'; + } + + if ( $has_generated_from && self::GENERATED_FROM !== $project['project']['generatedFrom'] ) { + return sprintf( 'project.emulsify.json generatedFrom is "%s", expected "%s"', $project['project']['generatedFrom'], self::GENERATED_FROM ); + } + + if ( $has_generated_from && ( ! $has_generated_from_version || ! is_string( $project['project']['generatedFromVersion'] ) || '' === trim( $project['project']['generatedFromVersion'] ) ) ) { + return 'project.emulsify.json is missing project.generatedFromVersion'; + } + + if ( ! $has_generated_from && $has_generated_from_version ) { + return 'project.emulsify.json has project.generatedFromVersion without project.generatedFrom'; + } + return null; } diff --git a/whisk/.cli/init.js b/whisk/.cli/init.js index 5ade121..448a127 100644 --- a/whisk/.cli/init.js +++ b/whisk/.cli/init.js @@ -4,6 +4,8 @@ import { fileURLToPath } from 'node:url'; const STARTER_SLUG = 'whisk'; const PARENT_THEME = 'emulsify'; +const GENERATED_FROM = 'emulsify-wordpress'; +const FALLBACK_GENERATED_FROM_VERSION = '2.0.0'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const projectConfigPath = path.join(ROOT, 'project.emulsify.json'); @@ -83,6 +85,14 @@ const updatePackageJson = ({ machineName }) => { writeJsonIfChanged(filePath, data); }; +const getGeneratedFromVersion = () => { + const data = readJson(path.join(ROOT, 'package.json')); + + return typeof data.version === 'string' && data.version.trim() !== '' + ? data.version.trim() + : FALLBACK_GENERATED_FROM_VERSION; +}; + const updateLockfile = (relativePath, { machineName }) => { const filePath = path.join(ROOT, relativePath); @@ -101,10 +111,12 @@ const updateLockfile = (relativePath, { machineName }) => { writeJsonIfChanged(filePath, data); }; -const updateProjectConfig = (config, { name, machineName }) => { +const updateProjectConfig = (config, { name, machineName, generatedFromVersion }) => { config.project.platform = 'wordpress'; config.project.name = name; config.project.machineName = machineName; + config.project.generatedFrom = GENERATED_FROM; + config.project.generatedFromVersion = generatedFromVersion; writeJsonIfChanged(projectConfigPath, config); }; @@ -161,6 +173,7 @@ const main = () => { const project = { name: config.project.name, machineName: config.project.machineName, + generatedFromVersion: getGeneratedFromVersion(), }; updateStyleCss(project); diff --git a/whisk/project.emulsify.json b/whisk/project.emulsify.json index 1470922..934de88 100644 --- a/whisk/project.emulsify.json +++ b/whisk/project.emulsify.json @@ -2,7 +2,9 @@ "project": { "platform": "wordpress", "name": "whisk", - "machineName": "whisk" + "machineName": "whisk", + "generatedFrom": "emulsify-wordpress", + "generatedFromVersion": "2.0.0" }, "starter": { "repository": "https://github.com/emulsify-ds/emulsify-wordpress-starter" From 9fc90aad4cb4bea633d880c8e5db3d5d366a527c Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 29 Jun 2026 14:29:31 -0500 Subject: [PATCH 36/55] docs: add wordpress component recipes --- .github/scripts/release-check.cjs | 20 ++++ README.md | 1 + docs/acf-twig-blocks.md | 2 + docs/block-patterns.md | 2 + docs/component-recipes.md | 161 ++++++++++++++++++++++++++++++ docs/core-4-vite-workflow.md | 2 + docs/core-block-twig-rendering.md | 2 + docs/native-gutenberg-blocks.md | 2 + docs/timber-and-twig-authoring.md | 2 + 9 files changed, 194 insertions(+) create mode 100644 docs/component-recipes.md diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index f99c2e4..9549df4 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -258,8 +258,10 @@ function runStaticChecks() { architecture: readFile('docs/parent-child-architecture.md'), twig: readFile('docs/timber-and-twig-authoring.md'), workflow: readFile('docs/core-4-vite-workflow.md'), + componentRecipes: readFile('docs/component-recipes.md'), acfJson: readFile('docs/acf-local-json.md'), acfBlocks: readFile('docs/acf-twig-blocks.md'), + coreBlockTwig: readFile('docs/core-block-twig-rendering.md'), nativeBlocks: readFile('docs/native-gutenberg-blocks.md'), blockPatterns: readFile('docs/block-patterns.md'), editorEnhancements: readFile('docs/editor-enhancements.md'), @@ -302,6 +304,8 @@ function runStaticChecks() { 'docs/acf-twig-blocks.md', 'docs/asset-loading.md', 'docs/block-patterns.md', + 'docs/component-recipes.md', + 'docs/core-block-twig-rendering.md', 'docs/core-4-vite-workflow.md', 'docs/editor-enhancements.md', 'docs/native-gutenberg-blocks.md', @@ -849,6 +853,7 @@ function runStaticChecks() { 'docs/editor-policy.md', 'docs/asset-loading.md', 'docs/wp-cli-child-theme-generation.md', + 'docs/component-recipes.md', 'docs/release-process.md', ]; @@ -869,6 +874,7 @@ function runStaticChecks() { ensure(readme.includes('generatedFrom') && readme.includes('generatedFromVersion'), 'README.md should explain generated child theme source metadata.'); ensure(readme.includes('whisk/assets/images') && readme.includes('whisk/assets/icons'), 'README.md should document the generated child asset placeholders.'); ensure(readme.includes('wp emulsify "Acme Site" --machine-name=acme-site'), 'README.md should document child theme generator examples.'); + ensure(readme.includes('docs/component-recipes.md'), 'README.md should link to component recipes.'); ensure(readme.includes('Normal PR checks do not start MySQL or run the full WordPress fixture'), 'README.md should distinguish practical PR checks from the full fixture.'); ensure(readme.includes('GitHub Actions > `WordPress Theme Readiness`'), 'README.md should tell maintainers where to run the manual fixture workflow.'); ensure(readme.includes('WP_SMOKE_REQUIRED=1'), 'README.md should document required WordPress fixture smoke behavior.'); @@ -908,6 +914,7 @@ function runStaticChecks() { ensure(docs.twig.includes('Install or author project components in the structure defined by the selected Emulsify component system'), 'Twig doc should avoid prescribing a component source structure.'); ensure(docs.twig.includes('The general form is `project_machine_name:component_name`'), 'Twig doc should document the generic project component include form.'); ensure(docs.twig.includes('The legacy `@components/example-card/example-card.twig` namespace remains supported for compatible component libraries'), 'Twig doc should preserve @components compatibility language.'); + ensure(docs.twig.includes('[Component recipes](component-recipes.md)'), 'Twig doc should link to component recipes.'); ensure(docs.twig.includes('emulsify_theme_context'), 'Twig doc should document context extension.'); ensure(docs.workflow.includes('Core 4, Vite, and Storybook commands'), 'Workflow doc should use the expected command heading.'); ensure(docs.workflow.includes('"platform": "wordpress"'), 'Workflow doc should explain the WordPress platform adapter.'); @@ -916,15 +923,27 @@ function runStaticChecks() { ensure(docs.workflow.includes('assets/images') && docs.workflow.includes('assets/icons'), 'Core 4 workflow doc should document generic starter asset directories.'); ensure(docs.workflow.includes('does not include a child `theme.json` by default'), 'Core 4 workflow doc should document the child theme.json convention.'); ensure(docs.workflow.includes('The following shape is an example of a compatible component, not files shipped by Whisk'), 'Core 4 workflow doc should keep component examples documentation-only.'); + ensure(docs.workflow.includes('[Component recipes](component-recipes.md)'), 'Core 4 workflow doc should link to component recipes.'); ensure(docs.workflow.includes('{% include "project_machine_name:component_name" %}') && docs.workflow.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Core 4 workflow doc should promote generic project machine-name component includes while preserving @components compatibility.'); + ensure(docs.componentRecipes.includes('not files that must ship in every starter'), 'Component recipes doc should keep examples documentation-only.'); + ensure(docs.componentRecipes.includes('Do not add a full component library to `whisk/src/components`'), 'Component recipes doc should avoid adding active starter components to Whisk.'); + ensure(docs.componentRecipes.includes('src/components/card/card.twig'), 'Component recipes doc should include a Twig component example.'); + ensure(docs.componentRecipes.includes('src/components/card/card.stories.js'), 'Component recipes doc should include a Storybook story example.'); + ensure(docs.componentRecipes.includes('src/components/card/card.component.json'), 'Component recipes doc should include an ACF/Twig component metadata example.'); + ensure(docs.componentRecipes.includes('src/components/card/block.json'), 'Component recipes doc should include a native block metadata example.'); + ensure(docs.componentRecipes.includes('patterns/card-feature.json'), 'Component recipes doc should include a pattern JSON example.'); + ensure(docs.componentRecipes.includes('Use core block Twig rendering only'), 'Component recipes doc should explain core block Twig rendering use.'); ensure(docs.acfJson.includes('config/acf-json'), 'ACF Local JSON doc should document the child theme JSON path.'); ensure(docs.acfJson.includes('emulsify_theme_acf_json_save_path'), 'ACF Local JSON doc should document the save path filter.'); ensure(docs.acfJson.includes('commit ACF JSON files'), 'ACF Local JSON doc should tell project teams to commit ACF JSON.'); ensure(docs.acfBlocks.includes('Whisk does not include an active ACF/Twig block example'), 'ACF/Twig blocks doc should explain that starter metadata is documentation-only.'); + ensure(docs.acfBlocks.includes('[Component recipes](component-recipes.md)'), 'ACF/Twig blocks doc should link to component recipes.'); ensure(docs.acfBlocks.includes('emulsify_theme_acf_block_args'), 'ACF/Twig blocks doc should document the block args filter.'); ensure(docs.nativeBlocks.includes('The starter does not include an active native block example'), 'Native blocks doc should avoid over-claiming a native example.'); + ensure(docs.nativeBlocks.includes('[Component recipes](component-recipes.md)'), 'Native block doc should link to component recipes.'); ensure(docs.nativeBlocks.includes('emulsify_theme_native_block_directories'), 'Native blocks doc should document the native block directories filter.'); ensure(docs.blockPatterns.includes('patterns/*.json'), 'Block patterns doc should document JSON pattern discovery.'); + ensure(docs.blockPatterns.includes('[Component recipes](component-recipes.md)'), 'Block patterns doc should link to component recipes.'); ensure(docs.blockPatterns.includes('emulsify_theme_pattern_directories'), 'Block patterns doc should document directory filtering.'); ensure(docs.blockPatterns.includes('emulsify_theme_pattern_args'), 'Block patterns doc should document final args filtering.'); ensure(docs.editorEnhancements.includes('emulsify_theme_editor_enhancements_config'), 'Editor enhancements doc should document the config filter.'); @@ -936,6 +955,7 @@ function runStaticChecks() { ensure(docs.editorPolicy.includes('auto_allow_pattern_blocks'), 'Editor policy doc should document pattern JSON auto-allow behavior.'); ensure(docs.editorPolicy.includes('emulsify_theme_block_support_overrides'), 'Editor policy doc should document block support overrides.'); ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); + ensure(docs.coreBlockTwig.includes('[Component recipes](component-recipes.md)'), 'Core block Twig rendering doc should link to component recipes.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); ensure(docs.cli.includes('Force replacement safety') && docs.cli.includes('Emulsify-generated child theme markers'), 'WP-CLI doc should document force replacement safety.'); ensure(docs.cli.includes('Upgrade and support diagnostics') && docs.cli.includes('generatedFromVersion'), 'WP-CLI doc should document generated child theme lineage diagnostics.'); diff --git a/README.md b/README.md index 48edf77..3711466 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ For Emulsify CLI integration, the standalone starter repository is `https://gith - [Parent and child theme architecture](docs/parent-child-architecture.md) - [Timber and Twig authoring](docs/timber-and-twig-authoring.md) - [Emulsify Core 4 and Vite workflow](docs/core-4-vite-workflow.md) +- [Component recipes](docs/component-recipes.md) - [ACF Local JSON](docs/acf-local-json.md) - [ACF/Twig blocks](docs/acf-twig-blocks.md) - [Core block Twig rendering](docs/core-block-twig-rendering.md) diff --git a/docs/acf-twig-blocks.md b/docs/acf-twig-blocks.md index cea08c9..a7b9e83 100644 --- a/docs/acf-twig-blocks.md +++ b/docs/acf-twig-blocks.md @@ -28,6 +28,8 @@ dist/components/example-card/example-card.twig This documentation example proves the shape, but it does not ship project field groups or content model assumptions. +See [Component recipes](component-recipes.md) for a short `*.component.json` example and guidance on when to choose ACF/Twig blocks. + ## Registration The parent theme reads the component metadata, merges default block arguments, and calls `acf_register_block_type()`. Rendering happens through Timber with block data, ACF fields, preview state, and the normal global Timber context. diff --git a/docs/block-patterns.md b/docs/block-patterns.md index c3eac7b..c4ab0a9 100644 --- a/docs/block-patterns.md +++ b/docs/block-patterns.md @@ -99,3 +99,5 @@ add_filter( ``` Generated child themes include an empty `patterns` placeholder, but no content patterns by default. If a project adds JSON patterns under `whisk/patterns`, the generator rewrites pattern names from the `whisk/` namespace to the generated child theme machine name. + +See [Component recipes](component-recipes.md) for a short pattern example and guidance on when a pattern is a better fit than a custom block. diff --git a/docs/component-recipes.md b/docs/component-recipes.md new file mode 100644 index 0000000..54ed823 --- /dev/null +++ b/docs/component-recipes.md @@ -0,0 +1,161 @@ +# Component recipes + +Whisk intentionally keeps `src/components` empty until a project installs the component system it wants to use. The snippets below are recipes for generated child themes, not files that must ship in every starter. Add them to a project child theme only when that project needs them. + +Do not add a full component library to `whisk/src/components`. Use Whisk as a light starter, then let the selected Emulsify component system define the project source structure. + +## When to use each option + +Use a plain Twig component for reusable markup that is rendered from templates, ACF/Twig blocks, patterns, or other components. + +Use an ACF/Twig block when editors need a custom block backed by ACF fields and the frontend should render through Twig. + +Use a native Gutenberg block when the project needs WordPress Block API features such as attributes, supports, transforms, editor scripts, view scripts, or dynamic rendering. + +Use core block Twig rendering only when a project intentionally replaces the frontend output of an existing core block such as `core/paragraph` or `core/heading`. + +Use a block pattern when editors need a reusable layout made from existing blocks rather than a new block type. + +## Plain Twig component + +Example generated child theme path: + +```text +src/components/card/card.twig +``` + +```twig + +``` + +Include it from a Twig template with the generated project machine name: + +```twig +{% include "project_machine_name:card" with { + eyebrow: 'News', + title: post.title, + url: post.link +} only %} +``` + +## Storybook story + +Story files belong with the component source when the selected component system supports that convention: + +```text +src/components/card/card.stories.js +``` + +```js +import template from './card.twig'; + +export default { + title: 'Components/Card', +}; + +export const Default = { + render: (args) => template(args), + args: { + eyebrow: 'News', + title: 'Example card', + url: '#', + link_text: 'Read more', + }, +}; +``` + +Adjust the import and render shape to the selected component system's Storybook Twig loader. + +## Optional ACF/Twig block metadata + +Add `*.component.json` only when the component should register as an ACF/Twig block after the child theme build: + +```text +src/components/card/card.component.json +``` + +```json +{ + "name": "emulsify-card", + "title": "Card", + "description": "A short card block rendered with Twig.", + "category": "widgets", + "icon": "index-card", + "mode": "preview" +} +``` + +After build, the parent scans `dist/components` for the built Twig template and matching `*.component.json` metadata. Whisk does not ship active ACF/Twig block metadata by default. + +## Optional native Gutenberg block metadata + +Add `block.json` only when the component is a native WordPress block: + +```text +src/components/card/block.json +``` + +```json +{ + "apiVersion": 3, + "name": "project/card", + "title": "Card", + "category": "widgets", + "icon": "index-card", + "description": "A project card block.", + "supports": { + "html": false + }, + "textdomain": "project" +} +``` + +Native blocks usually need editor scripts, attributes, supports, and save or render behavior defined by the project's chosen block tooling. Whisk does not ship a native block example by default. + +## Optional pattern JSON + +Use a block pattern when existing blocks can express the editor experience: + +```text +patterns/card-feature.json +``` + +```json +{ + "name": "project/card-feature", + "title": "Card feature", + "description": "A simple card-style feature pattern.", + "categories": ["text"], + "content": "

    Feature title

    Feature copy.

    " +} +``` + +Patterns live in the generated child theme's `patterns` directory. The child theme generator updates copied starter pattern namespaces from `whisk/*` to the generated machine name, but new project patterns should use the project namespace directly. + +## Core block Twig rendering + +Use core block Twig rendering only for explicit replacements of existing WordPress core block output: + +```php +add_filter( 'emulsify_theme_core_block_twig_rendering_enabled', '__return_true' ); + +add_filter( + 'emulsify_theme_core_block_twig_template_map', + function ( array $map ): array { + $map['core/heading'] = 'dist/components/heading/heading.twig'; + return $map; + } +); +``` + +Keep this opt-in narrow. Replacing core block markup can affect block validation, editor expectations, accessibility, and plugin integrations. diff --git a/docs/core-4-vite-workflow.md b/docs/core-4-vite-workflow.md index 9433bf9..af0feba 100644 --- a/docs/core-4-vite-workflow.md +++ b/docs/core-4-vite-workflow.md @@ -76,6 +76,8 @@ src/components/example-card/ Project teams should add, rename, or remove component files according to the selected component system and project naming model. This parent theme does not require a button component or any specific foundation, layout, or token Sass files. +See [Component recipes](component-recipes.md) for small Twig, Storybook, ACF/Twig, native block, and pattern examples that can be copied into a generated child theme when a project needs them. + When a component system builds `*.component.json` files into `dist/components`, the parent theme can discover those files for optional ACF/Twig block registration. Documentation examples prove the shape, but Whisk does not register any starter ACF/Twig blocks by default. For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`. The general form is `{% include "project_machine_name:component_name" %}`. The legacy `@components/component-name/component-name.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work. diff --git a/docs/core-block-twig-rendering.md b/docs/core-block-twig-rendering.md index 3a8be18..de9db9a 100644 --- a/docs/core-block-twig-rendering.md +++ b/docs/core-block-twig-rendering.md @@ -4,6 +4,8 @@ The parent theme includes an experimental `Core_Block_Twig_Renderer` service tha Use this carefully. Replacing core block output can affect block validation, style supports, accessibility attributes, plugin integrations, and future WordPress markup changes. Keep Twig output compatible with the saved block attributes and test editing, saving, reloading, and frontend rendering for every mapped block. +See [Component recipes](component-recipes.md) for guidance on when to choose core block Twig rendering instead of a plain Twig component, ACF/Twig block, native block, or pattern. + ## Enable the service Enable rendering and provide an explicit block-to-template map: diff --git a/docs/native-gutenberg-blocks.md b/docs/native-gutenberg-blocks.md index 8f486aa..e71e947 100644 --- a/docs/native-gutenberg-blocks.md +++ b/docs/native-gutenberg-blocks.md @@ -8,6 +8,8 @@ Add a `block.json` file to a component folder and build the child theme so the b The starter does not include an active native block example. Native blocks usually need project-specific editor scripts, attributes, supports, and block behavior, so projects should add them intentionally. +See [Component recipes](component-recipes.md) for a short `block.json` example and guidance on when to choose native blocks. + ## When to use native blocks Use native blocks when the project needs block editor APIs such as: diff --git a/docs/timber-and-twig-authoring.md b/docs/timber-and-twig-authoring.md index d75f899..3cee1f8 100644 --- a/docs/timber-and-twig-authoring.md +++ b/docs/timber-and-twig-authoring.md @@ -61,6 +61,8 @@ Use `emulsify_theme_project_component_roots` to adjust roots for `project_machin Root-level `components` and `src/components` directories are compatibility paths, not starter requirements. Do not remove existing `@components` includes during migration; both forms are supported. +See [Component recipes](component-recipes.md) for a small Twig component and Storybook story example that can be added to a generated child theme. + ## Component-system namespaces For legacy `@namespace/path/to/template.twig` references that should work in both Emulsify Core and WordPress, configure the namespace roots in `project.emulsify.json` with Core's existing `variant.structureImplementations` shape: From 6d25eb76f2bc16b7ecb7a29a7d82e176f541a64d Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 12:34:38 -0500 Subject: [PATCH 37/55] docs: add post 2x optimization roadmap --- .github/scripts/release-check.cjs | 20 +++++++++++ README.md | 1 + docs/post-2x-optimization-roadmap.md | 53 ++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 docs/post-2x-optimization-roadmap.md diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 9549df4..e952fc5 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -269,6 +269,7 @@ function runStaticChecks() { assets: readFile('docs/asset-loading.md'), cli: readFile('docs/wp-cli-child-theme-generation.md'), release: readFile('docs/release-process.md'), + post2xRoadmap: readFile('docs/post-2x-optimization-roadmap.md'), }; const docsText = Object.values(docs).join('\n'); const license = readFile('LICENSE'); @@ -311,6 +312,7 @@ function runStaticChecks() { 'docs/native-gutenberg-blocks.md', 'docs/editor-policy.md', 'docs/parent-child-architecture.md', + 'docs/post-2x-optimization-roadmap.md', 'docs/release-process.md', 'docs/sister-project-parity.md', 'docs/timber-and-twig-authoring.md', @@ -855,6 +857,7 @@ function runStaticChecks() { 'docs/wp-cli-child-theme-generation.md', 'docs/component-recipes.md', 'docs/release-process.md', + 'docs/post-2x-optimization-roadmap.md', ]; ensure(readme.includes('Emulsify WordPress 2.0.0 is a Timber-first WordPress parent theme'), 'README.md should describe the 2.0.0 Timber-first parent theme.'); @@ -970,6 +973,23 @@ function runStaticChecks() { ensure(docs.release.includes('ACF/Twig and native `block.json` discovery'), 'Release process doc should document block discovery fixture coverage.'); ensure(docs.release.includes('WP_SMOKE_REQUIRED=1'), 'Release process doc should document required fixture smoke behavior.'); ensure(docs.release.includes('Manual dispatch can also run the Whisk Storybook build and accessibility audit'), 'Release process doc should document optional extended checks.'); + ensure(docs.post2xRoadmap.includes('follow-up opportunities for focused minor releases, not 2.0 blockers'), 'Post-2.x roadmap should frame items as follow-up opportunities.'); + ensure(docs.post2xRoadmap.includes('Ship 2.0 without adding new runtime features'), 'Post-2.x roadmap should keep 2.0 focused.'); + ensure(docs.post2xRoadmap.includes('PSR-4 autoloading and grouped runtime directories'), 'Post-2.x roadmap should include the code organization milestone.'); + ensure(docs.post2xRoadmap.includes('optional manifest-driven asset loading'), 'Post-2.x roadmap should include the asset manifest milestone.'); + ensure(docs.post2xRoadmap.includes('wp emulsify doctor'), 'Post-2.x roadmap should include CLI diagnostics.'); + ensure(docs.post2xRoadmap.includes('persistent discovery caching after manifest behavior is stable'), 'Post-2.x roadmap should place persistent caching after manifests.'); + for (const heading of [ + '## Code organization', + '## Runtime architecture', + '## Asset loading and performance', + '## Component/block discovery', + '## CLI diagnostics', + '## Editor and block feature modules', + '## Documentation and support tooling', + ]) { + ensure(docs.post2xRoadmap.includes(heading), `Post-2.x roadmap should include ${heading}.`); + } ensure(pullRequestTemplate.includes('2.0 release branch merge') && pullRequestTemplate.includes('wordpress_fixture'), 'PR template should include the 2.0 manual fixture checklist item.'); ensure(/duplicate[\w\s/`.-]*skipped instead of being registered twice/i.test(docsText), 'Docs should document duplicate block handling.'); ensure(docsText.includes('normal frontend visitors') || docsText.includes('Normal frontend visitors'), 'Docs should document that duplicate diagnostics avoid frontend noise.'); diff --git a/README.md b/README.md index 3711466..5adb356 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ For Emulsify CLI integration, the standalone starter repository is `https://gith - [Asset loading](docs/asset-loading.md) - [WP-CLI child theme generation](docs/wp-cli-child-theme-generation.md) - [Release process](docs/release-process.md) +- [Post-2.x optimization roadmap](docs/post-2x-optimization-roadmap.md) ## License diff --git a/docs/post-2x-optimization-roadmap.md b/docs/post-2x-optimization-roadmap.md new file mode 100644 index 0000000..c73f52a --- /dev/null +++ b/docs/post-2x-optimization-roadmap.md @@ -0,0 +1,53 @@ +# Post-2.x optimization roadmap + +Emulsify WordPress 2.0 should stay focused on the parent and generated child theme release contract. The items below are follow-up opportunities for focused minor releases, not 2.0 blockers. + +## Recommended implementation order + +1. Ship 2.0 without adding new runtime features. +2. Add PSR-4 autoloading and grouped runtime directories in a minor release. +3. Add optional manifest-driven asset loading. +4. Add CLI diagnostics such as `wp emulsify doctor`. +5. Add optional persistent discovery caching after manifest behavior is stable. + +## Code organization + +- Move runtime PHP classes into grouped directories such as setup, assets, blocks, editor, and CLI while preserving public hooks and filters. +- Add Composer PSR-4 autoloading for namespaced classes in a minor release. +- Keep compatibility shims or clear upgrade notes for any class-loading paths that existing child themes may reference. + +## Runtime architecture + +- Split bootstrap responsibilities into small provider-style classes so setup, Twig, assets, block registration, and editor policy can be reasoned about independently. +- Keep the parent theme focused on reusable WordPress runtime behavior; project behavior should remain in child themes or site plugins. +- Document service boundaries before adding new runtime services. + +## Asset loading and performance + +- Add optional manifest-driven asset loading for `dist/global` and component assets. +- Keep current directory scanning as the fallback while manifests are optional. +- Avoid requiring build output in fresh Whisk child themes until a project installs a component system. + +## Component/block discovery + +- Refine child-first discovery diagnostics for duplicate ACF/Twig and native block definitions. +- Consider persistent discovery caching only after manifest behavior is stable and cache invalidation is explicit. +- Keep discovery safe when ACF or native block APIs are unavailable. + +## CLI diagnostics + +- Add a read-only `wp emulsify doctor` command that reports parent install state, active child theme, `Template` header, Timber availability, generated metadata, and missing build output. +- Use diagnostics before adding repair commands. +- Keep generation behavior stable so the 2.0 WP-CLI contract remains predictable. + +## Editor and block feature modules + +- Expand editor enhancements as opt-in modules, not broad parent-theme defaults. +- Treat ACF/Twig blocks, native Gutenberg blocks, core block Twig rendering, and pattern policies as independently enableable areas. +- Add documentation and smoke coverage with each module. + +## Documentation and support tooling + +- Maintain upgrade checklists, troubleshooting notes, and component recipes as follow-up support material. +- Surface generated child theme lineage metadata in diagnostics and support docs. +- Keep examples as recipes instead of active starter files in `whisk/src/components`. From 099c5948c6619ce6b35b5352ddd1f7499e3766d9 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 12:42:28 -0500 Subject: [PATCH 38/55] refactor: add runtime autoloading --- .github/scripts/bootstrap-loader-smoke.php | 114 +++++++++++++++++++++ .github/scripts/pr-validation.cjs | 1 + .github/scripts/release-check.cjs | 27 ++++- README.md | 2 + composer.json | 8 ++ docs/post-2x-optimization-roadmap.md | 4 +- includes/class-bootstrap.php | 79 ++++++++++---- package.json | 1 + 8 files changed, 210 insertions(+), 26 deletions(-) create mode 100644 .github/scripts/bootstrap-loader-smoke.php diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php new file mode 100644 index 0000000..c5055e9 --- /dev/null +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -0,0 +1,114 @@ +&1', $output, $status ); + unlink( $script ); + + if ( 0 !== $status ) { + throw new RuntimeException( implode( "\n", $output ) ); + } +} + +$repo_root = dirname( __DIR__, 2 ); +$autoload = $repo_root . '/vendor/autoload.php'; +$classes = array( + 'Emulsify\\Theme\\Bootstrap', + 'Emulsify\\Theme\\Setup', + 'Emulsify\\Theme\\Assets', + 'Emulsify\\Theme\\Acf_Local_JSON', + 'Emulsify\\Theme\\AttributeBag', + 'Emulsify\\Theme\\Cli', + 'Emulsify\\Theme\\Context', + 'Emulsify\\Theme\\Core_Block_Twig_Renderer', + 'Emulsify\\Theme\\Editor_Enhancements', + 'Emulsify\\Theme\\Editor_Policy', + 'Emulsify\\Theme\\Missing_Timber', + 'Emulsify\\Theme\\Patterns', + 'Emulsify\\Theme\\Timber_Integration', + 'Emulsify\\Theme\\Twig', + 'Emulsify\\Theme\\Blocks\\Component_Locator', + 'Emulsify\\Theme\\Blocks\\Acf_Blocks', + 'Emulsify\\Theme\\Blocks\\Native_Blocks', + 'Emulsify\\Theme\\Blocks\\Registry', +); + +emulsify_bootstrap_loader_assert( is_readable( $autoload ), 'Run composer install or composer dump-autoload before the Bootstrap loader smoke test.' ); + +$classes_export = var_export( $classes, true ); +$repo_export = var_export( $repo_root, true ); + +emulsify_bootstrap_loader_run_php( + <<newInstanceWithoutConstructor(); +\$theme_dir = \$reflection->getProperty( 'theme_dir' ); +\$theme_dir->setAccessible( true ); +\$theme_dir->setValue( \$bootstrap, \$repo_root ); +\$load_classes = \$reflection->getMethod( 'load_classes' ); +\$load_classes->setAccessible( true ); +\$load_classes->invoke( \$bootstrap ); + +foreach ( \$classes as \$class ) { + if ( ! class_exists( \$class, true ) ) { + throw new RuntimeException( sprintf( 'Fallback loader did not load %s.', \$class ) ); + } +} +PHP +); + +echo "Bootstrap loader smoke checks passed.\n"; diff --git a/.github/scripts/pr-validation.cjs b/.github/scripts/pr-validation.cjs index d5bba69..d6535c6 100644 --- a/.github/scripts/pr-validation.cjs +++ b/.github/scripts/pr-validation.cjs @@ -30,6 +30,7 @@ run('Install Composer dependencies for Twig smoke coverage', 'composer', [ '--no-progress', '--prefer-dist', ]); +run('Run Bootstrap loader smoke test', 'npm', ['run', 'smoke:bootstrap-loader']); run('Run PHP lint', 'npm', ['run', 'lint:php']); run('Run ACF Local JSON smoke test', 'npm', ['run', 'smoke:acf-json']); run('Run Twig attribute helper smoke test', 'npm', ['run', 'smoke:attributes']); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index e952fc5..6a65da2 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -340,6 +340,7 @@ function runStaticChecks() { 'templates/single.twig', '.github/scripts/acf-local-json-smoke.php', '.github/scripts/attribute-helper-smoke.php', + '.github/scripts/bootstrap-loader-smoke.php', '.github/scripts/child-theme-generator-smoke.php', '.github/scripts/component-locator-smoke.php', '.github/scripts/editor-enhancements-smoke.php', @@ -382,6 +383,7 @@ function runStaticChecks() { ensure(rootPackage.scripts['release:check'] === 'node .github/scripts/release-check.cjs', 'package.json should expose npm run release:check.'); ensure(rootPackage.scripts['smoke:acf-json'] === 'php .github/scripts/acf-local-json-smoke.php', 'package.json should expose npm run smoke:acf-json.'); ensure(rootPackage.scripts['smoke:attributes'] === 'php .github/scripts/attribute-helper-smoke.php', 'package.json should expose npm run smoke:attributes.'); + ensure(rootPackage.scripts['smoke:bootstrap-loader'] === 'php .github/scripts/bootstrap-loader-smoke.php', 'package.json should expose npm run smoke:bootstrap-loader.'); ensure(rootPackage.scripts['smoke:child-theme-generator'] === 'php .github/scripts/child-theme-generator-smoke.php', 'package.json should expose npm run smoke:child-theme-generator.'); ensure(rootPackage.scripts['smoke:component-locator'] === 'php .github/scripts/component-locator-smoke.php', 'package.json should expose npm run smoke:component-locator.'); ensure(rootPackage.scripts['smoke:editor-enhancements'] === 'php .github/scripts/editor-enhancements-smoke.php', 'package.json should expose npm run smoke:editor-enhancements.'); @@ -400,6 +402,8 @@ function runStaticChecks() { ensure(!Object.hasOwn(composer, 'minimum-stability'), 'composer.json should not lower release stability for a stable parent theme.'); ensure(!Object.hasOwn(composer, 'prefer-stable'), 'composer.json should not keep prefer-stable when stable-only constraints are sufficient.'); ensure(composer.require && composer.require['timber/timber'] === '^2.3', 'composer.json should keep the Timber 2 dependency constraint.'); + ensure(composer.autoload && composer.autoload['psr-4'] && composer.autoload['psr-4']['Emulsify\\Theme\\'] === 'includes/', 'composer.json should expose the runtime namespace through PSR-4 autoloading.'); + ensure(Array.isArray(composer.autoload.classmap) && composer.autoload.classmap.includes('includes/'), 'composer.json should classmap current runtime files until all filenames are PSR-4-shaped.'); ensureParentThemeLanguage('composer.json description', composer.description); return `Validated root package ${rootPackage.version} and composer metadata.`; }); @@ -432,7 +436,11 @@ function runStaticChecks() { const attributeBag = readFile('includes/class-attribute-bag.php'); const smoke = readFile('.github/scripts/attribute-helper-smoke.php'); - ensure(bootstrap.includes('class-attribute-bag.php'), 'Bootstrap should load AttributeBag before Twig helpers.'); + ensure(bootstrap.includes('load_vendor_autoload();') && bootstrap.includes('load_classes();'), 'Bootstrap should load Composer before registering fallback runtime loading.'); + ensure(bootstrap.indexOf('load_vendor_autoload();') < bootstrap.indexOf('load_classes();'), 'Bootstrap should try Composer autoloading before fallback runtime loading.'); + ensure(bootstrap.includes('spl_autoload_register'), 'Bootstrap should register a fallback runtime autoloader.'); + ensure(bootstrap.includes('runtime_class_file'), 'Bootstrap should resolve runtime classes through a fallback file mapper.'); + ensure(bootstrap.includes('class_file_slug'), 'Bootstrap should support legacy class-* runtime filenames.'); ensure(attributeBag.includes('implements \\Stringable'), 'AttributeBag should serialize safely in Twig string contexts.'); ensure(attributeBag.includes('function addClass'), 'AttributeBag should support Core-style class merging.'); ensure(attributeBag.includes('function toString'), 'AttributeBag should expose explicit serialization.'); @@ -443,6 +451,19 @@ function runStaticChecks() { return 'Attribute helper runtime and smoke fixture are wired.'; }); + runStaticCheck('Bootstrap runtime autoloading', () => { + const bootstrap = readFile('includes/class-bootstrap.php'); + const smoke = readFile('.github/scripts/bootstrap-loader-smoke.php'); + + ensure(bootstrap.includes('$psr4_file'), 'Bootstrap fallback loader should check PSR-4-shaped runtime paths.'); + ensure(bootstrap.includes("'class-' . $this->class_file_slug"), 'Bootstrap fallback loader should preserve legacy class-* filename support.'); + ensure(smoke.includes("require_once \\$repo_root . '/vendor/autoload.php'"), 'Bootstrap loader smoke should verify Composer autoloading.'); + ensure(smoke.includes("require_once \\$repo_root . '/includes/class-bootstrap.php'"), 'Bootstrap loader smoke should verify fallback loading without Composer.'); + ensure(smoke.includes('Emulsify\\\\Theme\\\\Blocks\\\\Registry'), 'Bootstrap loader smoke should cover nested block runtime classes.'); + ensure(smoke.includes('Fallback loader did not load'), 'Bootstrap loader smoke should fail clearly when fallback loading breaks.'); + return 'Composer autoloading and Bootstrap fallback loading are covered.'; + }); + runStaticCheck('Child theme generator', () => { const cli = readFile('includes/class-cli.php'); const smoke = readFile('.github/scripts/child-theme-generator-smoke.php'); @@ -826,6 +847,8 @@ function runStaticChecks() { ensure(prValidationScript.includes('lint:php'), 'PR validation should run PHP lint.'); ensure(prValidationScript.includes('smoke:acf-json'), 'PR validation should run the ACF Local JSON smoke test.'); ensure(prValidationScript.includes('smoke:attributes'), 'PR validation should run the attribute helper smoke test.'); + ensure(prValidationScript.includes('smoke:bootstrap-loader'), 'PR validation should run the Bootstrap loader smoke test.'); + ensure(prValidationScript.indexOf('composer') < prValidationScript.indexOf('smoke:bootstrap-loader'), 'PR validation should install Composer dependencies before checking runtime autoloading.'); ensure(prValidationScript.includes('smoke:child-theme-generator'), 'PR validation should run the child theme generator smoke test.'); ensure(prValidationScript.includes('smoke:component-locator'), 'PR validation should run the component locator smoke test.'); ensure(prValidationScript.includes('smoke:editor-enhancements'), 'PR validation should run the editor enhancements smoke test.'); @@ -975,7 +998,7 @@ function runStaticChecks() { ensure(docs.release.includes('Manual dispatch can also run the Whisk Storybook build and accessibility audit'), 'Release process doc should document optional extended checks.'); ensure(docs.post2xRoadmap.includes('follow-up opportunities for focused minor releases, not 2.0 blockers'), 'Post-2.x roadmap should frame items as follow-up opportunities.'); ensure(docs.post2xRoadmap.includes('Ship 2.0 without adding new runtime features'), 'Post-2.x roadmap should keep 2.0 focused.'); - ensure(docs.post2xRoadmap.includes('PSR-4 autoloading and grouped runtime directories'), 'Post-2.x roadmap should include the code organization milestone.'); + ensure(docs.post2xRoadmap.includes('Build on Composer PSR-4 autoloading with grouped runtime directories'), 'Post-2.x roadmap should include the code organization milestone.'); ensure(docs.post2xRoadmap.includes('optional manifest-driven asset loading'), 'Post-2.x roadmap should include the asset manifest milestone.'); ensure(docs.post2xRoadmap.includes('wp emulsify doctor'), 'Post-2.x roadmap should include CLI diagnostics.'); ensure(docs.post2xRoadmap.includes('persistent discovery caching after manifest behavior is stable'), 'Post-2.x roadmap should place persistent caching after manifests.'); diff --git a/README.md b/README.md index 5adb356..db8393b 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ composer install npm ci --ignore-scripts ``` +Composer install creates the runtime autoloader. After adding or renaming parent runtime classes, run `composer dump-autoload` so Composer's optimized classmap sees the current files. Manual theme installs without Composer still use the Bootstrap fallback loader. + | Command | Purpose | | --- | --- | | `npm run lint:php` | Lint all PHP files with `php -l`. | diff --git a/composer.json b/composer.json index 263a44e..642be51 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,14 @@ "require": { "timber/timber": "^2.3" }, + "autoload": { + "psr-4": { + "Emulsify\\Theme\\": "includes/" + }, + "classmap": [ + "includes/" + ] + }, "config": { "optimize-autoloader": true, "sort-packages": true, diff --git a/docs/post-2x-optimization-roadmap.md b/docs/post-2x-optimization-roadmap.md index c73f52a..7677825 100644 --- a/docs/post-2x-optimization-roadmap.md +++ b/docs/post-2x-optimization-roadmap.md @@ -5,7 +5,7 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child the ## Recommended implementation order 1. Ship 2.0 without adding new runtime features. -2. Add PSR-4 autoloading and grouped runtime directories in a minor release. +2. Build on Composer PSR-4 autoloading with grouped runtime directories in a minor release. 3. Add optional manifest-driven asset loading. 4. Add CLI diagnostics such as `wp emulsify doctor`. 5. Add optional persistent discovery caching after manifest behavior is stable. @@ -13,7 +13,7 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child the ## Code organization - Move runtime PHP classes into grouped directories such as setup, assets, blocks, editor, and CLI while preserving public hooks and filters. -- Add Composer PSR-4 autoloading for namespaced classes in a minor release. +- Move current legacy class filenames toward clean PSR-4 paths in a minor release. - Keep compatibility shims or clear upgrade notes for any class-loading paths that existing child themes may reference. ## Runtime architecture diff --git a/includes/class-bootstrap.php b/includes/class-bootstrap.php index edb535f..b8a8d3a 100644 --- a/includes/class-bootstrap.php +++ b/includes/class-bootstrap.php @@ -94,30 +94,65 @@ private function load_vendor_autoload(): void { * @return void */ private function load_classes(): void { - // Keep load order explicit. Shared value objects and block discovery - // helpers are required before the services that instantiate them. - $files = array( - 'class-acf-local-json.php', - 'class-attribute-bag.php', - 'class-assets.php', - 'Blocks/class-component-locator.php', - 'Blocks/class-acf-blocks.php', - 'Blocks/class-native-blocks.php', - 'Blocks/class-registry.php', - 'class-cli.php', - 'class-context.php', - 'class-core-block-twig-renderer.php', - 'class-editor-enhancements.php', - 'class-editor-policy.php', - 'class-missing-timber.php', - 'class-patterns.php', - 'class-setup.php', - 'class-timber-integration.php', - 'class-twig.php', + spl_autoload_register( + function ( string $class ): void { + $this->autoload_runtime_class( $class ); + } ); + } + + /** + * Loads runtime classes when Composer autoloading is unavailable. + * + * @param string $class Fully qualified class name. + * @return void + */ + private function autoload_runtime_class( string $class ): void { + $file = $this->runtime_class_file( $class ); + + if ( null !== $file && is_readable( $file ) ) { + require_once $file; + } + } + + /** + * Resolves runtime class files for PSR-4 paths and legacy class-* filenames. + * + * @param string $class Fully qualified class name. + * @return string|null Runtime class file path, or null for another namespace. + */ + private function runtime_class_file( string $class ): ?string { + $prefix = __NAMESPACE__ . '\\'; + + if ( 0 !== strpos( $class, $prefix ) ) { + return null; + } - foreach ( $files as $file ) { - require_once __DIR__ . '/' . $file; + $relative_class = substr( $class, strlen( $prefix ) ); + $relative_path = str_replace( '\\', '/', $relative_class ); + $psr4_file = $this->theme_dir . '/includes/' . $relative_path . '.php'; + + if ( is_readable( $psr4_file ) ) { + return $psr4_file; } + + $parts = explode( '/', $relative_path ); + $class_name = array_pop( $parts ); + $directory = empty( $parts ) ? '' : implode( '/', $parts ) . '/'; + + return $this->theme_dir . '/includes/' . $directory . 'class-' . $this->class_file_slug( $class_name ) . '.php'; + } + + /** + * Converts a runtime class name to the legacy class-* file slug. + * + * @param string $class_name Short class name. + * @return string File slug. + */ + private function class_file_slug( string $class_name ): string { + $slug = str_replace( '_', '-', $class_name ); + $slug = preg_replace( '/(?<=[a-z0-9])([A-Z])/', '-$1', $slug ); + + return strtolower( (string) $slug ); } } diff --git a/package.json b/package.json index f45ea71..f4b0ee6 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "release:check": "node .github/scripts/release-check.cjs", "smoke:acf-json": "php .github/scripts/acf-local-json-smoke.php", "smoke:attributes": "php .github/scripts/attribute-helper-smoke.php", + "smoke:bootstrap-loader": "php .github/scripts/bootstrap-loader-smoke.php", "smoke:child-theme-generator": "php .github/scripts/child-theme-generator-smoke.php", "smoke:component-locator": "php .github/scripts/component-locator-smoke.php", "smoke:core-block-twig": "php .github/scripts/core-block-twig-renderer-smoke.php", From cb47609f336784ab9183b95790aab7c81ed75fb6 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 12:53:03 -0500 Subject: [PATCH 39/55] refactor: group runtime services by domain --- .github/scripts/acf-local-json-smoke.php | 6 +- .github/scripts/attribute-helper-smoke.php | 8 +- .github/scripts/bootstrap-loader-smoke.php | 56 +++++++++--- .../scripts/child-theme-generator-smoke.php | 4 +- .github/scripts/component-locator-smoke.php | 10 +-- .../core-block-twig-renderer-smoke.php | 6 +- .github/scripts/editor-enhancements-smoke.php | 4 +- .github/scripts/editor-policy-smoke.php | 4 +- .github/scripts/pattern-registry-smoke.php | 4 +- .github/scripts/release-check.cjs | 90 +++++++++++-------- .github/scripts/theme-filters-smoke.php | 30 +++---- .../scripts/twig-project-namespace-smoke.php | 6 +- .github/scripts/wordpress-fixture-smoke.cjs | 6 +- composer.json | 4 +- docs/core-block-twig-rendering.md | 2 +- docs/editor-policy.md | 2 +- functions.php | 2 +- .../LocalJson.php} | 4 +- .../{class-acf-blocks.php => AcfBlocks.php} | 10 +-- ...onent-locator.php => ComponentLocator.php} | 2 +- .../CoreBlockTwigRenderer.php} | 4 +- ...ass-native-blocks.php => NativeBlocks.php} | 12 +-- .../Patterns.php} | 2 +- .../{class-registry.php => Registry.php} | 6 +- .../{class-bootstrap.php => Bootstrap.php} | 61 ++++++------- .../GenerateChildThemeCommand.php} | 4 +- includes/Compatibility.php | 33 +++++++ .../Enhancements.php} | 4 +- .../Policy.php} | 4 +- .../{class-assets.php => Runtime/Assets.php} | 4 +- .../Context.php} | 2 +- .../MissingTimber.php} | 4 +- .../{class-setup.php => Runtime/Setup.php} | 2 +- .../TimberIntegration.php} | 4 +- includes/{class-twig.php => Runtime/Twig.php} | 4 +- .../AttributeBag.php} | 2 +- 36 files changed, 248 insertions(+), 164 deletions(-) rename includes/{class-acf-local-json.php => Acf/LocalJson.php} (98%) rename includes/Blocks/{class-acf-blocks.php => AcfBlocks.php} (98%) rename includes/Blocks/{class-component-locator.php => ComponentLocator.php} (99%) rename includes/{class-core-block-twig-renderer.php => Blocks/CoreBlockTwigRenderer.php} (99%) rename includes/Blocks/{class-native-blocks.php => NativeBlocks.php} (94%) rename includes/{class-patterns.php => Blocks/Patterns.php} (99%) rename includes/Blocks/{class-registry.php => Registry.php} (75%) rename includes/{class-bootstrap.php => Bootstrap.php} (69%) rename includes/{class-cli.php => Cli/GenerateChildThemeCommand.php} (99%) create mode 100644 includes/Compatibility.php rename includes/{class-editor-enhancements.php => Editor/Enhancements.php} (99%) rename includes/{class-editor-policy.php => Editor/Policy.php} (99%) rename includes/{class-assets.php => Runtime/Assets.php} (98%) rename includes/{class-context.php => Runtime/Context.php} (99%) rename includes/{class-missing-timber.php => Runtime/MissingTimber.php} (96%) rename includes/{class-setup.php => Runtime/Setup.php} (98%) rename includes/{class-timber-integration.php => Runtime/TimberIntegration.php} (90%) rename includes/{class-twig.php => Runtime/Twig.php} (99%) rename includes/{class-attribute-bag.php => Support/AttributeBag.php} (99%) diff --git a/.github/scripts/acf-local-json-smoke.php b/.github/scripts/acf-local-json-smoke.php index de0c76e..bcf3c19 100644 --- a/.github/scripts/acf-local-json-smoke.php +++ b/.github/scripts/acf-local-json-smoke.php @@ -105,9 +105,9 @@ function emulsify_acf_json_smoke_remove( string $path ): void { $GLOBALS['emulsify_acf_json_smoke_child'] = $child; try { - require_once $repo_root . '/includes/class-acf-local-json.php'; + require_once $repo_root . '/includes/Acf/LocalJson.php'; - $inactive = new Emulsify\Theme\Acf_Local_JSON(); + $inactive = new Emulsify\Theme\Acf\LocalJson(); $inactive->register(); emulsify_acf_json_smoke_assert( @@ -122,7 +122,7 @@ function acf(): bool { } } - $service = new Emulsify\Theme\Acf_Local_JSON(); + $service = new Emulsify\Theme\Acf\LocalJson(); $service->register(); emulsify_acf_json_smoke_assert( diff --git a/.github/scripts/attribute-helper-smoke.php b/.github/scripts/attribute-helper-smoke.php index 9cc50f6..b364a69 100644 --- a/.github/scripts/attribute-helper-smoke.php +++ b/.github/scripts/attribute-helper-smoke.php @@ -5,11 +5,11 @@ * @package Emulsify */ -require_once __DIR__ . '/../../includes/class-attribute-bag.php'; -require_once __DIR__ . '/../../includes/class-twig.php'; +require_once __DIR__ . '/../../includes/Support/AttributeBag.php'; +require_once __DIR__ . '/../../includes/Runtime/Twig.php'; -use Emulsify\Theme\AttributeBag; -use Emulsify\Theme\Twig; +use Emulsify\Theme\Support\AttributeBag; +use Emulsify\Theme\Runtime\Twig; /** * Asserts two values are identical. diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php index c5055e9..88e709e 100644 --- a/.github/scripts/bootstrap-loader-smoke.php +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -50,34 +50,55 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { $autoload = $repo_root . '/vendor/autoload.php'; $classes = array( 'Emulsify\\Theme\\Bootstrap', + 'Emulsify\\Theme\\Runtime\\Setup', + 'Emulsify\\Theme\\Runtime\\Assets', + 'Emulsify\\Theme\\Runtime\\Context', + 'Emulsify\\Theme\\Runtime\\Twig', + 'Emulsify\\Theme\\Runtime\\TimberIntegration', + 'Emulsify\\Theme\\Runtime\\MissingTimber', + 'Emulsify\\Theme\\Blocks\\Registry', + 'Emulsify\\Theme\\Blocks\\ComponentLocator', + 'Emulsify\\Theme\\Blocks\\AcfBlocks', + 'Emulsify\\Theme\\Blocks\\NativeBlocks', + 'Emulsify\\Theme\\Blocks\\CoreBlockTwigRenderer', + 'Emulsify\\Theme\\Blocks\\Patterns', + 'Emulsify\\Theme\\Editor\\Enhancements', + 'Emulsify\\Theme\\Editor\\Policy', + 'Emulsify\\Theme\\Acf\\LocalJson', + 'Emulsify\\Theme\\Cli\\GenerateChildThemeCommand', + 'Emulsify\\Theme\\Support\\AttributeBag', +); +$legacy_classes = array( 'Emulsify\\Theme\\Setup', 'Emulsify\\Theme\\Assets', - 'Emulsify\\Theme\\Acf_Local_JSON', - 'Emulsify\\Theme\\AttributeBag', - 'Emulsify\\Theme\\Cli', 'Emulsify\\Theme\\Context', - 'Emulsify\\Theme\\Core_Block_Twig_Renderer', - 'Emulsify\\Theme\\Editor_Enhancements', - 'Emulsify\\Theme\\Editor_Policy', - 'Emulsify\\Theme\\Missing_Timber', - 'Emulsify\\Theme\\Patterns', - 'Emulsify\\Theme\\Timber_Integration', 'Emulsify\\Theme\\Twig', + 'Emulsify\\Theme\\Timber_Integration', + 'Emulsify\\Theme\\Missing_Timber', + 'Emulsify\\Theme\\Blocks\\Registry', 'Emulsify\\Theme\\Blocks\\Component_Locator', 'Emulsify\\Theme\\Blocks\\Acf_Blocks', 'Emulsify\\Theme\\Blocks\\Native_Blocks', - 'Emulsify\\Theme\\Blocks\\Registry', + 'Emulsify\\Theme\\Core_Block_Twig_Renderer', + 'Emulsify\\Theme\\Patterns', + 'Emulsify\\Theme\\Editor_Enhancements', + 'Emulsify\\Theme\\Editor_Policy', + 'Emulsify\\Theme\\Acf_Local_JSON', + 'Emulsify\\Theme\\Cli', + 'Emulsify\\Theme\\AttributeBag', ); emulsify_bootstrap_loader_assert( is_readable( $autoload ), 'Run composer install or composer dump-autoload before the Bootstrap loader smoke test.' ); $classes_export = var_export( $classes, true ); +$legacy_export = var_export( $legacy_classes, true ); $repo_export = var_export( $repo_root, true ); emulsify_bootstrap_loader_run_php( <<newInstanceWithoutConstructor(); @@ -108,6 +136,12 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { throw new RuntimeException( sprintf( 'Fallback loader did not load %s.', \$class ) ); } } + +foreach ( \$legacy_classes as \$class ) { + if ( ! class_exists( \$class, true ) ) { + throw new RuntimeException( sprintf( 'Compatibility alias did not load %s.', \$class ) ); + } +} PHP ); diff --git a/.github/scripts/child-theme-generator-smoke.php b/.github/scripts/child-theme-generator-smoke.php index 3d40d31..ff4b18f 100644 --- a/.github/scripts/child-theme-generator-smoke.php +++ b/.github/scripts/child-theme-generator-smoke.php @@ -202,9 +202,9 @@ function emulsify_cli_smoke_json( string $path ): array { ) . "\n" ); - require_once $repo_root . '/includes/class-cli.php'; + require_once $repo_root . '/includes/Cli/GenerateChildThemeCommand.php'; - $cli = new Emulsify\Theme\Cli(); + $cli = new Emulsify\Theme\Cli\GenerateChildThemeCommand(); $cli( array( 'Acme Theme' ), array( 'machine-name' => 'acme-child' ) ); diff --git a/.github/scripts/component-locator-smoke.php b/.github/scripts/component-locator-smoke.php index 465839f..801d4b1 100644 --- a/.github/scripts/component-locator-smoke.php +++ b/.github/scripts/component-locator-smoke.php @@ -186,9 +186,9 @@ static function ( array $args ): string { emulsify_locator_smoke_write( $parent . '/dist/components/native/block.json', '{"name":"emulsify/native-parent"}' ); emulsify_locator_smoke_write( $parent . '/dist/components/parent-native/block.json', '{"name":"emulsify/parent-native"}' ); - require_once $repo_root . '/includes/Blocks/class-component-locator.php'; + require_once $repo_root . '/includes/Blocks/ComponentLocator.php'; - $locator = new Emulsify\Theme\Blocks\Component_Locator(); + $locator = new Emulsify\Theme\Blocks\ComponentLocator(); $acf = $locator->acf_components(); emulsify_locator_smoke_assert( @@ -244,7 +244,7 @@ static function ( array $args ): string { 'ACF/Twig discovery should return the per-request memoized result on repeated calls.' ); - $fresh_locator = new Emulsify\Theme\Blocks\Component_Locator(); + $fresh_locator = new Emulsify\Theme\Blocks\ComponentLocator(); emulsify_locator_smoke_assert( in_array( 'late-card', emulsify_locator_smoke_relatives( $fresh_locator->acf_components() ), true ), @@ -260,9 +260,9 @@ static function ( array $args ): string { emulsify_locator_smoke_write( $child . '/dist/components/acf-name-b/acf-name-b.component.json', '{"name":"emulsify/shared-acf","title":"Shared B"}' ); emulsify_locator_smoke_write( $child . '/dist/components/acf-name-b/acf-name-b.twig', '
    Shared B
    ' ); - require_once $repo_root . '/includes/Blocks/class-acf-blocks.php'; + require_once $repo_root . '/includes/Blocks/AcfBlocks.php'; - $acf_blocks = new Emulsify\Theme\Blocks\Acf_Blocks( new Emulsify\Theme\Blocks\Component_Locator() ); + $acf_blocks = new Emulsify\Theme\Blocks\AcfBlocks( new Emulsify\Theme\Blocks\ComponentLocator() ); $acf_blocks->register_blocks(); $registered_names = emulsify_locator_smoke_acf_registered_names(); diff --git a/.github/scripts/core-block-twig-renderer-smoke.php b/.github/scripts/core-block-twig-renderer-smoke.php index b364555..94cc2b5 100644 --- a/.github/scripts/core-block-twig-renderer-smoke.php +++ b/.github/scripts/core-block-twig-renderer-smoke.php @@ -300,7 +300,7 @@ function emulsify_core_block_twig_smoke_remove( string $path ): void { $GLOBALS['emulsify_core_block_twig_smoke_parent'] = $parent; try { - require_once $repo_root . '/includes/class-core-block-twig-renderer.php'; + require_once $repo_root . '/includes/Blocks/CoreBlockTwigRenderer.php'; emulsify_core_block_twig_smoke_write( $child . '/dist/components/paragraph/paragraph.twig', 'paragraph twig' ); emulsify_core_block_twig_smoke_write( $child . '/dist/components/error/throws.twig', 'throws twig' ); @@ -315,7 +315,7 @@ function emulsify_core_block_twig_smoke_remove( string $path ): void { 'innerBlocks' => array(), ); - $disabled = new \Emulsify\Theme\Core_Block_Twig_Renderer(); + $disabled = new \Emulsify\Theme\Blocks\CoreBlockTwigRenderer(); $disabled->register(); emulsify_core_block_twig_smoke_assert( @@ -351,7 +351,7 @@ static function ( array $context ): array { 'site' => 'Smoke Site', ); - $enabled = new \Emulsify\Theme\Core_Block_Twig_Renderer(); + $enabled = new \Emulsify\Theme\Blocks\CoreBlockTwigRenderer(); $enabled->register(); emulsify_core_block_twig_smoke_assert( diff --git a/.github/scripts/editor-enhancements-smoke.php b/.github/scripts/editor-enhancements-smoke.php index 7a45505..60e043c 100644 --- a/.github/scripts/editor-enhancements-smoke.php +++ b/.github/scripts/editor-enhancements-smoke.php @@ -218,9 +218,9 @@ function emulsify_editor_enhancements_smoke_remove( string $path ): void { $GLOBALS['emulsify_editor_enhancements_smoke_parent'] = $parent; try { - require_once $repo_root . '/includes/class-editor-enhancements.php'; + require_once $repo_root . '/includes/Editor/Enhancements.php'; - $service = new Emulsify\Theme\Editor_Enhancements(); + $service = new Emulsify\Theme\Editor\Enhancements(); $service->register(); foreach ( array( 'enqueue_block_editor_assets', 'render_block_data', 'render_block' ) as $hook ) { diff --git a/.github/scripts/editor-policy-smoke.php b/.github/scripts/editor-policy-smoke.php index 1e76298..f464a62 100644 --- a/.github/scripts/editor-policy-smoke.php +++ b/.github/scripts/editor-policy-smoke.php @@ -147,9 +147,9 @@ function emulsify_editor_policy_smoke_remove( string $path ): void { $GLOBALS['emulsify_editor_policy_smoke_parent'] = $parent; try { - require_once $repo_root . '/includes/class-editor-policy.php'; + require_once $repo_root . '/includes/Editor/Policy.php'; - $policy = new Emulsify\Theme\Editor_Policy(); + $policy = new Emulsify\Theme\Editor\Policy(); $policy->register(); foreach ( array( 'allowed_block_types_all', 'block_editor_settings_all', 'register_post_type_args', 'block_type_metadata_settings', 'register_block_type_args' ) as $hook ) { diff --git a/.github/scripts/pattern-registry-smoke.php b/.github/scripts/pattern-registry-smoke.php index 285e99a..5349d98 100644 --- a/.github/scripts/pattern-registry-smoke.php +++ b/.github/scripts/pattern-registry-smoke.php @@ -179,9 +179,9 @@ function emulsify_pattern_smoke_remove( string $path ): void { $GLOBALS['emulsify_pattern_smoke_parent'] = $parent; try { - require_once $repo_root . '/includes/class-patterns.php'; + require_once $repo_root . '/includes/Blocks/Patterns.php'; - $patterns = new Emulsify\Theme\Patterns(); + $patterns = new Emulsify\Theme\Blocks\Patterns(); $patterns->register(); emulsify_pattern_smoke_assert( diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 6a65da2..a504cfe 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -320,13 +320,25 @@ function runStaticChecks() { 'docs/wp-cli-child-theme-generation.md', 'composer.json', 'functions.php', - 'includes/class-acf-local-json.php', - 'includes/class-attribute-bag.php', - 'includes/class-cli.php', - 'includes/class-editor-enhancements.php', - 'includes/class-editor-policy.php', - 'includes/class-patterns.php', - 'includes/class-twig.php', + 'includes/Bootstrap.php', + 'includes/Compatibility.php', + 'includes/Acf/LocalJson.php', + 'includes/Blocks/AcfBlocks.php', + 'includes/Blocks/ComponentLocator.php', + 'includes/Blocks/CoreBlockTwigRenderer.php', + 'includes/Blocks/NativeBlocks.php', + 'includes/Blocks/Patterns.php', + 'includes/Blocks/Registry.php', + 'includes/Cli/GenerateChildThemeCommand.php', + 'includes/Editor/Enhancements.php', + 'includes/Editor/Policy.php', + 'includes/Runtime/Assets.php', + 'includes/Runtime/Context.php', + 'includes/Runtime/MissingTimber.php', + 'includes/Runtime/Setup.php', + 'includes/Runtime/TimberIntegration.php', + 'includes/Runtime/Twig.php', + 'includes/Support/AttributeBag.php', 'package.json', 'release.config.js', 'style.css', @@ -403,7 +415,8 @@ function runStaticChecks() { ensure(!Object.hasOwn(composer, 'prefer-stable'), 'composer.json should not keep prefer-stable when stable-only constraints are sufficient.'); ensure(composer.require && composer.require['timber/timber'] === '^2.3', 'composer.json should keep the Timber 2 dependency constraint.'); ensure(composer.autoload && composer.autoload['psr-4'] && composer.autoload['psr-4']['Emulsify\\Theme\\'] === 'includes/', 'composer.json should expose the runtime namespace through PSR-4 autoloading.'); - ensure(Array.isArray(composer.autoload.classmap) && composer.autoload.classmap.includes('includes/'), 'composer.json should classmap current runtime files until all filenames are PSR-4-shaped.'); + ensure(!Object.hasOwn(composer.autoload, 'classmap'), 'composer.json should rely on PSR-4 runtime paths instead of classmap loading.'); + ensure(Array.isArray(composer.autoload.files) && composer.autoload.files.includes('includes/Compatibility.php'), 'composer.json should load runtime compatibility aliases for Composer installs.'); ensureParentThemeLanguage('composer.json description', composer.description); return `Validated root package ${rootPackage.version} and composer metadata.`; }); @@ -431,16 +444,16 @@ function runStaticChecks() { }); runStaticCheck('Timber attribute helpers', () => { - const bootstrap = readFile('includes/class-bootstrap.php'); - const twig = readFile('includes/class-twig.php'); - const attributeBag = readFile('includes/class-attribute-bag.php'); + const bootstrap = readFile('includes/Bootstrap.php'); + const twig = readFile('includes/Runtime/Twig.php'); + const attributeBag = readFile('includes/Support/AttributeBag.php'); const smoke = readFile('.github/scripts/attribute-helper-smoke.php'); ensure(bootstrap.includes('load_vendor_autoload();') && bootstrap.includes('load_classes();'), 'Bootstrap should load Composer before registering fallback runtime loading.'); ensure(bootstrap.indexOf('load_vendor_autoload();') < bootstrap.indexOf('load_classes();'), 'Bootstrap should try Composer autoloading before fallback runtime loading.'); ensure(bootstrap.includes('spl_autoload_register'), 'Bootstrap should register a fallback runtime autoloader.'); ensure(bootstrap.includes('runtime_class_file'), 'Bootstrap should resolve runtime classes through a fallback file mapper.'); - ensure(bootstrap.includes('class_file_slug'), 'Bootstrap should support legacy class-* runtime filenames.'); + ensure(bootstrap.includes('Compatibility.php'), 'Bootstrap should load compatibility aliases after registering fallback runtime loading.'); ensure(attributeBag.includes('implements \\Stringable'), 'AttributeBag should serialize safely in Twig string contexts.'); ensure(attributeBag.includes('function addClass'), 'AttributeBag should support Core-style class merging.'); ensure(attributeBag.includes('function toString'), 'AttributeBag should expose explicit serialization.'); @@ -452,20 +465,25 @@ function runStaticChecks() { }); runStaticCheck('Bootstrap runtime autoloading', () => { - const bootstrap = readFile('includes/class-bootstrap.php'); + const bootstrap = readFile('includes/Bootstrap.php'); + const compatibility = readFile('includes/Compatibility.php'); const smoke = readFile('.github/scripts/bootstrap-loader-smoke.php'); - ensure(bootstrap.includes('$psr4_file'), 'Bootstrap fallback loader should check PSR-4-shaped runtime paths.'); - ensure(bootstrap.includes("'class-' . $this->class_file_slug"), 'Bootstrap fallback loader should preserve legacy class-* filename support.'); + ensure(bootstrap.includes("str_replace( '\\\\', '/', $relative_class )"), 'Bootstrap fallback loader should resolve PSR-4-shaped runtime paths.'); + ensure(compatibility.includes('class_alias'), 'Compatibility should preserve old runtime class names with aliases.'); + ensure(compatibility.includes('Runtime\\Twig::class') && compatibility.includes("'\\\\Twig'"), 'Compatibility should alias old root Twig class names.'); + ensure(compatibility.includes('Blocks\\ComponentLocator::class') && compatibility.includes("'\\\\Blocks\\\\Component_Locator'"), 'Compatibility should alias old block service class names.'); ensure(smoke.includes("require_once \\$repo_root . '/vendor/autoload.php'"), 'Bootstrap loader smoke should verify Composer autoloading.'); - ensure(smoke.includes("require_once \\$repo_root . '/includes/class-bootstrap.php'"), 'Bootstrap loader smoke should verify fallback loading without Composer.'); + ensure(smoke.includes('Composer compatibility alias did not load'), 'Bootstrap loader smoke should verify Composer-loaded compatibility aliases.'); + ensure(smoke.includes("require_once \\$repo_root . '/includes/Bootstrap.php'"), 'Bootstrap loader smoke should verify fallback loading without Composer.'); ensure(smoke.includes('Emulsify\\\\Theme\\\\Blocks\\\\Registry'), 'Bootstrap loader smoke should cover nested block runtime classes.'); + ensure(smoke.includes('Emulsify\\\\Theme\\\\Blocks\\\\Component_Locator'), 'Bootstrap loader smoke should cover legacy compatibility aliases.'); ensure(smoke.includes('Fallback loader did not load'), 'Bootstrap loader smoke should fail clearly when fallback loading breaks.'); return 'Composer autoloading and Bootstrap fallback loading are covered.'; }); runStaticCheck('Child theme generator', () => { - const cli = readFile('includes/class-cli.php'); + const cli = readFile('includes/Cli/GenerateChildThemeCommand.php'); const smoke = readFile('.github/scripts/child-theme-generator-smoke.php'); ensure(cli.includes('[--machine-name=]'), 'WP-CLI help should document --machine-name.'); @@ -510,10 +528,10 @@ function runStaticChecks() { }); runStaticCheck('Component locator memoization', () => { - const locator = readFile('includes/Blocks/class-component-locator.php'); - const registry = readFile('includes/Blocks/class-registry.php'); - const acfBlocks = readFile('includes/Blocks/class-acf-blocks.php'); - const nativeBlocks = readFile('includes/Blocks/class-native-blocks.php'); + const locator = readFile('includes/Blocks/ComponentLocator.php'); + const registry = readFile('includes/Blocks/Registry.php'); + const acfBlocks = readFile('includes/Blocks/AcfBlocks.php'); + const nativeBlocks = readFile('includes/Blocks/NativeBlocks.php'); const smoke = readFile('.github/scripts/component-locator-smoke.php'); ensure(locator.includes('private $component_roots'), 'Component locator should memoize component roots per request.'); @@ -528,11 +546,11 @@ function runStaticChecks() { ensure(locator.includes('get_stylesheet_directory()') && locator.includes('get_template_directory()'), 'Component locator should keep child and parent component roots.'); ensure(locator.indexOf('get_stylesheet_directory()') < locator.indexOf('get_template_directory()'), 'Component locator should keep child roots before parent roots.'); ensure(!/wp_cache_|transient/i.test(locator), 'Component locator should not use persistent caching without invalidation.'); - ensure(registry.includes('$components = new Component_Locator()'), 'Block registry should share one Component_Locator instance.'); - ensure(acfBlocks.includes('$this->components->acf_components()'), 'ACF/Twig block discovery should use Component_Locator.'); + ensure(registry.includes('$components = new ComponentLocator()'), 'Block registry should share one ComponentLocator instance.'); + ensure(acfBlocks.includes('$this->components->acf_components()'), 'ACF/Twig block discovery should use ComponentLocator.'); ensure(acfBlocks.includes('acf_block_name'), 'ACF/Twig block registration should skip duplicate final ACF block names.'); ensure(acfBlocks.includes('acf_get_block_type'), 'ACF/Twig block registration should avoid already registered ACF block names.'); - ensure(nativeBlocks.includes('$this->components->native_block_directories()'), 'Native block discovery should use Component_Locator.'); + ensure(nativeBlocks.includes('$this->components->native_block_directories()'), 'Native block discovery should use ComponentLocator.'); ensure(nativeBlocks.includes('native_registered_block_name'), 'Native block registration should avoid already registered native block names.'); ensure(smoke.includes('late-native'), 'Component locator smoke should prove native discovery reuses the memoized file index.'); ensure(smoke.includes('late-card'), 'Component locator smoke should prove repeated ACF/Twig discovery is memoized per locator instance.'); @@ -545,17 +563,17 @@ function runStaticChecks() { }); runStaticCheck('Runtime filters', () => { - const acfJson = readFile('includes/class-acf-local-json.php'); - const assets = readFile('includes/class-assets.php'); - const twig = readFile('includes/class-twig.php'); - const context = readFile('includes/class-context.php'); - const setup = readFile('includes/class-setup.php'); - const editorEnhancements = readFile('includes/class-editor-enhancements.php'); - const editorPolicy = readFile('includes/class-editor-policy.php'); - const patterns = readFile('includes/class-patterns.php'); - const locator = readFile('includes/Blocks/class-component-locator.php'); - const acfBlocks = readFile('includes/Blocks/class-acf-blocks.php'); - const nativeBlocks = readFile('includes/Blocks/class-native-blocks.php'); + const acfJson = readFile('includes/Acf/LocalJson.php'); + const assets = readFile('includes/Runtime/Assets.php'); + const twig = readFile('includes/Runtime/Twig.php'); + const context = readFile('includes/Runtime/Context.php'); + const setup = readFile('includes/Runtime/Setup.php'); + const editorEnhancements = readFile('includes/Editor/Enhancements.php'); + const editorPolicy = readFile('includes/Editor/Policy.php'); + const patterns = readFile('includes/Blocks/Patterns.php'); + const locator = readFile('includes/Blocks/ComponentLocator.php'); + const acfBlocks = readFile('includes/Blocks/AcfBlocks.php'); + const nativeBlocks = readFile('includes/Blocks/NativeBlocks.php'); const acfJsonSmoke = readFile('.github/scripts/acf-local-json-smoke.php'); const editorEnhancementsSmoke = readFile('.github/scripts/editor-enhancements-smoke.php'); const smoke = readFile('.github/scripts/theme-filters-smoke.php'); @@ -682,7 +700,7 @@ function runStaticChecks() { }); runStaticCheck('Template fallback model', () => { - const twigIntegration = readFile('includes/class-twig.php'); + const twigIntegration = readFile('includes/Runtime/Twig.php'); const twigNamespaceSmoke = readFile('.github/scripts/twig-project-namespace-smoke.php'); const childFunctions = readFile('whisk/functions.php'); const childPageTemplate = readFile('whisk/templates/page.twig'); diff --git a/.github/scripts/theme-filters-smoke.php b/.github/scripts/theme-filters-smoke.php index 91f24d2..b22e92d 100644 --- a/.github/scripts/theme-filters-smoke.php +++ b/.github/scripts/theme-filters-smoke.php @@ -304,14 +304,14 @@ function emulsify_filter_smoke_remove( string $path ): void { emulsify_filter_smoke_write( $extra . '/components/filter-card/filter-card.twig', '
    Filter card
    ' ); emulsify_filter_smoke_write( $extra . '/native/block.json', '{"name":"emulsify/filter-native"}' ); - require_once $repo_root . '/includes/class-attribute-bag.php'; - require_once $repo_root . '/includes/class-assets.php'; - require_once $repo_root . '/includes/class-context.php'; - require_once $repo_root . '/includes/class-setup.php'; - require_once $repo_root . '/includes/class-twig.php'; - require_once $repo_root . '/includes/Blocks/class-component-locator.php'; - require_once $repo_root . '/includes/Blocks/class-acf-blocks.php'; - require_once $repo_root . '/includes/Blocks/class-native-blocks.php'; + require_once $repo_root . '/includes/Support/AttributeBag.php'; + require_once $repo_root . '/includes/Runtime/Assets.php'; + require_once $repo_root . '/includes/Runtime/Context.php'; + require_once $repo_root . '/includes/Runtime/Setup.php'; + require_once $repo_root . '/includes/Runtime/Twig.php'; + require_once $repo_root . '/includes/Blocks/ComponentLocator.php'; + require_once $repo_root . '/includes/Blocks/AcfBlocks.php'; + require_once $repo_root . '/includes/Blocks/NativeBlocks.php'; add_filter( 'emulsify_theme_asset_directories', @@ -431,7 +431,7 @@ static function ( array $directories ) use ( $extra ): array { } ); - ( new Emulsify\Theme\Assets() )->styles(); + ( new Emulsify\Theme\Runtime\Assets() )->styles(); emulsify_filter_smoke_assert( isset( $GLOBALS['emulsify_filter_smoke_styles']['emulsify-global-extra'] ), @@ -461,21 +461,21 @@ public function addPath( string $path, string $namespace ): void { } }; - ( new Emulsify\Theme\Twig() )->loader_paths( $loader ); + ( new Emulsify\Theme\Runtime\Twig() )->loader_paths( $loader ); emulsify_filter_smoke_assert( in_array( array( 'path' => $extra . '/twig', 'namespace' => 'project' ), $loader->paths, true ), 'Twig namespace filter should add a project namespace.' ); - $context = ( new Emulsify\Theme\Context() )->add( array() ); + $context = ( new Emulsify\Theme\Runtime\Context() )->add( array() ); emulsify_filter_smoke_assert( true === $context['project_flag'], 'Context filter should add project context values.' ); - ( new Emulsify\Theme\Setup() )->theme_supports(); + ( new Emulsify\Theme\Runtime\Setup() )->theme_supports(); emulsify_filter_smoke_assert( array_key_exists( 'custom-spacing', $GLOBALS['emulsify_filter_smoke_theme_support'] ), @@ -486,14 +486,14 @@ public function addPath( string $path, string $namespace ): void { 'Setup options filter should add custom image sizes.' ); - $locator = new Emulsify\Theme\Blocks\Component_Locator(); + $locator = new Emulsify\Theme\Blocks\ComponentLocator(); emulsify_filter_smoke_assert( in_array( 'filter-card', array_column( $locator->acf_components(), 'relative' ), true ), 'Component roots filter should add an extra component discovery root.' ); - ( new Emulsify\Theme\Blocks\Acf_Blocks( new Emulsify\Theme\Blocks\Component_Locator() ) )->register_blocks(); + ( new Emulsify\Theme\Blocks\AcfBlocks( new Emulsify\Theme\Blocks\ComponentLocator() ) )->register_blocks(); emulsify_filter_smoke_assert( 'emulsify-filtered-card' === $GLOBALS['emulsify_filter_smoke_acf_blocks'][0]['name'], @@ -504,7 +504,7 @@ public function addPath( string $path, string $namespace ): void { 'ACF block metadata filter should alter metadata before defaults are merged.' ); - ( new Emulsify\Theme\Blocks\Native_Blocks( new Emulsify\Theme\Blocks\Component_Locator() ) )->register_blocks(); + ( new Emulsify\Theme\Blocks\NativeBlocks( new Emulsify\Theme\Blocks\ComponentLocator() ) )->register_blocks(); emulsify_filter_smoke_assert( in_array( $extra . '/native', $GLOBALS['emulsify_filter_smoke_native_blocks'], true ), diff --git a/.github/scripts/twig-project-namespace-smoke.php b/.github/scripts/twig-project-namespace-smoke.php index f39f46b..b636032 100644 --- a/.github/scripts/twig-project-namespace-smoke.php +++ b/.github/scripts/twig-project-namespace-smoke.php @@ -75,8 +75,8 @@ function get_template_directory(): string { } } -require_once $repo_root . '/includes/class-attribute-bag.php'; -require_once $repo_root . '/includes/class-twig.php'; +require_once $repo_root . '/includes/Support/AttributeBag.php'; +require_once $repo_root . '/includes/Runtime/Twig.php'; /** * Fails the smoke script when an assertion is false. @@ -171,7 +171,7 @@ function emulsify_twig_project_smoke_environment( string $child, string $parent $GLOBALS['emulsify_twig_project_smoke_parent'] = $parent; $loader = new \Twig\Loader\FilesystemLoader(); - $loader = ( new \Emulsify\Theme\Twig() )->loader_paths( $loader ); + $loader = ( new \Emulsify\Theme\Runtime\Twig() )->loader_paths( $loader ); return new \Twig\Environment( $loader, diff --git a/.github/scripts/wordpress-fixture-smoke.cjs b/.github/scripts/wordpress-fixture-smoke.cjs index e40ea5d..cee8b0e 100755 --- a/.github/scripts/wordpress-fixture-smoke.cjs +++ b/.github/scripts/wordpress-fixture-smoke.cjs @@ -387,7 +387,7 @@ if ( function_exists( 'acf_register_block_type' ) ) { emulsify_smoke_fail( 'ACF block API should not be present before the fixture stub is installed.' ); } -$locator = new \Emulsify\Theme\Blocks\Component_Locator(); +$locator = new \Emulsify\Theme\Blocks\ComponentLocator(); $components = $locator->acf_components(); $fixture = null; @@ -406,7 +406,7 @@ if ( 'dist/components/smoke-acf/smoke-acf.twig' !== $fixture['template'] ) { emulsify_smoke_fail( 'Generated child ACF/Twig fixture template was not resolved.' ); } -( new \Emulsify\Theme\Blocks\Acf_Blocks( $locator ) )->register_blocks(); +( new \Emulsify\Theme\Blocks\AcfBlocks( $locator ) )->register_blocks(); `; wp(wpPath, ['eval', code]); @@ -466,7 +466,7 @@ if ( ! $registry->is_registered( 'emulsify/smoke-native' ) ) { emulsify_smoke_fail( 'Generated child native block.json fixture was not registered.' ); } -$locator = new \Emulsify\Theme\Blocks\Component_Locator(); +$locator = new \Emulsify\Theme\Blocks\ComponentLocator(); $native = $locator->native_block_directories(); $found = false; diff --git a/composer.json b/composer.json index 642be51..b81f508 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ "psr-4": { "Emulsify\\Theme\\": "includes/" }, - "classmap": [ - "includes/" + "files": [ + "includes/Compatibility.php" ] }, "config": { diff --git a/docs/core-block-twig-rendering.md b/docs/core-block-twig-rendering.md index de9db9a..afe09eb 100644 --- a/docs/core-block-twig-rendering.md +++ b/docs/core-block-twig-rendering.md @@ -1,6 +1,6 @@ # Core block Twig rendering -The parent theme includes an experimental `Core_Block_Twig_Renderer` service that can render explicitly mapped WordPress blocks through Twig templates. It is disabled by default and does not change frontend output unless a child theme or project plugin opts in. +The parent theme includes an experimental `CoreBlockTwigRenderer` service that can render explicitly mapped WordPress blocks through Twig templates. It is disabled by default and does not change frontend output unless a child theme or project plugin opts in. Use this carefully. Replacing core block output can affect block validation, style supports, accessibility attributes, plugin integrations, and future WordPress markup changes. Keep Twig output compatible with the saved block attributes and test editing, saving, reloading, and frontend rendering for every mapped block. diff --git a/docs/editor-policy.md b/docs/editor-policy.md index 184ef68..5a16409 100644 --- a/docs/editor-policy.md +++ b/docs/editor-policy.md @@ -1,6 +1,6 @@ # Editor policy -The parent theme includes an `Editor_Policy` service for project-specific block editor governance. It is intentionally no-op by default, so activating the parent theme does not change allowed blocks, visible patterns, user-created pattern behavior, `wp_block` capabilities, or registered block supports unless a child theme or project plugin configures the policy with filters. +The parent theme includes an `Editor\Policy` service for project-specific block editor governance. It is intentionally no-op by default, so activating the parent theme does not change allowed blocks, visible patterns, user-created pattern behavior, `wp_block` capabilities, or registered block supports unless a child theme or project plugin configures the policy with filters. ## Configure policy options diff --git a/functions.php b/functions.php index 76b3f33..3018d18 100644 --- a/functions.php +++ b/functions.php @@ -9,7 +9,7 @@ return; } -require_once __DIR__ . '/includes/class-bootstrap.php'; +require_once __DIR__ . '/includes/Bootstrap.php'; // Keep functions.php as the smallest possible WordPress entry point. Runtime // behavior belongs in Bootstrap-managed services under includes/. diff --git a/includes/class-acf-local-json.php b/includes/Acf/LocalJson.php similarity index 98% rename from includes/class-acf-local-json.php rename to includes/Acf/LocalJson.php index fc8f0a2..4e441bd 100644 --- a/includes/class-acf-local-json.php +++ b/includes/Acf/LocalJson.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Acf; /** * ACF Local JSON integration. */ -final class Acf_Local_JSON { +final class LocalJson { /** * Registers ACF Local JSON hooks. diff --git a/includes/Blocks/class-acf-blocks.php b/includes/Blocks/AcfBlocks.php similarity index 98% rename from includes/Blocks/class-acf-blocks.php rename to includes/Blocks/AcfBlocks.php index 1307885..57c76ab 100644 --- a/includes/Blocks/class-acf-blocks.php +++ b/includes/Blocks/AcfBlocks.php @@ -10,12 +10,12 @@ /** * ACF/Twig block integration. */ -final class Acf_Blocks { +final class AcfBlocks { /** * Component locator. * - * @var Component_Locator + * @var ComponentLocator */ private $components; @@ -29,10 +29,10 @@ final class Acf_Blocks { /** * Constructor. * - * @param Component_Locator|null $components Component locator. + * @param ComponentLocator|null $components Component locator. */ - public function __construct( ?Component_Locator $components = null ) { - $this->components = $components ?? new Component_Locator(); + public function __construct( ?ComponentLocator $components = null ) { + $this->components = $components ?? new ComponentLocator(); } /** diff --git a/includes/Blocks/class-component-locator.php b/includes/Blocks/ComponentLocator.php similarity index 99% rename from includes/Blocks/class-component-locator.php rename to includes/Blocks/ComponentLocator.php index 7f792dc..671048a 100644 --- a/includes/Blocks/class-component-locator.php +++ b/includes/Blocks/ComponentLocator.php @@ -15,7 +15,7 @@ * registration, so both paths reuse the same deterministic child-first file * index without introducing persistent cache invalidation concerns. */ -final class Component_Locator { +final class ComponentLocator { /** * Theme-relative component build directory. diff --git a/includes/class-core-block-twig-renderer.php b/includes/Blocks/CoreBlockTwigRenderer.php similarity index 99% rename from includes/class-core-block-twig-renderer.php rename to includes/Blocks/CoreBlockTwigRenderer.php index 23bf9ee..c852f0b 100644 --- a/includes/class-core-block-twig-renderer.php +++ b/includes/Blocks/CoreBlockTwigRenderer.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Blocks; /** * Opt-in core block to Twig rendering bridge. */ -final class Core_Block_Twig_Renderer { +final class CoreBlockTwigRenderer { /** * Registers block rendering hooks when explicitly enabled. diff --git a/includes/Blocks/class-native-blocks.php b/includes/Blocks/NativeBlocks.php similarity index 94% rename from includes/Blocks/class-native-blocks.php rename to includes/Blocks/NativeBlocks.php index 83bf7af..5e5e63a 100644 --- a/includes/Blocks/class-native-blocks.php +++ b/includes/Blocks/NativeBlocks.php @@ -10,22 +10,22 @@ /** * Native WordPress Block API integration. */ -final class Native_Blocks { +final class NativeBlocks { /** * Component locator. * - * @var Component_Locator + * @var ComponentLocator */ private $components; /** * Constructor. * - * @param Component_Locator|null $components Component locator. + * @param ComponentLocator|null $components Component locator. */ - public function __construct( ?Component_Locator $components = null ) { - $this->components = $components ?? new Component_Locator(); + public function __construct( ?ComponentLocator $components = null ) { + $this->components = $components ?? new ComponentLocator(); } /** @@ -59,7 +59,7 @@ public function register_blocks(): void { * this filter runs. * * @param array $directories Native block directory records. - * @param Component_Locator $components Component locator instance. + * @param ComponentLocator $components Component locator instance. */ $filtered = apply_filters( 'emulsify_theme_native_block_directories', $directories, $this->components ); diff --git a/includes/class-patterns.php b/includes/Blocks/Patterns.php similarity index 99% rename from includes/class-patterns.php rename to includes/Blocks/Patterns.php index 4c58211..6ea8aff 100644 --- a/includes/class-patterns.php +++ b/includes/Blocks/Patterns.php @@ -5,7 +5,7 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Blocks; /** * Discovers child-first JSON block patterns. diff --git a/includes/Blocks/class-registry.php b/includes/Blocks/Registry.php similarity index 75% rename from includes/Blocks/class-registry.php rename to includes/Blocks/Registry.php index a5a301b..f2821c1 100644 --- a/includes/Blocks/class-registry.php +++ b/includes/Blocks/Registry.php @@ -18,11 +18,11 @@ final class Registry { * @return void */ public function register(): void { - $components = new Component_Locator(); + $components = new ComponentLocator(); // Share one locator so ACF/Twig and native block registration use the same // request-local child-first component index and duplicate diagnostics. - ( new Acf_Blocks( $components ) )->register(); - ( new Native_Blocks( $components ) )->register(); + ( new AcfBlocks( $components ) )->register(); + ( new NativeBlocks( $components ) )->register(); } } diff --git a/includes/class-bootstrap.php b/includes/Bootstrap.php similarity index 69% rename from includes/class-bootstrap.php rename to includes/Bootstrap.php index b8a8d3a..c6bfd12 100644 --- a/includes/class-bootstrap.php +++ b/includes/Bootstrap.php @@ -7,6 +7,19 @@ namespace Emulsify\Theme; +use Emulsify\Theme\Acf\LocalJson; +use Emulsify\Theme\Blocks\CoreBlockTwigRenderer; +use Emulsify\Theme\Blocks\Patterns; +use Emulsify\Theme\Cli\GenerateChildThemeCommand; +use Emulsify\Theme\Editor\Enhancements; +use Emulsify\Theme\Editor\Policy; +use Emulsify\Theme\Runtime\Assets; +use Emulsify\Theme\Runtime\Context; +use Emulsify\Theme\Runtime\MissingTimber; +use Emulsify\Theme\Runtime\Setup; +use Emulsify\Theme\Runtime\TimberIntegration; +use Emulsify\Theme\Runtime\Twig; + /** * Coordinates theme services. */ @@ -49,19 +62,19 @@ private function register(): void { $this->load_classes(); // These services use WordPress APIs directly and must stay available even - // when Timber is missing. The Missing_Timber service owns frontend failure + // when Timber is missing. The MissingTimber service owns frontend failure // handling later in this method. ( new Setup() )->register(); ( new Assets() )->register(); - ( new Acf_Local_JSON() )->register(); - ( new Core_Block_Twig_Renderer() )->register(); - ( new Editor_Enhancements() )->register(); - ( new Editor_Policy() )->register(); + ( new LocalJson() )->register(); + ( new CoreBlockTwigRenderer() )->register(); + ( new Enhancements() )->register(); + ( new Policy() )->register(); ( new Patterns() )->register(); ( new Blocks\Registry() )->register(); - ( new Cli() )->register(); + ( new GenerateChildThemeCommand() )->register(); - $timber = new Timber_Integration(); + $timber = new TimberIntegration(); if ( $timber->register() ) { // Timber-dependent services are registered only after Timber has @@ -72,7 +85,7 @@ private function register(): void { return; } - ( new Missing_Timber() )->register(); + ( new MissingTimber() )->register(); } /** @@ -99,6 +112,12 @@ function ( string $class ): void { $this->autoload_runtime_class( $class ); } ); + + $compatibility = $this->theme_dir . '/includes/Compatibility.php'; + + if ( is_readable( $compatibility ) ) { + require_once $compatibility; + } } /** @@ -116,7 +135,7 @@ private function autoload_runtime_class( string $class ): void { } /** - * Resolves runtime class files for PSR-4 paths and legacy class-* filenames. + * Resolves runtime class files for PSR-4 paths. * * @param string $class Fully qualified class name. * @return string|null Runtime class file path, or null for another namespace. @@ -130,29 +149,7 @@ private function runtime_class_file( string $class ): ?string { $relative_class = substr( $class, strlen( $prefix ) ); $relative_path = str_replace( '\\', '/', $relative_class ); - $psr4_file = $this->theme_dir . '/includes/' . $relative_path . '.php'; - - if ( is_readable( $psr4_file ) ) { - return $psr4_file; - } - - $parts = explode( '/', $relative_path ); - $class_name = array_pop( $parts ); - $directory = empty( $parts ) ? '' : implode( '/', $parts ) . '/'; - - return $this->theme_dir . '/includes/' . $directory . 'class-' . $this->class_file_slug( $class_name ) . '.php'; - } - - /** - * Converts a runtime class name to the legacy class-* file slug. - * - * @param string $class_name Short class name. - * @return string File slug. - */ - private function class_file_slug( string $class_name ): string { - $slug = str_replace( '_', '-', $class_name ); - $slug = preg_replace( '/(?<=[a-z0-9])([A-Z])/', '-$1', $slug ); - return strtolower( (string) $slug ); + return $this->theme_dir . '/includes/' . $relative_path . '.php'; } } diff --git a/includes/class-cli.php b/includes/Cli/GenerateChildThemeCommand.php similarity index 99% rename from includes/class-cli.php rename to includes/Cli/GenerateChildThemeCommand.php index 2a87985..693ea57 100644 --- a/includes/class-cli.php +++ b/includes/Cli/GenerateChildThemeCommand.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Cli; /** * Provides the `wp emulsify` command. */ -final class Cli { +final class GenerateChildThemeCommand { /** * Bundled starter child theme directory. diff --git a/includes/Compatibility.php b/includes/Compatibility.php new file mode 100644 index 0000000..d832989 --- /dev/null +++ b/includes/Compatibility.php @@ -0,0 +1,33 @@ + __NAMESPACE__ . '\\Setup', + Runtime\Assets::class => __NAMESPACE__ . '\\Assets', + Runtime\Context::class => __NAMESPACE__ . '\\Context', + Runtime\Twig::class => __NAMESPACE__ . '\\Twig', + Runtime\TimberIntegration::class => __NAMESPACE__ . '\\Timber_Integration', + Runtime\MissingTimber::class => __NAMESPACE__ . '\\Missing_Timber', + Blocks\ComponentLocator::class => __NAMESPACE__ . '\\Blocks\\Component_Locator', + Blocks\AcfBlocks::class => __NAMESPACE__ . '\\Blocks\\Acf_Blocks', + Blocks\NativeBlocks::class => __NAMESPACE__ . '\\Blocks\\Native_Blocks', + Blocks\CoreBlockTwigRenderer::class => __NAMESPACE__ . '\\Core_Block_Twig_Renderer', + Blocks\Patterns::class => __NAMESPACE__ . '\\Patterns', + Editor\Enhancements::class => __NAMESPACE__ . '\\Editor_Enhancements', + Editor\Policy::class => __NAMESPACE__ . '\\Editor_Policy', + Acf\LocalJson::class => __NAMESPACE__ . '\\Acf_Local_JSON', + Cli\GenerateChildThemeCommand::class => __NAMESPACE__ . '\\Cli', + Support\AttributeBag::class => __NAMESPACE__ . '\\AttributeBag', +); + +foreach ( $aliases as $class => $alias ) { + if ( ! class_exists( $alias, false ) && class_exists( $class ) ) { + class_alias( $class, $alias ); + } +} diff --git a/includes/class-editor-enhancements.php b/includes/Editor/Enhancements.php similarity index 99% rename from includes/class-editor-enhancements.php rename to includes/Editor/Enhancements.php index e80e843..f5011a1 100644 --- a/includes/class-editor-enhancements.php +++ b/includes/Editor/Enhancements.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Editor; /** * Optional editor enhancement runtime. */ -final class Editor_Enhancements { +final class Enhancements { /** * Registers editor enhancement hooks. diff --git a/includes/class-editor-policy.php b/includes/Editor/Policy.php similarity index 99% rename from includes/class-editor-policy.php rename to includes/Editor/Policy.php index ba3bb75..e76b708 100644 --- a/includes/class-editor-policy.php +++ b/includes/Editor/Policy.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Editor; /** * Parent-theme block editor policy hooks. */ -final class Editor_Policy { +final class Policy { /** * Registers block editor policy hooks. diff --git a/includes/class-assets.php b/includes/Runtime/Assets.php similarity index 98% rename from includes/class-assets.php rename to includes/Runtime/Assets.php index 13da285..2d1e26c 100644 --- a/includes/class-assets.php +++ b/includes/Runtime/Assets.php @@ -5,7 +5,7 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Runtime; /** * Global asset registration. @@ -118,7 +118,7 @@ private function asset_files( string $directory, array $extensions ): array { $relative = $this->relative_path( $root['path'], $file->getPathname() ); if ( $this->is_reserved_editor_asset( $directory, $relative ) ) { - // Editor-only assets are handled by Editor_Enhancements so they + // Editor-only assets are handled by Editor\Enhancements so they // receive editor dependencies and configuration before enqueue. continue; } diff --git a/includes/class-context.php b/includes/Runtime/Context.php similarity index 99% rename from includes/class-context.php rename to includes/Runtime/Context.php index 3dc92b7..6acfdce 100644 --- a/includes/class-context.php +++ b/includes/Runtime/Context.php @@ -5,7 +5,7 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Runtime; /** * Timber context integration. diff --git a/includes/class-missing-timber.php b/includes/Runtime/MissingTimber.php similarity index 96% rename from includes/class-missing-timber.php rename to includes/Runtime/MissingTimber.php index 100facd..8cb615a 100644 --- a/includes/class-missing-timber.php +++ b/includes/Runtime/MissingTimber.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Runtime; /** * Missing Timber error handling. */ -final class Missing_Timber { +final class MissingTimber { /** * Registers admin and runtime notices. diff --git a/includes/class-setup.php b/includes/Runtime/Setup.php similarity index 98% rename from includes/class-setup.php rename to includes/Runtime/Setup.php index 9aa5afc..ccdc717 100644 --- a/includes/class-setup.php +++ b/includes/Runtime/Setup.php @@ -5,7 +5,7 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Runtime; /** * Theme setup hooks. diff --git a/includes/class-timber-integration.php b/includes/Runtime/TimberIntegration.php similarity index 90% rename from includes/class-timber-integration.php rename to includes/Runtime/TimberIntegration.php index d54a683..195495f 100644 --- a/includes/class-timber-integration.php +++ b/includes/Runtime/TimberIntegration.php @@ -5,12 +5,12 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Runtime; /** * Timber runtime guard. */ -final class Timber_Integration { +final class TimberIntegration { /** * Initializes Timber. diff --git a/includes/class-twig.php b/includes/Runtime/Twig.php similarity index 99% rename from includes/class-twig.php rename to includes/Runtime/Twig.php index 868a777..3817ab8 100644 --- a/includes/class-twig.php +++ b/includes/Runtime/Twig.php @@ -5,7 +5,9 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Runtime; + +use Emulsify\Theme\Support\AttributeBag; /** * Twig integration for Timber. diff --git a/includes/class-attribute-bag.php b/includes/Support/AttributeBag.php similarity index 99% rename from includes/class-attribute-bag.php rename to includes/Support/AttributeBag.php index 290d23a..56ec4c4 100644 --- a/includes/class-attribute-bag.php +++ b/includes/Support/AttributeBag.php @@ -5,7 +5,7 @@ * @package Emulsify */ -namespace Emulsify\Theme; +namespace Emulsify\Theme\Support; /** * Small AttributeBag implementation for Twig helper output. From 9d3a0e8bebad39302a480eefa3beaf38c044d6b5 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 13:04:47 -0500 Subject: [PATCH 40/55] refactor: consolidate theme file discovery --- .github/scripts/attribute-helper-smoke.php | 1 + .github/scripts/component-locator-smoke.php | 1 + .github/scripts/editor-enhancements-smoke.php | 1 + .github/scripts/pattern-registry-smoke.php | 1 + .github/scripts/release-check.cjs | 23 +- .github/scripts/theme-filters-smoke.php | 6 + .../scripts/twig-project-namespace-smoke.php | 1 + includes/Blocks/ComponentLocator.php | 99 +----- includes/Blocks/Patterns.php | 116 ++----- includes/Editor/Enhancements.php | 107 ++---- includes/Runtime/Assets.php | 123 ++----- includes/Runtime/Twig.php | 30 +- includes/Support/FileDiscovery.php | 313 ++++++++++++++++++ 13 files changed, 447 insertions(+), 375 deletions(-) create mode 100644 includes/Support/FileDiscovery.php diff --git a/.github/scripts/attribute-helper-smoke.php b/.github/scripts/attribute-helper-smoke.php index b364a69..b8a3170 100644 --- a/.github/scripts/attribute-helper-smoke.php +++ b/.github/scripts/attribute-helper-smoke.php @@ -6,6 +6,7 @@ */ require_once __DIR__ . '/../../includes/Support/AttributeBag.php'; +require_once __DIR__ . '/../../includes/Support/FileDiscovery.php'; require_once __DIR__ . '/../../includes/Runtime/Twig.php'; use Emulsify\Theme\Support\AttributeBag; diff --git a/.github/scripts/component-locator-smoke.php b/.github/scripts/component-locator-smoke.php index 801d4b1..15d9a57 100644 --- a/.github/scripts/component-locator-smoke.php +++ b/.github/scripts/component-locator-smoke.php @@ -186,6 +186,7 @@ static function ( array $args ): string { emulsify_locator_smoke_write( $parent . '/dist/components/native/block.json', '{"name":"emulsify/native-parent"}' ); emulsify_locator_smoke_write( $parent . '/dist/components/parent-native/block.json', '{"name":"emulsify/parent-native"}' ); + require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Blocks/ComponentLocator.php'; $locator = new Emulsify\Theme\Blocks\ComponentLocator(); diff --git a/.github/scripts/editor-enhancements-smoke.php b/.github/scripts/editor-enhancements-smoke.php index 60e043c..2096201 100644 --- a/.github/scripts/editor-enhancements-smoke.php +++ b/.github/scripts/editor-enhancements-smoke.php @@ -218,6 +218,7 @@ function emulsify_editor_enhancements_smoke_remove( string $path ): void { $GLOBALS['emulsify_editor_enhancements_smoke_parent'] = $parent; try { + require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Editor/Enhancements.php'; $service = new Emulsify\Theme\Editor\Enhancements(); diff --git a/.github/scripts/pattern-registry-smoke.php b/.github/scripts/pattern-registry-smoke.php index 5349d98..d01c559 100644 --- a/.github/scripts/pattern-registry-smoke.php +++ b/.github/scripts/pattern-registry-smoke.php @@ -179,6 +179,7 @@ function emulsify_pattern_smoke_remove( string $path ): void { $GLOBALS['emulsify_pattern_smoke_parent'] = $parent; try { + require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Blocks/Patterns.php'; $patterns = new Emulsify\Theme\Blocks\Patterns(); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index a504cfe..5f863a5 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -339,6 +339,7 @@ function runStaticChecks() { 'includes/Runtime/TimberIntegration.php', 'includes/Runtime/Twig.php', 'includes/Support/AttributeBag.php', + 'includes/Support/FileDiscovery.php', 'package.json', 'release.config.js', 'style.css', @@ -529,6 +530,7 @@ function runStaticChecks() { runStaticCheck('Component locator memoization', () => { const locator = readFile('includes/Blocks/ComponentLocator.php'); + const fileDiscovery = readFile('includes/Support/FileDiscovery.php'); const registry = readFile('includes/Blocks/Registry.php'); const acfBlocks = readFile('includes/Blocks/AcfBlocks.php'); const nativeBlocks = readFile('includes/Blocks/NativeBlocks.php'); @@ -541,10 +543,11 @@ function runStaticChecks() { ensure(locator.includes('private $skipped_duplicates'), 'Component locator should track skipped duplicate component records.'); ensure(locator.includes('function component_files'), 'Component locator should expose a shared internal component file index.'); ensure(locator.includes('$this->component_files()'), 'ACF/Twig and native discovery should use the shared component file index.'); + ensure(locator.includes('FileDiscovery::theme_roots') && locator.includes('FileDiscovery::file_records'), 'Component locator should use shared filesystem discovery helpers.'); ensure(locator.includes('acf_component_slug'), 'Component locator should detect duplicate ACF/Twig component slugs.'); ensure(locator.includes('native_block_name'), 'Component locator should detect duplicate native block names.'); - ensure(locator.includes('get_stylesheet_directory()') && locator.includes('get_template_directory()'), 'Component locator should keep child and parent component roots.'); - ensure(locator.indexOf('get_stylesheet_directory()') < locator.indexOf('get_template_directory()'), 'Component locator should keep child roots before parent roots.'); + ensure(fileDiscovery.includes('get_stylesheet_directory()') && fileDiscovery.includes('get_template_directory()'), 'File discovery helper should build child and parent theme roots.'); + ensure(fileDiscovery.indexOf('get_stylesheet_directory()') < fileDiscovery.indexOf('get_template_directory()'), 'File discovery helper should keep child roots before parent roots.'); ensure(!/wp_cache_|transient/i.test(locator), 'Component locator should not use persistent caching without invalidation.'); ensure(registry.includes('$components = new ComponentLocator()'), 'Block registry should share one ComponentLocator instance.'); ensure(acfBlocks.includes('$this->components->acf_components()'), 'ACF/Twig block discovery should use ComponentLocator.'); @@ -572,6 +575,7 @@ function runStaticChecks() { const editorPolicy = readFile('includes/Editor/Policy.php'); const patterns = readFile('includes/Blocks/Patterns.php'); const locator = readFile('includes/Blocks/ComponentLocator.php'); + const fileDiscovery = readFile('includes/Support/FileDiscovery.php'); const acfBlocks = readFile('includes/Blocks/AcfBlocks.php'); const nativeBlocks = readFile('includes/Blocks/NativeBlocks.php'); const acfJsonSmoke = readFile('.github/scripts/acf-local-json-smoke.php'); @@ -619,6 +623,21 @@ function runStaticChecks() { ensure(smoke.includes('Theme filter smoke checks passed'), 'Runtime filter smoke should have a clear success message.'); ensure(editorPolicySmoke.includes('Editor policy smoke checks passed'), 'Editor policy smoke should have a clear success message.'); ensure(patternSmoke.includes('Pattern registry smoke checks passed'), 'Pattern registry smoke should have a clear success message.'); + ensure(fileDiscovery.includes('function theme_roots'), 'File discovery helper should create child-first theme roots.'); + ensure(fileDiscovery.includes('function normalize_roots'), 'File discovery helper should normalize readable unique roots.'); + ensure(fileDiscovery.includes('function file_records'), 'File discovery helper should create file records for service-specific filtering.'); + ensure(fileDiscovery.includes('function recursive_files'), 'File discovery helper should support recursive scans.'); + ensure(fileDiscovery.includes('function directory_files'), 'File discovery helper should support shallow directory scans.'); + ensure(fileDiscovery.includes('function relative_path'), 'File discovery helper should normalize POSIX relative paths.'); + ensure(fileDiscovery.includes('function sort_by_priority_and_relative'), 'File discovery helper should provide stable priority sorting.'); + ensure(assets.includes('FileDiscovery::theme_roots') && assets.includes('FileDiscovery::file_records'), 'Runtime assets should use shared filesystem discovery helpers.'); + ensure(editorEnhancements.includes('FileDiscovery::theme_roots') && editorEnhancements.includes('FileDiscovery::file_records'), 'Editor assets should use shared filesystem discovery helpers.'); + ensure(locator.includes('FileDiscovery::theme_roots') && locator.includes('FileDiscovery::file_records'), 'Component locator should use shared filesystem discovery helpers.'); + ensure(patterns.includes('FileDiscovery::theme_roots') && patterns.includes("FileDiscovery::file_records( $this->pattern_directories(), array( 'json' ), false )"), 'Pattern discovery should use shared helpers while staying shallow.'); + ensure(twig.includes('FileDiscovery::normalize_roots'), 'Twig project component roots should use shared root normalization.'); + ensure(smoke.includes('Asset discovery should keep child roots before parent fallback roots'), 'Runtime filter smoke should verify asset root priority.'); + ensure(editorEnhancementsSmoke.includes('skip duplicate parent relative paths'), 'Editor enhancements smoke should verify editor asset duplicate handling.'); + ensure(patternSmoke.includes('Child pattern JSON files should override parent files with the same basename'), 'Pattern smoke should verify child-first pattern discovery.'); return 'Parent runtime exposes documented filters with smoke coverage.'; }); diff --git a/.github/scripts/theme-filters-smoke.php b/.github/scripts/theme-filters-smoke.php index b22e92d..43ebd76 100644 --- a/.github/scripts/theme-filters-smoke.php +++ b/.github/scripts/theme-filters-smoke.php @@ -298,6 +298,7 @@ function emulsify_filter_smoke_remove( string $path ): void { try { emulsify_filter_smoke_write( $child . '/dist/global/child.css', 'body { color: black; }' ); emulsify_filter_smoke_write( $child . '/dist/global/editor/css/index.css', '.editor-only { display: block; }' ); + emulsify_filter_smoke_write( $parent . '/dist/global/child.css', 'body { color: parent; }' ); emulsify_filter_smoke_write( $extra . '/assets/extra.css', 'body { color: red; }' ); emulsify_filter_smoke_write( $extra . '/twig/example.twig', 'Example' ); emulsify_filter_smoke_write( $extra . '/components/filter-card/filter-card.component.json', '{"title":"Filter Card"}' ); @@ -305,6 +306,7 @@ function emulsify_filter_smoke_remove( string $path ): void { emulsify_filter_smoke_write( $extra . '/native/block.json', '{"name":"emulsify/filter-native"}' ); require_once $repo_root . '/includes/Support/AttributeBag.php'; + require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Runtime/Assets.php'; require_once $repo_root . '/includes/Runtime/Context.php'; require_once $repo_root . '/includes/Runtime/Setup.php'; @@ -433,6 +435,10 @@ static function ( array $directories ) use ( $extra ): array { ( new Emulsify\Theme\Runtime\Assets() )->styles(); + emulsify_filter_smoke_assert( + 'https://example.test/wp-content/themes/child/dist/global/child.css' === $GLOBALS['emulsify_filter_smoke_styles']['emulsify-global-child']['src'], + 'Asset discovery should keep child roots before parent fallback roots for duplicate relative paths.' + ); emulsify_filter_smoke_assert( isset( $GLOBALS['emulsify_filter_smoke_styles']['emulsify-global-extra'] ), 'Asset directory filter should add an extra CSS asset root.' diff --git a/.github/scripts/twig-project-namespace-smoke.php b/.github/scripts/twig-project-namespace-smoke.php index b636032..1026c92 100644 --- a/.github/scripts/twig-project-namespace-smoke.php +++ b/.github/scripts/twig-project-namespace-smoke.php @@ -76,6 +76,7 @@ function get_template_directory(): string { } require_once $repo_root . '/includes/Support/AttributeBag.php'; +require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Runtime/Twig.php'; /** diff --git a/includes/Blocks/ComponentLocator.php b/includes/Blocks/ComponentLocator.php index 671048a..3c38fd8 100644 --- a/includes/Blocks/ComponentLocator.php +++ b/includes/Blocks/ComponentLocator.php @@ -7,6 +7,8 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\FileDiscovery; + /** * Finds component metadata in child and parent theme build output. * @@ -81,7 +83,7 @@ public function acf_components(): array { } $directory = dirname( $path ); - $relative = $this->relative_path( $file['root_path'], $directory ); + $relative = FileDiscovery::relative_path( $file['root_path'], $directory ); $key = '' === $relative ? '.' : $relative; $record = $this->component_record( $file, $directory, $relative, $path ); @@ -156,7 +158,7 @@ public function native_block_directories(): array { } $directory = dirname( $path ); - $relative = $this->relative_path( $file['root_path'], $directory ); + $relative = FileDiscovery::relative_path( $file['root_path'], $directory ); $key = '' === $relative ? '.' : $relative; $name = $this->native_block_name( $path ); $record = $this->component_record( $file, $directory, $relative, $path ); @@ -242,19 +244,7 @@ private function component_files(): array { return $this->component_files; } - $files = array(); - - foreach ( $this->component_roots() as $root ) { - foreach ( $this->recursive_files( $root['path'] ) as $path ) { - $files[] = array( - 'path' => $path, - 'root_path' => $root['path'], - 'root_source' => $root['source'], - ); - } - } - - $this->component_files = $files; + $this->component_files = FileDiscovery::file_records( $this->component_roots() ); return $this->component_files; } @@ -273,18 +263,7 @@ private function component_roots(): array { return $this->component_roots; } - $roots = array(); - $seen_paths = array(); - $candidates = array( - array( - 'path' => rtrim( get_stylesheet_directory(), '/\\' ) . '/' . self::COMPONENTS_DIRECTORY, - 'source' => 'child', - ), - array( - 'path' => rtrim( get_template_directory(), '/\\' ) . '/' . self::COMPONENTS_DIRECTORY, - 'source' => 'parent', - ), - ); + $candidates = FileDiscovery::theme_roots( self::COMPONENTS_DIRECTORY ); /** * Filters built component discovery roots before scanning. @@ -301,28 +280,12 @@ private function component_roots(): array { $candidates = $filtered; } - foreach ( $candidates as $candidate ) { - if ( ! is_array( $candidate ) || empty( $candidate['path'] ) ) { - continue; - } - - $path = rtrim( (string) $candidate['path'], '/\\' ); - $key = realpath( $path ); - - if ( false === $key || isset( $seen_paths[ $key ] ) || ! is_dir( $path ) || ! is_readable( $path ) ) { - // Missing build output is expected before a project installs and runs - // its chosen component system. - continue; - } - - $seen_paths[ $key ] = true; - $roots[] = array( - 'path' => rtrim( $path, '/\\' ), - 'source' => isset( $candidate['source'] ) ? (string) $candidate['source'] : 'filtered', - ); - } - - $this->component_roots = $roots; + $this->component_roots = FileDiscovery::normalize_roots( + $candidates, + array( + 'default_source' => 'filtered', + ) + ); return $this->component_roots; } @@ -365,29 +328,6 @@ private function record_skipped_duplicate( string $type, string $name, array $ke ); } - /** - * Gets all files below a directory in deterministic order. - * - * @param string $directory Absolute directory path. - * @return array Absolute file paths. - */ - private function recursive_files( string $directory ): array { - $files = array(); - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator( $directory, \RecursiveDirectoryIterator::SKIP_DOTS ) - ); - - foreach ( $iterator as $file ) { - if ( $file->isFile() ) { - $files[] = $file->getPathname(); - } - } - - sort( $files ); - - return $files; - } - /** * Gets the declared block name from a native block.json file. * @@ -447,19 +387,6 @@ private function slug( string $relative, string $metadata_path ): string { return sanitize_title( str_replace( '/', '-', (string) $slug ) ); } - /** - * Builds a POSIX path relative to the component root. - * - * @param string $base_path Base directory. - * @param string $path Absolute path. - * @return string Relative path. - */ - private function relative_path( string $base_path, string $path ): string { - $relative = ltrim( str_replace( rtrim( $base_path, '/\\' ), '', $path ), '/\\' ); - - return str_replace( '\\', '/', $relative ); - } - /** * Builds a theme-relative path for Timber rendering. * @@ -468,7 +395,7 @@ private function relative_path( string $base_path, string $path ): string { * @return string Theme-relative template path. */ private function theme_relative_path( string $root_path, string $path ): string { - $relative = $this->relative_path( $root_path, $path ); + $relative = FileDiscovery::relative_path( $root_path, $path ); return self::COMPONENTS_DIRECTORY . '/' . $relative; } diff --git a/includes/Blocks/Patterns.php b/includes/Blocks/Patterns.php index 6ea8aff..e222442 100644 --- a/includes/Blocks/Patterns.php +++ b/includes/Blocks/Patterns.php @@ -7,6 +7,8 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\FileDiscovery; + /** * Discovers child-first JSON block patterns. */ @@ -102,48 +104,35 @@ private function pattern_files(): array { $seen_relative = array(); $seen_file_path = array(); - foreach ( $this->pattern_directories() as $directory ) { - $paths = glob( $directory['path'] . '/*.json' ); + foreach ( FileDiscovery::file_records( $this->pattern_directories(), array( 'json' ), false ) as $file ) { + $path = $file['path']; + $relative = basename( $path ); + $real = realpath( $path ) ?: $path; - if ( ! is_array( $paths ) ) { + if ( isset( $seen_file_path[ $real ] ) ) { continue; } - sort( $paths ); - - foreach ( $paths as $path ) { - if ( ! is_file( $path ) || ! is_readable( $path ) ) { - continue; - } - - $relative = basename( $path ); - $real = realpath( $path ) ?: $path; - - if ( isset( $seen_file_path[ $real ] ) ) { - continue; - } - - if ( isset( $seen_relative[ $relative ] ) ) { - // Directories are scanned child-first. A child JSON file with the - // same basename intentionally overrides the parent starter file. - $this->debug( - sprintf( - 'Skipped duplicate block pattern JSON file "%s" from %s.', - $relative, - $path - ) - ); - continue; - } - - $seen_file_path[ $real ] = true; - $seen_relative[ $relative ] = true; - $files[] = array( - 'path' => $path, - 'relative' => $relative, - 'source' => $directory['source'], + if ( isset( $seen_relative[ $relative ] ) ) { + // Directories are scanned child-first. A child JSON file with the + // same basename intentionally overrides the parent starter file. + $this->debug( + sprintf( + 'Skipped duplicate block pattern JSON file "%s" from %s.', + $relative, + $path + ) ); + continue; } + + $seen_file_path[ $real ] = true; + $seen_relative[ $relative ] = true; + $files[] = array( + 'path' => $path, + 'relative' => $relative, + 'source' => $file['root_source'], + ); } return $files; @@ -155,21 +144,7 @@ private function pattern_files(): array { * @return array Pattern directory records. */ private function pattern_directories(): array { - $directories = array(); - - if ( function_exists( 'get_stylesheet_directory' ) ) { - $directories[] = array( - 'path' => rtrim( get_stylesheet_directory(), '/\\' ) . '/patterns', - 'source' => 'child', - ); - } - - if ( function_exists( 'get_template_directory' ) ) { - $directories[] = array( - 'path' => rtrim( get_template_directory(), '/\\' ) . '/patterns', - 'source' => 'parent', - ); - } + $directories = FileDiscovery::theme_roots( 'patterns' ); /** * Filters directories scanned for JSON block patterns. @@ -185,46 +160,21 @@ private function pattern_directories(): array { $directories = $filtered; } - return $this->normalize_directories( $directories ); - } - - /** - * Normalizes configured pattern directory records. - * - * @param array $directories Directory candidates. - * @return array Valid directory records. - */ - private function normalize_directories( array $directories ): array { - $normalized = array(); - $seen = array(); - foreach ( $directories as $index => $directory ) { if ( is_string( $directory ) ) { - $directory = array( + $directories[ $index ] = array( 'path' => $directory, 'source' => 'filtered', ); } - - if ( ! is_array( $directory ) || empty( $directory['path'] ) || ! is_scalar( $directory['path'] ) ) { - continue; - } - - $path = rtrim( (string) $directory['path'], '/\\' ); - $real = realpath( $path ); - - if ( false === $real || isset( $seen[ $real ] ) || ! is_dir( $path ) || ! is_readable( $path ) ) { - continue; - } - - $seen[ $real ] = true; - $normalized[] = array( - 'path' => $path, - 'source' => isset( $directory['source'] ) && is_scalar( $directory['source'] ) ? (string) $directory['source'] : (string) $index, - ); } - return $normalized; + return FileDiscovery::normalize_roots( + $directories, + array( + 'default_source' => null, + ) + ); } /** diff --git a/includes/Editor/Enhancements.php b/includes/Editor/Enhancements.php index f5011a1..f63e34d 100644 --- a/includes/Editor/Enhancements.php +++ b/includes/Editor/Enhancements.php @@ -7,6 +7,8 @@ namespace Emulsify\Theme\Editor; +use Emulsify\Theme\Support\FileDiscovery; + /** * Optional editor enhancement runtime. */ @@ -254,41 +256,24 @@ private function asset_files( string $directory, array $extensions ): array { $assets = array(); $seen = array(); - foreach ( $this->asset_roots( $directory ) as $root ) { - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator( $root['path'], \RecursiveDirectoryIterator::SKIP_DOTS ) - ); + foreach ( FileDiscovery::file_records( $this->asset_roots( $directory ), $extensions ) as $file ) { + $relative = $file['relative']; - foreach ( $iterator as $file ) { - if ( ! $file->isFile() || ! in_array( strtolower( $file->getExtension() ), $extensions, true ) ) { - continue; - } - - $relative = $this->relative_path( $root['path'], $file->getPathname() ); - - if ( isset( $seen[ $relative ] ) ) { - continue; - } - - $seen[ $relative ] = true; - $assets[] = array( - 'path' => $file->getPathname(), - 'priority' => $root['priority'], - 'relative' => $relative, - 'uri' => $root['uri'] . '/' . $relative, - 'version' => $this->version( $file->getPathname() ), - ); + if ( isset( $seen[ $relative ] ) ) { + continue; } - } - usort( - $assets, - static function ( array $left, array $right ): int { - $priority = $left['priority'] <=> $right['priority']; + $seen[ $relative ] = true; + $assets[] = array( + 'path' => $file['path'], + 'priority' => $file['priority'], + 'relative' => $relative, + 'uri' => $file['root_uri'] . '/' . $relative, + 'version' => $this->version( $file['path'] ), + ); + } - return 0 === $priority ? strcmp( $left['relative'], $right['relative'] ) : $priority; - } - ); + $assets = FileDiscovery::sort_by_priority_and_relative( $assets ); /** * Filters built editor asset files before they are enqueued. @@ -311,22 +296,7 @@ static function ( array $left, array $right ): int { * @return array Asset root records. */ private function asset_roots( string $directory ): array { - $roots = array(); - $seen_paths = array(); - $directories = array( - array( - 'path' => rtrim( get_stylesheet_directory(), '/\\' ) . '/' . ltrim( $directory, '/\\' ), - 'priority' => 0, - 'source' => 'child', - 'uri' => rtrim( get_stylesheet_directory_uri(), '/' ) . '/' . trim( $directory, '/' ), - ), - array( - 'path' => rtrim( get_template_directory(), '/\\' ) . '/' . ltrim( $directory, '/\\' ), - 'priority' => 1, - 'source' => 'parent', - 'uri' => rtrim( get_template_directory_uri(), '/' ) . '/' . trim( $directory, '/' ), - ), - ); + $directories = FileDiscovery::theme_roots( $directory, true ); /** * Filters built editor asset directories before discovery. @@ -344,30 +314,12 @@ private function asset_roots( string $directory ): array { $directories = $filtered; } - foreach ( $directories as $priority => $candidate ) { - if ( ! is_array( $candidate ) || empty( $candidate['path'] ) || empty( $candidate['uri'] ) ) { - continue; - } - - $path = rtrim( (string) $candidate['path'], '/\\' ); - $uri = rtrim( (string) $candidate['uri'], '/' ); - $key = realpath( $path ); - - if ( false === $key || isset( $seen_paths[ $key ] ) || ! is_dir( $path ) || ! is_readable( $path ) ) { - // Missing editor build output is normal for an agnostic starter. Only - // enqueue assets after a project has opted into building them. - continue; - } - - $seen_paths[ $key ] = true; - $roots[] = array( - 'path' => rtrim( $path, '/\\' ), - 'priority' => isset( $candidate['priority'] ) ? (int) $candidate['priority'] : (int) $priority, - 'uri' => $uri, - ); - } - - return $roots; + return FileDiscovery::normalize_roots( + $directories, + array( + 'require_uri' => true, + ) + ); } /** @@ -500,19 +452,6 @@ private function handle( string $prefix, string $relative ): string { return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); } - /** - * Builds a POSIX relative path. - * - * @param string $base_path Base directory. - * @param string $path Absolute file path. - * @return string Relative path. - */ - private function relative_path( string $base_path, string $path ): string { - $relative = ltrim( str_replace( rtrim( $base_path, '/\\' ), '', $path ), '/\\' ); - - return str_replace( '\\', '/', $relative ); - } - /** * Gets a filemtime-based asset version. * diff --git a/includes/Runtime/Assets.php b/includes/Runtime/Assets.php index 2d1e26c..9de0dc6 100644 --- a/includes/Runtime/Assets.php +++ b/includes/Runtime/Assets.php @@ -7,6 +7,8 @@ namespace Emulsify\Theme\Runtime; +use Emulsify\Theme\Support\FileDiscovery; + /** * Global asset registration. */ @@ -105,50 +107,33 @@ private function asset_files( string $directory, array $extensions ): array { $assets = array(); $seen = array(); - foreach ( $this->asset_roots( $directory ) as $root ) { - $iterator = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator( $root['path'], \RecursiveDirectoryIterator::SKIP_DOTS ) - ); - - foreach ( $iterator as $file ) { - if ( ! $file->isFile() || ! in_array( strtolower( $file->getExtension() ), $extensions, true ) ) { - continue; - } - - $relative = $this->relative_path( $root['path'], $file->getPathname() ); - - if ( $this->is_reserved_editor_asset( $directory, $relative ) ) { - // Editor-only assets are handled by Editor\Enhancements so they - // receive editor dependencies and configuration before enqueue. - continue; - } + foreach ( FileDiscovery::file_records( $this->asset_roots( $directory ), $extensions ) as $file ) { + $relative = $file['relative']; - if ( isset( $seen[ $relative ] ) ) { - // Discovery roots are child-first. Once a relative path is seen, - // later parent files with the same name are intentional fallbacks - // and should not be enqueued twice. - continue; - } + if ( $this->is_reserved_editor_asset( $directory, $relative ) ) { + // Editor-only assets are handled by Editor\Enhancements so they + // receive editor dependencies and configuration before enqueue. + continue; + } - $seen[ $relative ] = true; - $assets[] = array( - 'path' => $file->getPathname(), - 'priority' => $root['priority'], - 'relative' => $relative, - 'uri' => $root['uri'] . '/' . $relative, - 'version' => $this->version( $file->getPathname() ), - ); + if ( isset( $seen[ $relative ] ) ) { + // Discovery roots are child-first. Once a relative path is seen, + // later parent files with the same name are intentional fallbacks + // and should not be enqueued twice. + continue; } - } - usort( - $assets, - static function ( array $left, array $right ): int { - $priority = $left['priority'] <=> $right['priority']; + $seen[ $relative ] = true; + $assets[] = array( + 'path' => $file['path'], + 'priority' => $file['priority'], + 'relative' => $relative, + 'uri' => $file['root_uri'] . '/' . $relative, + 'version' => $this->version( $file['path'] ), + ); + } - return 0 === $priority ? strcmp( $left['relative'], $right['relative'] ) : $priority; - } - ); + $assets = FileDiscovery::sort_by_priority_and_relative( $assets ); /** * Filters built asset files before they are enqueued. @@ -172,22 +157,7 @@ static function ( array $left, array $right ): int { * @return array Asset root records. */ private function asset_roots( string $directory ): array { - $roots = array(); - $seen_paths = array(); - $directories = array( - array( - 'path' => rtrim( get_stylesheet_directory(), '/\\' ) . '/' . ltrim( $directory, '/\\' ), - 'priority' => 0, - 'source' => 'child', - 'uri' => rtrim( get_stylesheet_directory_uri(), '/' ) . '/' . trim( $directory, '/' ), - ), - array( - 'path' => rtrim( get_template_directory(), '/\\' ) . '/' . ltrim( $directory, '/\\' ), - 'priority' => 1, - 'source' => 'parent', - 'uri' => rtrim( get_template_directory_uri(), '/' ) . '/' . trim( $directory, '/' ), - ), - ); + $directories = FileDiscovery::theme_roots( $directory, true ); /** * Filters built asset directories before files are discovered. @@ -205,30 +175,12 @@ private function asset_roots( string $directory ): array { $directories = $filtered; } - foreach ( $directories as $priority => $candidate ) { - if ( ! is_array( $candidate ) || empty( $candidate['path'] ) || empty( $candidate['uri'] ) ) { - continue; - } - - $path = rtrim( (string) $candidate['path'], '/\\' ); - $uri = rtrim( (string) $candidate['uri'], '/' ); - $key = realpath( $path ); - - if ( false === $key || isset( $seen_paths[ $key ] ) || ! is_dir( $path ) || ! is_readable( $path ) ) { - // Ignore missing build directories silently. Generated child themes - // may not have installed a component system or produced Vite output yet. - continue; - } - - $seen_paths[ $key ] = true; - $roots[] = array( - 'path' => rtrim( $path, '/\\' ), - 'priority' => isset( $candidate['priority'] ) ? (int) $candidate['priority'] : (int) $priority, - 'uri' => $uri, - ); - } - - return $roots; + return FileDiscovery::normalize_roots( + $directories, + array( + 'require_uri' => true, + ) + ); } /** @@ -245,19 +197,6 @@ private function handle( string $prefix, string $relative ): string { return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); } - /** - * Builds a POSIX relative path. - * - * @param string $base_path Base directory. - * @param string $path Absolute file path. - * @return string Relative path. - */ - private function relative_path( string $base_path, string $path ): string { - $relative = ltrim( str_replace( rtrim( $base_path, '/\\' ), '', $path ), '/\\' ); - - return str_replace( '\\', '/', $relative ); - } - /** * Checks whether a built asset is reserved for editor-only loading. * diff --git a/includes/Runtime/Twig.php b/includes/Runtime/Twig.php index 3817ab8..8b9222b 100644 --- a/includes/Runtime/Twig.php +++ b/includes/Runtime/Twig.php @@ -8,6 +8,7 @@ namespace Emulsify\Theme\Runtime; use Emulsify\Theme\Support\AttributeBag; +use Emulsify\Theme\Support\FileDiscovery; /** * Twig integration for Timber. @@ -405,34 +406,7 @@ private function project_component_roots( string $machine_name, $loader ): array * @return array Component root paths. */ private function normalize_project_component_roots( array $roots ): array { - $paths = array(); - $seen = array(); - - foreach ( $roots as $root ) { - $path = is_array( $root ) ? ( $root['path'] ?? '' ) : $root; - - if ( ! is_scalar( $path ) ) { - continue; - } - - $path = rtrim( (string) $path, '/\\' ); - - if ( '' === $path || ! is_dir( $path ) || ! is_readable( $path ) ) { - continue; - } - - $key = realpath( $path ); - $key = false !== $key ? $key : $path; - - if ( isset( $seen[ $key ] ) ) { - continue; - } - - $seen[ $key ] = true; - $paths[] = $path; - } - - return $paths; + return array_column( FileDiscovery::normalize_roots( $roots ), 'path' ); } /** diff --git a/includes/Support/FileDiscovery.php b/includes/Support/FileDiscovery.php new file mode 100644 index 0000000..da2b4ef --- /dev/null +++ b/includes/Support/FileDiscovery.php @@ -0,0 +1,313 @@ + rtrim( get_stylesheet_directory(), '/\\' ) . '/' . $directory, + 'priority' => 0, + 'source' => 'child', + ); + + if ( $include_uri && function_exists( 'get_stylesheet_directory_uri' ) ) { + $root['uri'] = rtrim( get_stylesheet_directory_uri(), '/' ) . '/' . $directory; + } + + $roots[] = $root; + } + + if ( function_exists( 'get_template_directory' ) ) { + $root = array( + 'path' => rtrim( get_template_directory(), '/\\' ) . '/' . $directory, + 'priority' => 1, + 'source' => 'parent', + ); + + if ( $include_uri && function_exists( 'get_template_directory_uri' ) ) { + $root['uri'] = rtrim( get_template_directory_uri(), '/' ) . '/' . $directory; + } + + $roots[] = $root; + } + + return $roots; + } + + /** + * Normalizes root records to readable, unique directories. + * + * @param array $roots Root records or path strings. + * @param array $options Normalization options. + * @return array Normalized root records. + */ + public static function normalize_roots( array $roots, array $options = array() ): array { + $require_uri = ! empty( $options['require_uri'] ); + $default_source = array_key_exists( 'default_source', $options ) ? $options['default_source'] : 'filtered'; + $default_source = null === $default_source || is_scalar( $default_source ) ? $default_source : 'filtered'; + $default_source = null === $default_source ? null : (string) $default_source; + $normalized = array(); + $seen = array(); + + foreach ( $roots as $index => $root ) { + $candidate = self::normalize_root_candidate( $root, (int) $index, $default_source, $require_uri ); + + if ( null === $candidate ) { + continue; + } + + $real = realpath( $candidate['path'] ); + + if ( + false === $real + || isset( $seen[ $real ] ) + || ! is_dir( $candidate['path'] ) + || ! is_readable( $candidate['path'] ) + ) { + continue; + } + + $seen[ $real ] = true; + $normalized[] = $candidate; + } + + return $normalized; + } + + /** + * Gets file records for normalized roots. + * + * @param array $roots Normalized root records. + * @param array $extensions Allowed extensions. Empty allows all files. + * @param bool $recursive Whether child directories should be scanned. + * @return array File records. + */ + public static function file_records( array $roots, array $extensions = array(), bool $recursive = true ): array { + $records = array(); + + foreach ( $roots as $root ) { + if ( empty( $root['path'] ) || ! is_scalar( $root['path'] ) ) { + continue; + } + + $path = rtrim( (string) $root['path'], '/\\' ); + $files = $recursive ? self::recursive_files( $path, $extensions ) : self::directory_files( $path, $extensions ); + + foreach ( $files as $file ) { + $record = array( + 'path' => $file, + 'priority' => isset( $root['priority'] ) ? (int) $root['priority'] : 0, + 'relative' => self::relative_path( $path, $file ), + 'root_path' => $path, + 'root_source' => isset( $root['source'] ) ? (string) $root['source'] : '', + ); + + if ( isset( $root['uri'] ) ) { + $record['root_uri'] = rtrim( (string) $root['uri'], '/' ); + } + + $records[] = $record; + } + } + + return $records; + } + + /** + * Gets all files below a directory in deterministic order. + * + * @param string $directory Directory to scan. + * @param array $extensions Allowed extensions. Empty allows all files. + * @return array Absolute file paths. + */ + public static function recursive_files( string $directory, array $extensions = array() ): array { + $files = array(); + $extensions = self::normalize_extensions( $extensions ); + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $directory, \RecursiveDirectoryIterator::SKIP_DOTS ) + ); + + foreach ( $iterator as $file ) { + if ( $file->isFile() && self::extension_allowed( $file, $extensions ) ) { + $files[] = $file->getPathname(); + } + } + + sort( $files ); + + return $files; + } + + /** + * Gets immediate child files from a directory in deterministic order. + * + * @param string $directory Directory to scan. + * @param array $extensions Allowed extensions. Empty allows all files. + * @return array Absolute file paths. + */ + public static function directory_files( string $directory, array $extensions = array() ): array { + $files = array(); + $extensions = self::normalize_extensions( $extensions ); + $iterator = new \FilesystemIterator( $directory, \FilesystemIterator::SKIP_DOTS ); + + foreach ( $iterator as $file ) { + if ( $file->isFile() && self::extension_allowed( $file, $extensions ) ) { + $files[] = $file->getPathname(); + } + } + + sort( $files ); + + return $files; + } + + /** + * Builds a POSIX relative path. + * + * @param string $base_path Base directory. + * @param string $path Absolute file path. + * @return string Relative path. + */ + public static function relative_path( string $base_path, string $path ): string { + $relative = ltrim( str_replace( rtrim( $base_path, '/\\' ), '', $path ), '/\\' ); + + return str_replace( '\\', '/', $relative ); + } + + /** + * Sorts records by root priority and relative path while preserving ties. + * + * @param array $records File records. + * @return array Sorted file records. + */ + public static function sort_by_priority_and_relative( array $records ): array { + $indexed = array(); + + foreach ( $records as $index => $record ) { + $indexed[] = array( + 'index' => (int) $index, + 'record' => $record, + ); + } + + usort( + $indexed, + static function ( array $left, array $right ): int { + $left_record = $left['record']; + $right_record = $right['record']; + $priority = ( (int) ( $left_record['priority'] ?? 0 ) ) <=> ( (int) ( $right_record['priority'] ?? 0 ) ); + + if ( 0 !== $priority ) { + return $priority; + } + + $relative = strcmp( (string) ( $left_record['relative'] ?? '' ), (string) ( $right_record['relative'] ?? '' ) ); + + return 0 === $relative ? $left['index'] <=> $right['index'] : $relative; + } + ); + + return array_column( $indexed, 'record' ); + } + + /** + * Normalizes a single root candidate. + * + * @param mixed $root Root record or path string. + * @param int $index Root list index. + * @param string|null $default_source Source value for string roots. + * @param bool $require_uri Whether roots must include a URI. + * @return array|null Normalized candidate, or null when invalid. + */ + private static function normalize_root_candidate( $root, int $index, ?string $default_source, bool $require_uri ): ?array { + if ( is_string( $root ) ) { + $root = array( + 'path' => $root, + 'source' => $default_source ?? (string) $index, + ); + } + + if ( ! is_array( $root ) || empty( $root['path'] ) || ! is_scalar( $root['path'] ) ) { + return null; + } + + $path = rtrim( (string) $root['path'], '/\\' ); + + if ( '' === $path ) { + return null; + } + + $candidate = array( + 'path' => $path, + 'priority' => isset( $root['priority'] ) ? (int) $root['priority'] : $index, + 'source' => isset( $root['source'] ) && is_scalar( $root['source'] ) + ? (string) $root['source'] + : ( $default_source ?? (string) $index ), + ); + + if ( isset( $root['uri'] ) && is_scalar( $root['uri'] ) ) { + $candidate['uri'] = rtrim( (string) $root['uri'], '/' ); + } + + if ( $require_uri && empty( $candidate['uri'] ) ) { + return null; + } + + return $candidate; + } + + /** + * Normalizes extension names for comparisons. + * + * @param array $extensions Extension list. + * @return array Normalized extension list. + */ + private static function normalize_extensions( array $extensions ): array { + $normalized = array(); + + foreach ( $extensions as $extension ) { + if ( ! is_scalar( $extension ) ) { + continue; + } + + $extension = strtolower( ltrim( trim( (string) $extension ), '.' ) ); + + if ( '' !== $extension ) { + $normalized[] = $extension; + } + } + + return array_values( array_unique( $normalized ) ); + } + + /** + * Checks whether a file extension is allowed. + * + * @param \SplFileInfo $file File info. + * @param array $extensions Normalized allowed extensions. + * @return bool TRUE when the file should be included. + */ + private static function extension_allowed( \SplFileInfo $file, array $extensions ): bool { + return empty( $extensions ) || in_array( strtolower( $file->getExtension() ), $extensions, true ); + } +} From ae9e15f4c6ba3b40188b7a6dc91c8b8c370682b9 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 14:17:54 -0500 Subject: [PATCH 41/55] feat: support optional asset manifest loading --- .github/scripts/asset-manifest-smoke.php | 455 ++++++++++++++++++ .github/scripts/editor-enhancements-smoke.php | 1 + .github/scripts/pr-validation.cjs | 1 + .github/scripts/release-check.cjs | 20 +- .github/scripts/theme-filters-smoke.php | 1 + docs/asset-loading.md | 100 +++- includes/Editor/Enhancements.php | 84 +++- includes/Runtime/Assets.php | 109 ++++- includes/Support/AssetManifest.php | 423 ++++++++++++++++ package.json | 1 + 10 files changed, 1146 insertions(+), 49 deletions(-) create mode 100644 .github/scripts/asset-manifest-smoke.php create mode 100644 includes/Support/AssetManifest.php diff --git a/.github/scripts/asset-manifest-smoke.php b/.github/scripts/asset-manifest-smoke.php new file mode 100644 index 0000000..e279aa4 --- /dev/null +++ b/.github/scripts/asset-manifest-smoke.php @@ -0,0 +1,455 @@ + $accepted_args, + 'callback' => $callback, + ); + + ksort( $GLOBALS['emulsify_asset_manifest_smoke_hooks'][ $hook ] ); + + return true; + } +} + +if ( ! function_exists( 'apply_filters' ) ) { + function apply_filters( string $hook, $value, ...$arguments ) { + if ( empty( $GLOBALS['emulsify_asset_manifest_smoke_hooks'][ $hook ] ) ) { + return $value; + } + + foreach ( $GLOBALS['emulsify_asset_manifest_smoke_hooks'][ $hook ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + $value = call_user_func_array( + $callback['callback'], + array_slice( + array_merge( array( $value ), $arguments ), + 0, + $callback['accepted_args'] + ) + ); + } + } + + return $value; + } +} + +if ( ! function_exists( 'get_stylesheet_directory' ) ) { + function get_stylesheet_directory(): string { + return $GLOBALS['emulsify_asset_manifest_smoke_child']; + } +} + +if ( ! function_exists( 'get_template_directory' ) ) { + function get_template_directory(): string { + return $GLOBALS['emulsify_asset_manifest_smoke_parent']; + } +} + +if ( ! function_exists( 'get_stylesheet_directory_uri' ) ) { + function get_stylesheet_directory_uri(): string { + return 'https://example.test/child'; + } +} + +if ( ! function_exists( 'get_template_directory_uri' ) ) { + function get_template_directory_uri(): string { + return 'https://example.test/parent'; + } +} + +if ( ! function_exists( 'sanitize_key' ) ) { + function sanitize_key( string $key ): string { + return trim( preg_replace( '/[^a-z0-9_-]+/', '-', strtolower( $key ) ), '-' ); + } +} + +if ( ! function_exists( 'wp_enqueue_style' ) ) { + function wp_enqueue_style( string $handle, string $src, array $deps = array(), $ver = false ): void { + $GLOBALS['emulsify_asset_manifest_smoke_styles'][ $handle ] = compact( 'src', 'deps', 'ver' ); + } +} + +if ( ! function_exists( 'wp_enqueue_script' ) ) { + function wp_enqueue_script( string $handle, string $src, array $deps = array(), $ver = false, $args = array() ): void { + $GLOBALS['emulsify_asset_manifest_smoke_scripts'][ $handle ] = compact( 'src', 'deps', 'ver', 'args' ); + } +} + +if ( ! function_exists( 'wp_enqueue_script_module' ) ) { + function wp_enqueue_script_module( string $id, string $src, array $deps = array(), $version = false ): void { + $GLOBALS['emulsify_asset_manifest_smoke_script_modules'][ $id ] = compact( 'src', 'deps', 'version' ); + } +} + +if ( ! function_exists( 'wp_add_inline_script' ) ) { + function wp_add_inline_script( string $handle, string $data, string $position = 'after' ): bool { + $GLOBALS['emulsify_asset_manifest_smoke_inline'][] = compact( 'handle', 'data', 'position' ); + + return true; + } +} + +if ( ! function_exists( 'wp_json_encode' ) ) { + function wp_json_encode( $value ) { + return json_encode( $value ); + } +} + +/** + * Fails the smoke script when an assertion is false. + * + * @param bool $condition Assertion condition. + * @param string $message Failure message. + * @return void + */ +function emulsify_asset_manifest_smoke_assert( bool $condition, string $message ): void { + if ( ! $condition ) { + throw new RuntimeException( $message ); + } +} + +/** + * Writes a fixture file. + * + * @param string $path File path. + * @param string $content File content. + * @return void + */ +function emulsify_asset_manifest_smoke_write( string $path, string $content ): void { + $directory = dirname( $path ); + + if ( ! is_dir( $directory ) ) { + mkdir( $directory, 0777, true ); + } + + file_put_contents( $path, $content ); +} + +/** + * Writes JSON fixture data. + * + * @param string $path File path. + * @param array $data JSON data. + * @return void + */ +function emulsify_asset_manifest_smoke_write_json( string $path, array $data ): void { + emulsify_asset_manifest_smoke_write( $path, (string) json_encode( $data ) ); +} + +/** + * Resets smoke globals for a case. + * + * @param string $child Child theme path. + * @param string $parent Parent theme path. + * @return void + */ +function emulsify_asset_manifest_smoke_reset( string $child, string $parent ): void { + $GLOBALS['emulsify_asset_manifest_smoke_child'] = $child; + $GLOBALS['emulsify_asset_manifest_smoke_parent'] = $parent; + $GLOBALS['emulsify_asset_manifest_smoke_hooks'] = array(); + $GLOBALS['emulsify_asset_manifest_smoke_styles'] = array(); + $GLOBALS['emulsify_asset_manifest_smoke_scripts'] = array(); + $GLOBALS['emulsify_asset_manifest_smoke_script_modules'] = array(); + $GLOBALS['emulsify_asset_manifest_smoke_inline'] = array(); +} + +/** + * Recursively removes a path. + * + * @param string $path Path to remove. + * @return void + */ +function emulsify_asset_manifest_smoke_remove( string $path ): void { + if ( ! file_exists( $path ) ) { + return; + } + + if ( is_file( $path ) || is_link( $path ) ) { + unlink( $path ); + return; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $iterator as $item ) { + $item->isDir() && ! $item->isLink() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); + } + + rmdir( $path ); +} + +$repo_root = dirname( __DIR__, 2 ); +$work_root = sys_get_temp_dir() . '/emulsify-asset-manifest-' . uniqid( '', true ); + +try { + require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Support/AssetManifest.php'; + require_once $repo_root . '/includes/Runtime/Assets.php'; + require_once $repo_root . '/includes/Editor/Enhancements.php'; + + $child = $work_root . '/fallback-child'; + $parent = $work_root . '/fallback-parent'; + emulsify_asset_manifest_smoke_reset( $child, $parent ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/fallback.css', '.fallback{}' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/components/fallback.js', 'export default true;' ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + ( new Emulsify\Theme\Runtime\Assets() )->frontend_scripts(); + + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-fallback'] ), + 'No manifest should fall back to recursive global CSS discovery.' + ); + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_script_modules']['emulsify-component-fallback'] ), + 'No manifest should fall back to recursive component JS discovery.' + ); + + $child = $work_root . '/manifest-child'; + $parent = $work_root . '/manifest-parent'; + emulsify_asset_manifest_smoke_reset( $child, $parent ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/scanner-only.css', '.scanner{}' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/manifest.css', '.manifest{}' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/app.js', 'window.manifestApp = true;' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/components/card.css', '.card{}' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/components/card.js', 'export default true;' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/components/blocks/hero.css', '.hero{}' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/editor/editor.css', '.editor{}' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/editor/editor.js', 'window.editor = true;' ); + emulsify_asset_manifest_smoke_write_json( + $child . '/dist/emulsify-assets.json', + array( + 'assets' => array( + 'global' => array( + 'css' => array( + array( + 'path' => 'global/manifest.css', + 'relative' => 'manifest.css', + 'version' => 'manifest-css', + 'dependencies' => array( 'wp-block-library' ), + ), + ), + 'js' => array( + array( + 'path' => 'global/app.js', + 'relative' => 'app.js', + 'hash' => 'manifest-js', + 'deps' => array( 'jquery' ), + 'module' => false, + ), + ), + ), + 'components' => array( + 'css' => array( + array( + 'path' => 'components/card.css', + 'relative' => 'card.css', + 'version' => 'component-css', + ), + ), + 'js' => array( + array( + 'path' => 'components/card.js', + 'relative' => 'card.js', + 'version' => 'component-js', + 'module' => true, + ), + ), + ), + 'blocks' => array( + 'emulsify/hero' => array( + 'css' => array( + array( + 'path' => 'components/blocks/hero.css', + 'relative' => 'blocks/hero.css', + 'version' => 'block-css', + ), + ), + ), + ), + 'editor' => array( + 'css' => array( + array( + 'path' => 'global/editor/editor.css', + 'version' => 'editor-css', + ), + ), + 'js' => array( + array( + 'path' => 'global/editor/editor.js', + 'version' => 'editor-js', + 'dependencies' => array( 'wp-edit-post' ), + ), + ), + ), + ), + ) + ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + ( new Emulsify\Theme\Runtime\Assets() )->frontend_scripts(); + ( new Emulsify\Theme\Editor\Enhancements() )->editor_assets(); + + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-manifest'] ) + && 'manifest-css' === $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-manifest']['ver'], + 'Valid manifest should enqueue global CSS with explicit version metadata.' + ); + emulsify_asset_manifest_smoke_assert( + in_array( 'wp-block-library', $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-manifest']['deps'], true ), + 'Valid manifest should preserve style dependencies.' + ); + emulsify_asset_manifest_smoke_assert( + ! isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-scanner-only'] ), + 'Declared manifest scopes should avoid recursive scanner-only assets.' + ); + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-component-card'] ) + && isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-component-blocks-hero'] ), + 'Valid manifest should enqueue component and block-specific CSS records.' + ); + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_scripts']['emulsify-global-app'] ) + && 'manifest-js' === $GLOBALS['emulsify_asset_manifest_smoke_scripts']['emulsify-global-app']['ver'] + && in_array( 'jquery', $GLOBALS['emulsify_asset_manifest_smoke_scripts']['emulsify-global-app']['deps'], true ), + 'Manifest global JS should support classic scripts, hashes, and dependencies.' + ); + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_script_modules']['emulsify-component-card'] ), + 'Manifest component JS should support script modules.' + ); + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-editor-global-editor-editor'] ) + && isset( $GLOBALS['emulsify_asset_manifest_smoke_scripts']['emulsify-editor-global-editor-editor'] ) + && in_array( 'wp-edit-post', $GLOBALS['emulsify_asset_manifest_smoke_scripts']['emulsify-editor-global-editor-editor']['deps'], true ), + 'Manifest editor assets should enqueue with editor dependencies.' + ); + + $child = $work_root . '/invalid-child'; + $parent = $work_root . '/invalid-parent'; + emulsify_asset_manifest_smoke_reset( $child, $parent ); + emulsify_asset_manifest_smoke_write( $child . '/dist/emulsify-assets.json', '{invalid' ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/fallback.css', '.fallback{}' ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-fallback'] ), + 'Invalid manifest should fall back safely to recursive discovery.' + ); + + $child = $work_root . '/priority-child'; + $parent = $work_root . '/priority-parent'; + emulsify_asset_manifest_smoke_reset( $child, $parent ); + emulsify_asset_manifest_smoke_write( $child . '/dist/global/child.css', '.child{}' ); + emulsify_asset_manifest_smoke_write( $parent . '/dist/global/parent.css', '.parent{}' ); + emulsify_asset_manifest_smoke_write_json( + $child . '/dist/emulsify-assets.json', + array( + 'assets' => array( + 'global' => array( + 'css' => array( array( 'path' => 'global/child.css' ) ), + ), + ), + ) + ); + emulsify_asset_manifest_smoke_write_json( + $parent . '/dist/emulsify-assets.json', + array( + 'assets' => array( + 'global' => array( + 'css' => array( array( 'path' => 'global/parent.css' ) ), + ), + ), + ) + ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-global-child'] ) + && ! isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-global-parent'] ), + 'Child manifest should take priority over parent manifest.' + ); + + $child = $work_root . '/filter-child'; + $parent = $work_root . '/filter-parent'; + emulsify_asset_manifest_smoke_reset( $child, $parent ); + emulsify_asset_manifest_smoke_write( $child . '/dist/alt/filter.css', '.filter{}' ); + emulsify_asset_manifest_smoke_write_json( + $child . '/dist/custom-assets.json', + array( + 'assets' => array( + 'global' => array( + 'css' => array( array( 'path' => 'alt/filter.css' ) ), + ), + ), + ) + ); + + add_filter( + 'emulsify_theme_asset_manifest_path', + static function ( string $path ): string { + unset( $path ); + + return 'dist/custom-assets.json'; + } + ); + add_filter( + 'emulsify_theme_asset_manifest_data', + static function ( array $data ): array { + $data['assets']['global']['css'][0]['version'] = 'filtered-manifest'; + + return $data; + } + ); + add_filter( + 'emulsify_theme_asset_files', + static function ( array $assets, string $directory ): array { + if ( 'dist/global' === $directory && isset( $assets[0]['manifest_path'] ) ) { + $assets[0]['version'] = 'filtered-final'; + } + + return $assets; + }, + 10, + 2 + ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + + emulsify_asset_manifest_smoke_assert( + isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-alt-filter'] ) + && 'filtered-final' === $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-global-alt-filter']['ver'], + 'Manifest path, parsed data, and final asset record filters should apply before enqueue.' + ); + + echo "Asset manifest smoke checks passed.\n"; +} catch ( Throwable $throwable ) { + fwrite( STDERR, $throwable->getMessage() . "\n" ); + exit( 1 ); +} finally { + emulsify_asset_manifest_smoke_remove( $work_root ); +} diff --git a/.github/scripts/editor-enhancements-smoke.php b/.github/scripts/editor-enhancements-smoke.php index 2096201..03e13f3 100644 --- a/.github/scripts/editor-enhancements-smoke.php +++ b/.github/scripts/editor-enhancements-smoke.php @@ -218,6 +218,7 @@ function emulsify_editor_enhancements_smoke_remove( string $path ): void { $GLOBALS['emulsify_editor_enhancements_smoke_parent'] = $parent; try { + require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Editor/Enhancements.php'; diff --git a/.github/scripts/pr-validation.cjs b/.github/scripts/pr-validation.cjs index d6535c6..684152b 100644 --- a/.github/scripts/pr-validation.cjs +++ b/.github/scripts/pr-validation.cjs @@ -33,6 +33,7 @@ run('Install Composer dependencies for Twig smoke coverage', 'composer', [ run('Run Bootstrap loader smoke test', 'npm', ['run', 'smoke:bootstrap-loader']); run('Run PHP lint', 'npm', ['run', 'lint:php']); run('Run ACF Local JSON smoke test', 'npm', ['run', 'smoke:acf-json']); +run('Run asset manifest smoke test', 'npm', ['run', 'smoke:asset-manifest']); run('Run Twig attribute helper smoke test', 'npm', ['run', 'smoke:attributes']); run('Run child theme generator smoke test', 'npm', ['run', 'smoke:child-theme-generator']); run('Run component locator smoke test', 'npm', ['run', 'smoke:component-locator']); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 5f863a5..932df3f 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -338,6 +338,7 @@ function runStaticChecks() { 'includes/Runtime/Setup.php', 'includes/Runtime/TimberIntegration.php', 'includes/Runtime/Twig.php', + 'includes/Support/AssetManifest.php', 'includes/Support/AttributeBag.php', 'includes/Support/FileDiscovery.php', 'package.json', @@ -352,6 +353,7 @@ function runStaticChecks() { 'templates/single-password.twig', 'templates/single.twig', '.github/scripts/acf-local-json-smoke.php', + '.github/scripts/asset-manifest-smoke.php', '.github/scripts/attribute-helper-smoke.php', '.github/scripts/bootstrap-loader-smoke.php', '.github/scripts/child-theme-generator-smoke.php', @@ -395,6 +397,7 @@ function runStaticChecks() { ensure(rootPackage.scripts['pr:check'] === 'node .github/scripts/pr-validation.cjs', 'package.json should expose npm run pr:check.'); ensure(rootPackage.scripts['release:check'] === 'node .github/scripts/release-check.cjs', 'package.json should expose npm run release:check.'); ensure(rootPackage.scripts['smoke:acf-json'] === 'php .github/scripts/acf-local-json-smoke.php', 'package.json should expose npm run smoke:acf-json.'); + ensure(rootPackage.scripts['smoke:asset-manifest'] === 'php .github/scripts/asset-manifest-smoke.php', 'package.json should expose npm run smoke:asset-manifest.'); ensure(rootPackage.scripts['smoke:attributes'] === 'php .github/scripts/attribute-helper-smoke.php', 'package.json should expose npm run smoke:attributes.'); ensure(rootPackage.scripts['smoke:bootstrap-loader'] === 'php .github/scripts/bootstrap-loader-smoke.php', 'package.json should expose npm run smoke:bootstrap-loader.'); ensure(rootPackage.scripts['smoke:child-theme-generator'] === 'php .github/scripts/child-theme-generator-smoke.php', 'package.json should expose npm run smoke:child-theme-generator.'); @@ -575,15 +578,17 @@ function runStaticChecks() { const editorPolicy = readFile('includes/Editor/Policy.php'); const patterns = readFile('includes/Blocks/Patterns.php'); const locator = readFile('includes/Blocks/ComponentLocator.php'); + const assetManifest = readFile('includes/Support/AssetManifest.php'); const fileDiscovery = readFile('includes/Support/FileDiscovery.php'); const acfBlocks = readFile('includes/Blocks/AcfBlocks.php'); const nativeBlocks = readFile('includes/Blocks/NativeBlocks.php'); const acfJsonSmoke = readFile('.github/scripts/acf-local-json-smoke.php'); + const assetManifestSmoke = readFile('.github/scripts/asset-manifest-smoke.php'); const editorEnhancementsSmoke = readFile('.github/scripts/editor-enhancements-smoke.php'); const smoke = readFile('.github/scripts/theme-filters-smoke.php'); const editorPolicySmoke = readFile('.github/scripts/editor-policy-smoke.php'); const patternSmoke = readFile('.github/scripts/pattern-registry-smoke.php'); - const smokeText = [acfJsonSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); + const smokeText = [acfJsonSmoke, assetManifestSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); const expectedFilters = [ 'emulsify_theme_acf_json_enabled', 'emulsify_theme_acf_json_save_path', @@ -591,6 +596,8 @@ function runStaticChecks() { 'emulsify_theme_acf_json_remove_default_load_path', 'emulsify_theme_asset_directories', 'emulsify_theme_asset_files', + 'emulsify_theme_asset_manifest_path', + 'emulsify_theme_asset_manifest_data', 'emulsify_theme_editor_enhancements_config', 'emulsify_theme_editor_asset_directories', 'emulsify_theme_editor_asset_files', @@ -610,7 +617,7 @@ function runStaticChecks() { 'emulsify_theme_pattern_namespaces', 'emulsify_theme_block_support_overrides', ]; - const runtimeText = [acfJson, assets, twig, context, setup, editorEnhancements, editorPolicy, patterns, locator, acfBlocks, nativeBlocks].join('\n'); + const runtimeText = [acfJson, assets, twig, context, setup, editorEnhancements, editorPolicy, patterns, locator, assetManifest, acfBlocks, nativeBlocks].join('\n'); for (const filter of expectedFilters) { ensure(runtimeText.includes(filter), `${filter} should be registered in runtime PHP code.`); @@ -619,6 +626,7 @@ function runStaticChecks() { } ensure(acfJsonSmoke.includes('ACF Local JSON smoke checks passed'), 'ACF Local JSON smoke should have a clear success message.'); + ensure(assetManifestSmoke.includes('Asset manifest smoke checks passed'), 'Asset manifest smoke should have a clear success message.'); ensure(editorEnhancementsSmoke.includes('Editor enhancements smoke checks passed'), 'Editor enhancements smoke should have a clear success message.'); ensure(smoke.includes('Theme filter smoke checks passed'), 'Runtime filter smoke should have a clear success message.'); ensure(editorPolicySmoke.includes('Editor policy smoke checks passed'), 'Editor policy smoke should have a clear success message.'); @@ -630,14 +638,20 @@ function runStaticChecks() { ensure(fileDiscovery.includes('function directory_files'), 'File discovery helper should support shallow directory scans.'); ensure(fileDiscovery.includes('function relative_path'), 'File discovery helper should normalize POSIX relative paths.'); ensure(fileDiscovery.includes('function sort_by_priority_and_relative'), 'File discovery helper should provide stable priority sorting.'); + ensure(assetManifest.includes('dist/emulsify-assets.json'), 'Asset manifest helper should default to dist/emulsify-assets.json.'); + ensure(assetManifest.includes('emulsify_theme_asset_manifest_path') && assetManifest.includes('emulsify_theme_asset_manifest_data'), 'Asset manifest helper should expose path and parsed data filters.'); + ensure(assets.includes("'global'") && editorEnhancements.includes("'editor'") && assetManifest.includes("'components'") && assetManifest.includes("'blocks'"), 'Asset manifest support should cover global, editor, component, and block-specific sections.'); ensure(assets.includes('FileDiscovery::theme_roots') && assets.includes('FileDiscovery::file_records'), 'Runtime assets should use shared filesystem discovery helpers.'); + ensure(assets.includes('AssetManifest') && assets.includes('manifest_asset_files') && assets.includes("enqueue_scripts( 'emulsify-global', 'dist/global' )"), 'Runtime assets should support manifest-backed global and component assets.'); ensure(editorEnhancements.includes('FileDiscovery::theme_roots') && editorEnhancements.includes('FileDiscovery::file_records'), 'Editor assets should use shared filesystem discovery helpers.'); + ensure(editorEnhancements.includes('AssetManifest') && editorEnhancements.includes("asset_records( 'editor'"), 'Editor assets should support manifest-backed editor assets.'); ensure(locator.includes('FileDiscovery::theme_roots') && locator.includes('FileDiscovery::file_records'), 'Component locator should use shared filesystem discovery helpers.'); ensure(patterns.includes('FileDiscovery::theme_roots') && patterns.includes("FileDiscovery::file_records( $this->pattern_directories(), array( 'json' ), false )"), 'Pattern discovery should use shared helpers while staying shallow.'); ensure(twig.includes('FileDiscovery::normalize_roots'), 'Twig project component roots should use shared root normalization.'); ensure(smoke.includes('Asset discovery should keep child roots before parent fallback roots'), 'Runtime filter smoke should verify asset root priority.'); ensure(editorEnhancementsSmoke.includes('skip duplicate parent relative paths'), 'Editor enhancements smoke should verify editor asset duplicate handling.'); ensure(patternSmoke.includes('Child pattern JSON files should override parent files with the same basename'), 'Pattern smoke should verify child-first pattern discovery.'); + ensure(assetManifestSmoke.includes('No manifest should fall back') && assetManifestSmoke.includes('Invalid manifest should fall back') && assetManifestSmoke.includes('Child manifest should take priority'), 'Asset manifest smoke should cover fallback, invalid, and child-priority paths.'); return 'Parent runtime exposes documented filters with smoke coverage.'; }); @@ -883,6 +897,7 @@ function runStaticChecks() { ensure(prValidationScript.includes('composer') && prValidationScript.includes('install'), 'PR validation should install Composer dependencies for Twig smoke coverage.'); ensure(prValidationScript.includes('lint:php'), 'PR validation should run PHP lint.'); ensure(prValidationScript.includes('smoke:acf-json'), 'PR validation should run the ACF Local JSON smoke test.'); + ensure(prValidationScript.includes('smoke:asset-manifest'), 'PR validation should run the asset manifest smoke test.'); ensure(prValidationScript.includes('smoke:attributes'), 'PR validation should run the attribute helper smoke test.'); ensure(prValidationScript.includes('smoke:bootstrap-loader'), 'PR validation should run the Bootstrap loader smoke test.'); ensure(prValidationScript.indexOf('composer') < prValidationScript.indexOf('smoke:bootstrap-loader'), 'PR validation should install Composer dependencies before checking runtime autoloading.'); @@ -1018,6 +1033,7 @@ function runStaticChecks() { ensure(docs.editorPolicy.includes('auto_allow_pattern_blocks'), 'Editor policy doc should document pattern JSON auto-allow behavior.'); ensure(docs.editorPolicy.includes('emulsify_theme_block_support_overrides'), 'Editor policy doc should document block support overrides.'); ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); + ensure(docs.assets.includes('dist/emulsify-assets.json') && docs.assets.includes('emulsify_theme_asset_manifest_path'), 'Asset loading doc should document optional manifest loading.'); ensure(docs.coreBlockTwig.includes('[Component recipes](component-recipes.md)'), 'Core block Twig rendering doc should link to component recipes.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); ensure(docs.cli.includes('Force replacement safety') && docs.cli.includes('Emulsify-generated child theme markers'), 'WP-CLI doc should document force replacement safety.'); diff --git a/.github/scripts/theme-filters-smoke.php b/.github/scripts/theme-filters-smoke.php index 43ebd76..58a308c 100644 --- a/.github/scripts/theme-filters-smoke.php +++ b/.github/scripts/theme-filters-smoke.php @@ -306,6 +306,7 @@ function emulsify_filter_smoke_remove( string $path ): void { emulsify_filter_smoke_write( $extra . '/native/block.json', '{"name":"emulsify/filter-native"}' ); require_once $repo_root . '/includes/Support/AttributeBag.php'; + require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Support/FileDiscovery.php'; require_once $repo_root . '/includes/Runtime/Assets.php'; require_once $repo_root . '/includes/Runtime/Context.php'; diff --git a/docs/asset-loading.md b/docs/asset-loading.md index d043c69..294fdec 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -2,26 +2,78 @@ The parent theme loads built assets from the active child theme first, then parent fallback assets. This keeps project builds in the child theme while allowing the parent to provide reusable runtime behavior. -## Asset directories +## Scanner behavior -The default built asset directories are: +When no usable manifest is present, Emulsify WordPress scans these built asset directories: - `dist/global` - `dist/components` -Global CSS is loaded from `dist/global`. Component CSS and JavaScript are loaded from `dist/components`. - -`dist/global/editor` is reserved for block editor enhancement assets. Those files are loaded by the editor enhancement service on `enqueue_block_editor_assets` and are skipped by the generic global style loader so editor-only CSS is not sent to normal frontend visitors. +Global frontend CSS and JavaScript are loaded from `dist/global`. Component CSS and JavaScript are loaded from `dist/components`. + +`dist/global/editor` is reserved for block editor enhancement assets. Those files are loaded by the editor enhancement service on `enqueue_block_editor_assets` and are skipped by the generic global loader so editor-only files are not sent to normal frontend visitors. + +Scanner asset versions use `filemtime()` so browsers receive updated files after a rebuild. If the child and parent themes both contain a built asset with the same relative path, the child asset wins. Assets are sorted by root priority and relative path before they are enqueued. + +## Manifest behavior + +Projects can optionally ship `dist/emulsify-assets.json` to avoid recursive discovery on every request. The manifest is child-first: a valid child manifest is used before a parent manifest. If no manifest exists, if the manifest is invalid JSON, or if a scope is not declared, the current scanner path remains the fallback for that scope. + +Manifest entry paths are relative to the manifest directory. For `dist/emulsify-assets.json`, use paths such as `global/app.css` or `components/card.js`. + +Example: + +```json +{ + "assets": { + "global": { + "css": [ + { + "path": "global/app.css", + "version": "app-css-123", + "dependencies": ["wp-block-library"] + } + ], + "js": [ + { + "path": "global/app.js", + "hash": "app-js-123", + "module": false, + "dependencies": ["jquery"] + } + ] + }, + "editor": { + "css": [{ "path": "global/editor/editor.css" }], + "js": [{ "path": "global/editor/editor.js" }] + }, + "components": { + "css": [{ "path": "components/card.css" }], + "js": [{ "path": "components/card.js", "module": true }] + }, + "blocks": { + "emulsify/card": { + "css": [{ "path": "components/card/block.css" }], + "js": [{ "path": "components/card/block.js", "module": true }] + } + } + } +} +``` -## Enqueue behavior +Manifest records may include: -The parent theme enqueues styles for both the frontend and block editor previews through WordPress block asset hooks. Frontend component JavaScript is loaded from built component output. +- `path`, or `file`, `src`, or `href` +- `relative` for handle generation when it should differ from `path` +- `version` or `hash` +- `dependencies` or `deps` +- `module` or `"type": "module"` for frontend scripts -Asset versions use `filemtime()` so browsers receive updated files after a rebuild. +Block-specific entries are accepted under `blocks` and are enqueued with component assets today. That keeps the manifest format ready for more precise block-aware loading later without making it a 2.0 requirement. -## Child-first priority +## Performance -If the child and parent themes both contain a built asset with the same relative path, the child asset wins. Assets are sorted by root priority and relative path before they are enqueued. +The scanner is simple and requires no build integration. A manifest is useful for larger projects because build tooling can write the exact asset list, hashes, and dependencies once, and PHP can avoid walking `dist/global` and `dist/components` on every request. ## Asset filters @@ -48,3 +100,31 @@ add_filter( ``` Use `emulsify_theme_asset_files` when a project needs to add, remove, or reorder individual files before enqueueing. Keep filtered records in the same shape as the parent records: `path`, `priority`, `relative`, `uri`, and `version`. + +Use `emulsify_theme_asset_manifest_path` to change the theme-relative manifest path: + +```php +add_filter( + 'emulsify_theme_asset_manifest_path', + function ( string $path ): string { + return 'dist/custom-assets.json'; + } +); +``` + +Use `emulsify_theme_asset_manifest_data` to adjust parsed manifest data before records are created: + +```php +add_filter( + 'emulsify_theme_asset_manifest_data', + function ( array $data, array $manifest ): array { + $data['assets']['global']['css'][0]['version'] = 'project-build'; + + return $data; + }, + 10, + 2 +); +``` + +Final frontend asset records still pass through `emulsify_theme_asset_files`. Editor asset records pass through `emulsify_theme_editor_asset_files`. diff --git a/includes/Editor/Enhancements.php b/includes/Editor/Enhancements.php index f63e34d..04c6b30 100644 --- a/includes/Editor/Enhancements.php +++ b/includes/Editor/Enhancements.php @@ -7,6 +7,7 @@ namespace Emulsify\Theme\Editor; +use Emulsify\Theme\Support\AssetManifest; use Emulsify\Theme\Support\FileDiscovery; /** @@ -39,7 +40,7 @@ public function editor_assets(): void { wp_enqueue_style( $this->handle( 'emulsify-editor', $asset['relative'] ), $asset['uri'], - array(), + $this->dependencies( $asset ), $asset['version'] ); } @@ -50,7 +51,7 @@ public function editor_assets(): void { wp_enqueue_script( $handle, $asset['uri'], - $this->editor_script_dependencies(), + $this->editor_script_dependencies( $asset ), $asset['version'], array( 'in_footer' => true, @@ -253,6 +254,46 @@ private function normalize_config( array $config ): array { * @return array Built asset records. */ private function asset_files( string $directory, array $extensions ): array { + $manifest_assets = $this->manifest_asset_files( $directory, $extensions ); + $assets = null === $manifest_assets ? $this->scanned_asset_files( $directory, $extensions ) : $manifest_assets; + + /** + * Filters built editor asset files before they are enqueued. + * + * Asset records should include path, relative, uri, and version keys. + * + * @param array $assets Built editor asset records. + * @param string $directory Theme-relative asset directory being scanned. + * @param array $extensions Allowed file extensions for the current enqueue pass. + */ + $filtered = apply_filters( 'emulsify_theme_editor_asset_files', $assets, $directory, $extensions ); + + return is_array( $filtered ) ? $filtered : $assets; + } + + /** + * Gets manifest-backed editor assets when available. + * + * @param string $directory Theme-relative asset directory. + * @param array $extensions Allowed file extensions. + * @return array|null Manifest-backed records, or null for scanner fallback. + */ + private function manifest_asset_files( string $directory, array $extensions ): ?array { + if ( 'dist/global/editor' !== trim( $directory, '/\\' ) ) { + return null; + } + + return ( new AssetManifest() )->asset_records( 'editor', $extensions ); + } + + /** + * Finds editor assets below a theme-relative directory through filesystem scans. + * + * @param string $directory Theme-relative asset directory. + * @param array $extensions Allowed file extensions. + * @return array Built asset records. + */ + private function scanned_asset_files( string $directory, array $extensions ): array { $assets = array(); $seen = array(); @@ -273,20 +314,7 @@ private function asset_files( string $directory, array $extensions ): array { ); } - $assets = FileDiscovery::sort_by_priority_and_relative( $assets ); - - /** - * Filters built editor asset files before they are enqueued. - * - * Asset records should include path, relative, uri, and version keys. - * - * @param array $assets Built editor asset records. - * @param string $directory Theme-relative asset directory being scanned. - * @param array $extensions Allowed file extensions for the current enqueue pass. - */ - $filtered = apply_filters( 'emulsify_theme_editor_asset_files', $assets, $directory, $extensions ); - - return is_array( $filtered ) ? $filtered : $assets; + return FileDiscovery::sort_by_priority_and_relative( $assets ); } /** @@ -325,10 +353,11 @@ private function asset_roots( string $directory ): array { /** * Gets WordPress script dependencies for editor modules. * + * @param array $asset Asset record. * @return array Script handles. */ - private function editor_script_dependencies(): array { - return array( + private function editor_script_dependencies( array $asset = array() ): array { + $dependencies = array( 'wp-block-editor', 'wp-blocks', 'wp-components', @@ -340,6 +369,15 @@ private function editor_script_dependencies(): array { 'wp-i18n', 'wp-notices', ); + + return array_values( + array_unique( + array_merge( + $dependencies, + $this->dependencies( $asset ) + ) + ) + ); } /** @@ -452,6 +490,16 @@ private function handle( string $prefix, string $relative ): string { return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); } + /** + * Gets dependency handles from an asset record. + * + * @param array $asset Asset record. + * @return array Dependency handles. + */ + private function dependencies( array $asset ): array { + return isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(); + } + /** * Gets a filemtime-based asset version. * diff --git a/includes/Runtime/Assets.php b/includes/Runtime/Assets.php index 9de0dc6..36d6c77 100644 --- a/includes/Runtime/Assets.php +++ b/includes/Runtime/Assets.php @@ -7,6 +7,7 @@ namespace Emulsify\Theme\Runtime; +use Emulsify\Theme\Support\AssetManifest; use Emulsify\Theme\Support\FileDiscovery; /** @@ -40,6 +41,7 @@ public function styles(): void { * @return void */ public function frontend_scripts(): void { + $this->enqueue_scripts( 'emulsify-global', 'dist/global' ); $this->enqueue_scripts( 'emulsify-component', 'dist/components' ); } @@ -57,7 +59,7 @@ private function enqueue_styles( string $prefix, string $directory ): void { wp_enqueue_style( $handle, $asset['uri'], - array(), + $this->dependencies( $asset ), $asset['version'] ); } @@ -74,11 +76,11 @@ private function enqueue_scripts( string $prefix, string $directory ): void { foreach ( $this->asset_files( $directory, array( 'js' ) ) as $asset ) { $handle = $this->handle( $prefix, $asset['relative'] ); - if ( function_exists( 'wp_enqueue_script_module' ) ) { + if ( $this->is_module_script( $asset ) && function_exists( 'wp_enqueue_script_module' ) ) { wp_enqueue_script_module( $handle, $asset['uri'], - array(), + $this->dependencies( $asset ), $asset['version'] ); continue; @@ -87,7 +89,7 @@ private function enqueue_scripts( string $prefix, string $directory ): void { wp_enqueue_script( $handle, $asset['uri'], - array(), + $this->dependencies( $asset ), $asset['version'], array( 'in_footer' => true, @@ -104,6 +106,69 @@ private function enqueue_scripts( string $prefix, string $directory ): void { * @return array Built asset records. */ private function asset_files( string $directory, array $extensions ): array { + $manifest_assets = $this->manifest_asset_files( $directory, $extensions ); + $assets = null === $manifest_assets ? $this->scanned_asset_files( $directory, $extensions ) : $manifest_assets; + + /** + * Filters built asset files before they are enqueued. + * + * Child themes and project plugins can add, remove, or reorder records. + * Asset records should include path, relative, uri, and version keys. + * + * @param array $assets Built asset records. + * @param string $directory Theme-relative asset directory being scanned. + * @param array $extensions Allowed file extensions for the current enqueue pass. + */ + $filtered = apply_filters( 'emulsify_theme_asset_files', $assets, $directory, $extensions ); + + return is_array( $filtered ) ? $filtered : $assets; + } + + /** + * Gets manifest-backed assets when a manifest declares the current scope. + * + * @param string $directory Theme-relative asset directory. + * @param array $extensions Allowed file extensions. + * @return array|null Manifest-backed records, or null for scanner fallback. + */ + private function manifest_asset_files( string $directory, array $extensions ): ?array { + $scope = $this->manifest_scope( $directory ); + + if ( null === $scope ) { + return null; + } + + return ( new AssetManifest() )->asset_records( $scope, $extensions ); + } + + /** + * Maps a scanned asset directory to a manifest scope. + * + * @param string $directory Theme-relative asset directory. + * @return string|null Manifest scope, or null when unsupported. + */ + private function manifest_scope( string $directory ): ?string { + $directory = trim( $directory, '/\\' ); + + if ( 'dist/global' === $directory ) { + return 'global'; + } + + if ( 'dist/components' === $directory ) { + return 'components'; + } + + return null; + } + + /** + * Finds built assets below a theme-relative directory through filesystem scans. + * + * @param string $directory Theme-relative asset directory. + * @param array $extensions Allowed file extensions. + * @return array Built asset records. + */ + private function scanned_asset_files( string $directory, array $extensions ): array { $assets = array(); $seen = array(); @@ -133,21 +198,7 @@ private function asset_files( string $directory, array $extensions ): array { ); } - $assets = FileDiscovery::sort_by_priority_and_relative( $assets ); - - /** - * Filters built asset files before they are enqueued. - * - * Child themes and project plugins can add, remove, or reorder records. - * Asset records should include path, relative, uri, and version keys. - * - * @param array $assets Built asset records. - * @param string $directory Theme-relative asset directory being scanned. - * @param array $extensions Allowed file extensions for the current enqueue pass. - */ - $filtered = apply_filters( 'emulsify_theme_asset_files', $assets, $directory, $extensions ); - - return is_array( $filtered ) ? $filtered : $assets; + return FileDiscovery::sort_by_priority_and_relative( $assets ); } /** @@ -197,6 +248,26 @@ private function handle( string $prefix, string $relative ): string { return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); } + /** + * Gets dependency handles from an asset record. + * + * @param array $asset Asset record. + * @return array Dependency handles. + */ + private function dependencies( array $asset ): array { + return isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(); + } + + /** + * Checks whether a script should be enqueued as a script module. + * + * @param array $asset Asset record. + * @return bool TRUE when script module enqueueing should be used. + */ + private function is_module_script( array $asset ): bool { + return ! array_key_exists( 'module', $asset ) || null === $asset['module'] ? true : (bool) $asset['module']; + } + /** * Checks whether a built asset is reserved for editor-only loading. * diff --git a/includes/Support/AssetManifest.php b/includes/Support/AssetManifest.php new file mode 100644 index 0000000..029b9e7 --- /dev/null +++ b/includes/Support/AssetManifest.php @@ -0,0 +1,423 @@ +manifest(); + + if ( null === $manifest ) { + return null; + } + + $assets = isset( $manifest['data']['assets'] ) && is_array( $manifest['data']['assets'] ) + ? $manifest['data']['assets'] + : $manifest['data']; + $records = $this->scope_records( $scope, $extensions, $assets, $manifest ); + + if ( null === $records ) { + return null; + } + + return FileDiscovery::sort_by_priority_and_relative( $records ); + } + + /** + * Gets records for a manifest asset scope. + * + * @param string $scope Asset scope. + * @param array $extensions Allowed file extensions. + * @param array $assets Manifest asset data. + * @param array $manifest Active manifest record. + * @return array|null Asset records, or null when the scope is undeclared. + */ + private function scope_records( string $scope, array $extensions, array $assets, array $manifest ): ?array { + if ( 'components' === $scope ) { + $declared = false; + $records = array(); + + if ( array_key_exists( 'components', $assets ) ) { + $declared = true; + $records = array_merge( $records, $this->section_records( $assets['components'], $extensions, $manifest ) ); + } + + if ( array_key_exists( 'blocks', $assets ) && is_array( $assets['blocks'] ) ) { + $declared = true; + + foreach ( $assets['blocks'] as $block_name => $section ) { + $records = array_merge( + $records, + $this->section_records( + $section, + $extensions, + $manifest, + is_scalar( $block_name ) ? (string) $block_name : '' + ) + ); + } + } + + return $declared ? $records : null; + } + + if ( ! array_key_exists( $scope, $assets ) ) { + return null; + } + + return $this->section_records( $assets[ $scope ], $extensions, $manifest ); + } + + /** + * Gets normalized records from a manifest section. + * + * @param mixed $section Manifest section. + * @param array $extensions Allowed file extensions. + * @param array $manifest Active manifest record. + * @param string $block_name Optional block name for block-specific entries. + * @return array Asset records. + */ + private function section_records( $section, array $extensions, array $manifest, string $block_name = '' ): array { + if ( ! is_array( $section ) ) { + return array(); + } + + $records = array(); + + foreach ( $extensions as $extension ) { + if ( ! is_scalar( $extension ) ) { + continue; + } + + $key = strtolower( ltrim( trim( (string) $extension ), '.' ) ); + + if ( '' === $key || empty( $section[ $key ] ) || ! is_array( $section[ $key ] ) ) { + continue; + } + + foreach ( $section[ $key ] as $entry ) { + $record = $this->asset_record( $entry, $key, $manifest, $block_name ); + + if ( null !== $record ) { + $records[] = $record; + } + } + } + + return $records; + } + + /** + * Normalizes a manifest asset entry. + * + * @param mixed $entry Manifest asset entry. + * @param string $extension Expected file extension. + * @param array $manifest Active manifest record. + * @param string $block_name Optional block name for block-specific entries. + * @return array|null Asset record, or null when invalid. + */ + private function asset_record( $entry, string $extension, array $manifest, string $block_name = '' ): ?array { + $data = is_array( $entry ) ? $entry : array( 'path' => $entry ); + $relative = $this->entry_path( $data ); + + if ( '' === $relative || strtolower( pathinfo( $relative, PATHINFO_EXTENSION ) ) !== $extension ) { + return null; + } + + $path = $manifest['base_path'] . '/' . $relative; + + if ( ! is_readable( $path ) ) { + return null; + } + + $record = array( + 'path' => $path, + 'priority' => $manifest['priority'], + 'relative' => $this->entry_relative( $data, $relative ), + 'uri' => $manifest['base_uri'] . '/' . $relative, + 'version' => $this->entry_version( $data, $path ), + 'dependencies' => $this->entry_dependencies( $data ), + 'module' => $this->entry_module( $data ), + 'source' => $manifest['source'], + 'manifest_path' => $manifest['path'], + ); + + if ( '' !== $block_name ) { + $record['block'] = $block_name; + } + + return $record; + } + + /** + * Gets the first readable, valid child-first manifest. + * + * @return array|null Active manifest record, or null when unavailable. + */ + private function manifest(): ?array { + foreach ( $this->manifest_candidates() as $candidate ) { + if ( ! is_readable( $candidate['path'] ) ) { + continue; + } + + $contents = file_get_contents( $candidate['path'] ); + + if ( ! is_string( $contents ) ) { + return null; + } + + $data = json_decode( $contents, true ); + + if ( ! is_array( $data ) || JSON_ERROR_NONE !== json_last_error() ) { + return null; + } + + /** + * Filters parsed asset manifest data before records are created. + * + * Return a non-array value to ignore the manifest and fall back to + * the recursive scanner for the current request. + * + * @param array $data Parsed manifest data. + * @param array $candidate Manifest file candidate. + */ + $filtered = apply_filters( 'emulsify_theme_asset_manifest_data', $data, $candidate ); + + if ( ! is_array( $filtered ) ) { + return null; + } + + $candidate['data'] = $filtered; + + return $candidate; + } + + return null; + } + + /** + * Gets child-first manifest candidates. + * + * @return array Manifest file candidates. + */ + private function manifest_candidates(): array { + $relative_path = self::DEFAULT_PATH; + + /** + * Filters the theme-relative asset manifest path. + * + * Return an empty value to disable manifest loading and always use the + * recursive scanner fallback. + * + * @param string $relative_path Theme-relative manifest path. + */ + $filtered = apply_filters( 'emulsify_theme_asset_manifest_path', $relative_path ); + + if ( ! is_scalar( $filtered ) ) { + return array(); + } + + $relative_path = $this->normalize_relative_path( (string) $filtered ); + + if ( '' === $relative_path ) { + return array(); + } + + $candidates = array(); + + if ( function_exists( 'get_stylesheet_directory' ) && function_exists( 'get_stylesheet_directory_uri' ) ) { + $candidates[] = $this->candidate( + get_stylesheet_directory(), + get_stylesheet_directory_uri(), + $relative_path, + 0, + 'child' + ); + } + + if ( function_exists( 'get_template_directory' ) && function_exists( 'get_template_directory_uri' ) ) { + $candidates[] = $this->candidate( + get_template_directory(), + get_template_directory_uri(), + $relative_path, + 1, + 'parent' + ); + } + + return $candidates; + } + + /** + * Builds a manifest candidate record. + * + * @param string $theme_path Theme root path. + * @param string $theme_uri Theme root URI. + * @param string $relative_path Theme-relative manifest path. + * @param int $priority Root priority. + * @param string $source Root source. + * @return array Manifest candidate record. + */ + private function candidate( string $theme_path, string $theme_uri, string $relative_path, int $priority, string $source ): array { + $path = rtrim( $theme_path, '/\\' ) . '/' . $relative_path; + $uri = rtrim( $theme_uri, '/' ) . '/' . $relative_path; + + return array( + 'path' => $path, + 'base_path' => dirname( $path ), + 'base_uri' => rtrim( str_replace( '\\', '/', dirname( $uri ) ), '/' ), + 'priority' => $priority, + 'source' => $source, + ); + } + + /** + * Gets an asset entry path. + * + * @param array $entry Manifest asset entry. + * @return string Entry path relative to the manifest directory. + */ + private function entry_path( array $entry ): string { + foreach ( array( 'path', 'file', 'src', 'href' ) as $key ) { + if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) ) { + return $this->normalize_relative_path( (string) $entry[ $key ] ); + } + } + + return ''; + } + + /** + * Gets the record-relative path used for handle generation. + * + * @param array $entry Manifest asset entry. + * @param string $fallback Fallback relative path. + * @return string Relative path. + */ + private function entry_relative( array $entry, string $fallback ): string { + if ( isset( $entry['relative'] ) && is_scalar( $entry['relative'] ) ) { + $relative = $this->normalize_relative_path( (string) $entry['relative'] ); + + if ( '' !== $relative ) { + return $relative; + } + } + + return $fallback; + } + + /** + * Gets the asset version from explicit metadata or filemtime. + * + * @param array $entry Manifest asset entry. + * @param string $path Absolute asset path. + * @return string|null Asset version. + */ + private function entry_version( array $entry, string $path ): ?string { + foreach ( array( 'version', 'hash' ) as $key ) { + if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) && '' !== trim( (string) $entry[ $key ] ) ) { + return (string) $entry[ $key ]; + } + } + + $modified = filemtime( $path ); + + return false === $modified ? null : (string) $modified; + } + + /** + * Gets normalized dependency handles. + * + * @param array $entry Manifest asset entry. + * @return array Dependency handles. + */ + private function entry_dependencies( array $entry ): array { + $dependencies = $entry['dependencies'] ?? ( $entry['deps'] ?? array() ); + + if ( ! is_array( $dependencies ) ) { + return array(); + } + + return array_values( + array_filter( + array_map( + static function ( $dependency ) { + return is_scalar( $dependency ) ? trim( (string) $dependency ) : null; + }, + $dependencies + ), + static function ( $dependency ): bool { + return is_string( $dependency ) && '' !== $dependency; + } + ) + ); + } + + /** + * Gets an optional script module flag. + * + * @param array $entry Manifest asset entry. + * @return bool|null Module flag, or null to use service default. + */ + private function entry_module( array $entry ): ?bool { + if ( array_key_exists( 'module', $entry ) ) { + return ! empty( $entry['module'] ); + } + + if ( array_key_exists( 'type', $entry ) && is_scalar( $entry['type'] ) ) { + return 'module' === strtolower( trim( (string) $entry['type'] ) ); + } + + return null; + } + + /** + * Normalizes safe relative paths. + * + * @param string $path Relative path. + * @return string Normalized relative path, or empty string when invalid. + */ + private function normalize_relative_path( string $path ): string { + $path = trim( str_replace( '\\', '/', $path ) ); + + if ( + '' === $path + || false !== strpos( $path, "\0" ) + || 0 === strpos( $path, '/' ) + || preg_match( '#(^|/)\.\.(/|$)#', $path ) + ) { + return ''; + } + + while ( 0 === strpos( $path, './' ) ) { + $path = substr( $path, 2 ); + } + + return $path; + } +} diff --git a/package.json b/package.json index f4b0ee6..df1bb75 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "pr:check": "node .github/scripts/pr-validation.cjs", "release:check": "node .github/scripts/release-check.cjs", "smoke:acf-json": "php .github/scripts/acf-local-json-smoke.php", + "smoke:asset-manifest": "php .github/scripts/asset-manifest-smoke.php", "smoke:attributes": "php .github/scripts/attribute-helper-smoke.php", "smoke:bootstrap-loader": "php .github/scripts/bootstrap-loader-smoke.php", "smoke:child-theme-generator": "php .github/scripts/child-theme-generator-smoke.php", From c57ef897e20f90c944fea80f47a5b35ad70a70e9 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 14:45:16 -0500 Subject: [PATCH 42/55] feat: support block scoped assets --- .github/scripts/asset-manifest-smoke.php | 4 +- .github/scripts/block-scoped-assets-smoke.php | 493 +++++++++++++++ .github/scripts/component-locator-smoke.php | 1 + .github/scripts/pr-validation.cjs | 1 + .github/scripts/release-check.cjs | 15 +- docs/acf-twig-blocks.md | 42 ++ docs/asset-loading.md | 6 +- docs/native-gutenberg-blocks.md | 6 + includes/Blocks/AcfBlocks.php | 576 ++++++++++++++++++ includes/Blocks/ComponentLocator.php | 9 +- includes/Runtime/Assets.php | 180 ++++++ includes/Support/AssetManifest.php | 307 +++++++++- package.json | 1 + 13 files changed, 1615 insertions(+), 26 deletions(-) create mode 100644 .github/scripts/block-scoped-assets-smoke.php diff --git a/.github/scripts/asset-manifest-smoke.php b/.github/scripts/asset-manifest-smoke.php index e279aa4..a3c1ba9 100644 --- a/.github/scripts/asset-manifest-smoke.php +++ b/.github/scripts/asset-manifest-smoke.php @@ -327,8 +327,8 @@ function emulsify_asset_manifest_smoke_remove( string $path ): void { ); emulsify_asset_manifest_smoke_assert( isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-component-card'] ) - && isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-component-blocks-hero'] ), - 'Valid manifest should enqueue component and block-specific CSS records.' + && ! isset( $GLOBALS['emulsify_asset_manifest_smoke_styles']['emulsify-component-blocks-hero'] ), + 'Valid manifest should enqueue broad component CSS without auto-loading block-specific CSS records.' ); emulsify_asset_manifest_smoke_assert( isset( $GLOBALS['emulsify_asset_manifest_smoke_scripts']['emulsify-global-app'] ) diff --git a/.github/scripts/block-scoped-assets-smoke.php b/.github/scripts/block-scoped-assets-smoke.php new file mode 100644 index 0000000..9f98ca5 --- /dev/null +++ b/.github/scripts/block-scoped-assets-smoke.php @@ -0,0 +1,493 @@ + $accepted_args, + 'callback' => $callback, + ); + + ksort( $GLOBALS['emulsify_block_assets_smoke_hooks'][ $hook ] ); + + return true; + } +} + +if ( ! function_exists( 'apply_filters' ) ) { + function apply_filters( string $hook, $value, ...$arguments ) { + if ( empty( $GLOBALS['emulsify_block_assets_smoke_hooks'][ $hook ] ) ) { + return $value; + } + + foreach ( $GLOBALS['emulsify_block_assets_smoke_hooks'][ $hook ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + $value = call_user_func_array( + $callback['callback'], + array_slice( + array_merge( array( $value ), $arguments ), + 0, + $callback['accepted_args'] + ) + ); + } + } + + return $value; + } +} + +if ( ! function_exists( 'get_stylesheet_directory' ) ) { + function get_stylesheet_directory(): string { + return $GLOBALS['emulsify_block_assets_smoke_child']; + } +} + +if ( ! function_exists( 'get_template_directory' ) ) { + function get_template_directory(): string { + return $GLOBALS['emulsify_block_assets_smoke_parent']; + } +} + +if ( ! function_exists( 'get_stylesheet_directory_uri' ) ) { + function get_stylesheet_directory_uri(): string { + return 'https://example.test/child'; + } +} + +if ( ! function_exists( 'get_template_directory_uri' ) ) { + function get_template_directory_uri(): string { + return 'https://example.test/parent'; + } +} + +if ( ! function_exists( 'sanitize_title' ) ) { + function sanitize_title( string $title ): string { + return trim( preg_replace( '/[^a-z0-9]+/', '-', strtolower( $title ) ), '-' ); + } +} + +if ( ! function_exists( 'sanitize_key' ) ) { + function sanitize_key( string $key ): string { + return trim( preg_replace( '/[^a-z0-9_-]+/', '-', strtolower( $key ) ), '-' ); + } +} + +if ( ! function_exists( 'is_admin' ) ) { + function is_admin(): bool { + return ! empty( $GLOBALS['emulsify_block_assets_smoke_is_admin'] ); + } +} + +if ( ! function_exists( 'acf_register_block_type' ) ) { + function acf_register_block_type( array $args ) { + $GLOBALS['emulsify_block_assets_smoke_acf_blocks'][ $args['name'] ] = $args; + + return $args; + } +} + +if ( ! function_exists( 'acf_get_block_type' ) ) { + function acf_get_block_type( string $name ) { + return null; + } +} + +if ( ! function_exists( 'register_block_type' ) ) { + function register_block_type( string $path ) { + $GLOBALS['emulsify_block_assets_smoke_native_blocks'][] = $path; + + return $path; + } +} + +if ( ! function_exists( 'wp_enqueue_style' ) ) { + function wp_enqueue_style( string $handle, string $src, array $deps = array(), $ver = false ): void { + $GLOBALS['emulsify_block_assets_smoke_styles'][ $handle ] = compact( 'src', 'deps', 'ver' ); + } +} + +if ( ! function_exists( 'wp_enqueue_script' ) ) { + function wp_enqueue_script( string $handle, string $src, array $deps = array(), $ver = false, $args = array() ): void { + $GLOBALS['emulsify_block_assets_smoke_scripts'][ $handle ] = compact( 'src', 'deps', 'ver', 'args' ); + } +} + +if ( ! function_exists( 'wp_enqueue_script_module' ) ) { + function wp_enqueue_script_module( string $id, string $src, array $deps = array(), $version = false ): void { + $GLOBALS['emulsify_block_assets_smoke_script_modules'][ $id ] = compact( 'src', 'deps', 'version' ); + } +} + +/** + * Fails the smoke script when an assertion is false. + * + * @param bool $condition Assertion condition. + * @param string $message Failure message. + * @return void + */ +function emulsify_block_assets_smoke_assert( bool $condition, string $message ): void { + if ( ! $condition ) { + throw new RuntimeException( $message ); + } +} + +/** + * Writes a fixture file. + * + * @param string $path File path. + * @param string $content File content. + * @return void + */ +function emulsify_block_assets_smoke_write( string $path, string $content ): void { + $directory = dirname( $path ); + + if ( ! is_dir( $directory ) ) { + mkdir( $directory, 0777, true ); + } + + file_put_contents( $path, $content ); +} + +/** + * Writes JSON fixture data. + * + * @param string $path File path. + * @param array $data JSON data. + * @return void + */ +function emulsify_block_assets_smoke_write_json( string $path, array $data ): void { + emulsify_block_assets_smoke_write( $path, (string) json_encode( $data ) ); +} + +/** + * Resets captured enqueue calls. + * + * @return void + */ +function emulsify_block_assets_smoke_reset_enqueues(): void { + $GLOBALS['emulsify_block_assets_smoke_styles'] = array(); + $GLOBALS['emulsify_block_assets_smoke_scripts'] = array(); + $GLOBALS['emulsify_block_assets_smoke_script_modules'] = array(); +} + +/** + * Recursively removes a path. + * + * @param string $path Path to remove. + * @return void + */ +function emulsify_block_assets_smoke_remove( string $path ): void { + if ( ! file_exists( $path ) ) { + return; + } + + if ( is_file( $path ) || is_link( $path ) ) { + unlink( $path ); + return; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $iterator as $item ) { + $item->isDir() && ! $item->isLink() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); + } + + rmdir( $path ); +} + +$repo_root = dirname( __DIR__, 2 ); +$work_root = sys_get_temp_dir() . '/emulsify-block-assets-' . uniqid( '', true ); +$child = $work_root . '/child-theme'; +$parent = $work_root . '/parent-theme'; + +$GLOBALS['emulsify_block_assets_smoke_child'] = $child; +$GLOBALS['emulsify_block_assets_smoke_parent'] = $parent; + +try { + emulsify_block_assets_smoke_write_json( + $child . '/dist/components/card/card.component.json', + array( + 'title' => 'Card', + 'assets' => array( + 'frontend' => array( + 'css' => array( + array( + 'path' => 'card.css', + 'version' => 'metadata-css', + ), + ), + 'js' => array( + array( + 'path' => 'card.js', + 'version' => 'metadata-js', + 'dependencies' => array( 'jquery' ), + 'module' => false, + ), + ), + ), + 'editor' => array( + 'css' => array( + array( + 'path' => 'editor.css', + 'version' => 'metadata-editor-css', + ), + ), + 'js' => array( + array( + 'path' => 'editor.js', + 'version' => 'metadata-editor-js', + 'dependencies' => array( 'wp-blocks' ), + ), + ), + ), + ), + ) + ); + emulsify_block_assets_smoke_write( $child . '/dist/components/card/card.twig', '
    Card
    ' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/card/card.css', '.card{}' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/card/card.js', 'window.card = true;' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/card/editor.css', '.editor{}' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/card/editor.js', 'window.editorCard = true;' ); + + emulsify_block_assets_smoke_write_json( + $child . '/dist/components/hero/hero.component.json', + array( + 'title' => 'Hero', + 'assets' => array( + 'frontend' => array( + 'css' => array( + array( + 'path' => 'metadata.css', + 'version' => 'metadata-hero-css', + ), + ), + ), + ), + ) + ); + emulsify_block_assets_smoke_write( $child . '/dist/components/hero/hero.twig', '
    Hero
    ' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/hero/metadata.css', '.metadata-hero{}' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/hero/manifest.css', '.manifest-hero{}' ); + + emulsify_block_assets_smoke_write_json( + $child . '/dist/components/plain/plain.component.json', + array( + 'title' => 'Plain', + ) + ); + emulsify_block_assets_smoke_write( $child . '/dist/components/plain/plain.twig', '

    Plain

    ' ); + + emulsify_block_assets_smoke_write_json( + $child . '/dist/components/native/block.json', + array( + 'name' => 'emulsify/native', + 'style' => 'file:./style.css', + 'viewScript' => 'file:./view.js', + 'editorScript' => 'file:./editor.js', + ) + ); + emulsify_block_assets_smoke_write( $child . '/dist/components/native/style.css', '.native{}' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/native/view.js', 'window.nativeView = true;' ); + emulsify_block_assets_smoke_write( $child . '/dist/components/native/editor.js', 'window.nativeEditor = true;' ); + + emulsify_block_assets_smoke_write_json( + $child . '/dist/emulsify-assets.json', + array( + 'assets' => array( + 'blocks' => array( + 'acf/emulsify-hero' => array( + 'frontend' => array( + 'css' => array( + array( + 'path' => 'components/hero/manifest.css', + 'relative' => 'hero-manifest.css', + 'version' => 'manifest-hero-css', + ), + ), + ), + ), + ), + ), + ) + ); + + require_once $repo_root . '/includes/Support/AssetManifest.php'; + require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Runtime/Assets.php'; + require_once $repo_root . '/includes/Blocks/ComponentLocator.php'; + require_once $repo_root . '/includes/Blocks/AcfBlocks.php'; + require_once $repo_root . '/includes/Blocks/NativeBlocks.php'; + + add_filter( + 'emulsify_theme_acf_block_asset_records', + static function ( array $records, array $component ): array { + if ( 'card' === $component['relative'] && isset( $records['frontend']['css'][0] ) ) { + $records['frontend']['css'][0]['version'] = 'filtered-card-css'; + } + + return $records; + }, + 10, + 2 + ); + + $acf_blocks = new Emulsify\Theme\Blocks\AcfBlocks( new Emulsify\Theme\Blocks\ComponentLocator() ); + $acf_blocks->register_blocks(); + + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-card']['enqueue_assets'] ) + && is_callable( $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-card']['enqueue_assets'] ), + 'ACF/Twig blocks with component metadata assets should register an enqueue_assets callback.' + ); + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-hero']['enqueue_assets'] ) + && is_callable( $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-hero']['enqueue_assets'] ), + 'ACF/Twig blocks with manifest assets should register an enqueue_assets callback.' + ); + emulsify_block_assets_smoke_assert( + empty( $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-plain']['enqueue_assets'] ), + 'ACF/Twig blocks without manifest or metadata assets should keep global component loading as the fallback.' + ); + + $GLOBALS['emulsify_block_assets_smoke_is_admin'] = false; + emulsify_block_assets_smoke_reset_enqueues(); + $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-card']['enqueue_assets'](); + + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-frontend-emulsify-card-card'] ) + && 'filtered-card-css' === $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-frontend-emulsify-card-card']['ver'], + 'ACF/Twig metadata frontend CSS should enqueue only when the block callback runs and should be filterable.' + ); + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_scripts']['emulsify-acf-frontend-emulsify-card-card'] ) + && in_array( 'jquery', $GLOBALS['emulsify_block_assets_smoke_scripts']['emulsify-acf-frontend-emulsify-card-card']['deps'], true ), + 'ACF/Twig metadata frontend JS should enqueue with dependencies.' + ); + emulsify_block_assets_smoke_assert( + empty( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-editor-emulsify-card-editor'] ), + 'ACF/Twig editor assets should not enqueue on frontend block renders.' + ); + + $GLOBALS['emulsify_block_assets_smoke_is_admin'] = true; + emulsify_block_assets_smoke_reset_enqueues(); + $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-card']['enqueue_assets'](); + + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-editor-emulsify-card-editor'] ) + && isset( $GLOBALS['emulsify_block_assets_smoke_script_modules']['emulsify-acf-editor-emulsify-card-editor'] ) + && in_array( 'wp-blocks', $GLOBALS['emulsify_block_assets_smoke_script_modules']['emulsify-acf-editor-emulsify-card-editor']['deps'], true ), + 'ACF/Twig metadata editor assets should enqueue in admin/editor contexts.' + ); + + $GLOBALS['emulsify_block_assets_smoke_is_admin'] = false; + emulsify_block_assets_smoke_reset_enqueues(); + $GLOBALS['emulsify_block_assets_smoke_acf_blocks']['emulsify-hero']['enqueue_assets'](); + + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-frontend-emulsify-hero-hero-manifest'] ) + && 'manifest-hero-css' === $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-frontend-emulsify-hero-hero-manifest']['ver'] + && ! isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-acf-frontend-emulsify-hero-metadata'] ), + 'Manifest block assets should take priority over component metadata assets.' + ); + + emulsify_block_assets_smoke_reset_enqueues(); + ( new Emulsify\Theme\Blocks\NativeBlocks( new Emulsify\Theme\Blocks\ComponentLocator() ) )->register_blocks(); + + emulsify_block_assets_smoke_assert( + in_array( $child . '/dist/components/native', $GLOBALS['emulsify_block_assets_smoke_native_blocks'], true ), + 'Native blocks with block.json should be registered through register_block_type().' + ); + emulsify_block_assets_smoke_assert( + empty( $GLOBALS['emulsify_block_assets_smoke_styles'] ) + && empty( $GLOBALS['emulsify_block_assets_smoke_scripts'] ) + && empty( $GLOBALS['emulsify_block_assets_smoke_script_modules'] ), + 'Native block.json asset fields should be left for WordPress to enqueue.' + ); + + $GLOBALS['emulsify_block_assets_smoke_child'] = $work_root . '/scanner-child'; + $GLOBALS['emulsify_block_assets_smoke_parent'] = $work_root . '/scanner-parent'; + emulsify_block_assets_smoke_reset_enqueues(); + emulsify_block_assets_smoke_write_json( + $GLOBALS['emulsify_block_assets_smoke_child'] . '/dist/components/card/card.component.json', + array( + 'title' => 'Scanner Card', + 'assets' => array( + 'frontend' => array( + 'css' => array( array( 'path' => 'card.css' ) ), + ), + ), + ) + ); + emulsify_block_assets_smoke_write( $GLOBALS['emulsify_block_assets_smoke_child'] . '/dist/components/card/card.css', '.card{}' ); + emulsify_block_assets_smoke_write( $GLOBALS['emulsify_block_assets_smoke_child'] . '/dist/components/plain/plain.css', '.plain{}' ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + + emulsify_block_assets_smoke_assert( + ! isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-component-card-card'] ), + 'Global component scanning should skip assets declared as block-scoped component metadata.' + ); + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-component-plain-plain'] ), + 'Global component scanning should remain the fallback when no scoped metadata exists.' + ); + + $GLOBALS['emulsify_block_assets_smoke_child'] = $work_root . '/manifest-scanner-child'; + $GLOBALS['emulsify_block_assets_smoke_parent'] = $work_root . '/manifest-scanner-parent'; + emulsify_block_assets_smoke_reset_enqueues(); + emulsify_block_assets_smoke_write_json( + $GLOBALS['emulsify_block_assets_smoke_child'] . '/dist/emulsify-assets.json', + array( + 'assets' => array( + 'blocks' => array( + 'acf/emulsify-card' => array( + 'frontend' => array( + 'css' => array( array( 'path' => 'components/card/card.css' ) ), + ), + ), + ), + ), + ) + ); + emulsify_block_assets_smoke_write( $GLOBALS['emulsify_block_assets_smoke_child'] . '/dist/components/card/card.css', '.card{}' ); + emulsify_block_assets_smoke_write( $GLOBALS['emulsify_block_assets_smoke_child'] . '/dist/components/plain/plain.css', '.plain{}' ); + + ( new Emulsify\Theme\Runtime\Assets() )->styles(); + + emulsify_block_assets_smoke_assert( + ! isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-component-card-card'] ), + 'Block-scoped manifest assets should not be loaded by the global component scanner.' + ); + emulsify_block_assets_smoke_assert( + isset( $GLOBALS['emulsify_block_assets_smoke_styles']['emulsify-component-plain-plain'] ), + 'Block-only manifests should still allow scanner fallback for undeclared component files.' + ); + + echo "Block scoped asset smoke checks passed.\n"; +} catch ( Throwable $throwable ) { + fwrite( STDERR, $throwable->getMessage() . "\n" ); + exit( 1 ); +} finally { + emulsify_block_assets_smoke_remove( $work_root ); +} diff --git a/.github/scripts/component-locator-smoke.php b/.github/scripts/component-locator-smoke.php index 15d9a57..4f588f3 100644 --- a/.github/scripts/component-locator-smoke.php +++ b/.github/scripts/component-locator-smoke.php @@ -261,6 +261,7 @@ static function ( array $args ): string { emulsify_locator_smoke_write( $child . '/dist/components/acf-name-b/acf-name-b.component.json', '{"name":"emulsify/shared-acf","title":"Shared B"}' ); emulsify_locator_smoke_write( $child . '/dist/components/acf-name-b/acf-name-b.twig', '
    Shared B
    ' ); + require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Blocks/AcfBlocks.php'; $acf_blocks = new Emulsify\Theme\Blocks\AcfBlocks( new Emulsify\Theme\Blocks\ComponentLocator() ); diff --git a/.github/scripts/pr-validation.cjs b/.github/scripts/pr-validation.cjs index 684152b..9a7d697 100644 --- a/.github/scripts/pr-validation.cjs +++ b/.github/scripts/pr-validation.cjs @@ -35,6 +35,7 @@ run('Run PHP lint', 'npm', ['run', 'lint:php']); run('Run ACF Local JSON smoke test', 'npm', ['run', 'smoke:acf-json']); run('Run asset manifest smoke test', 'npm', ['run', 'smoke:asset-manifest']); run('Run Twig attribute helper smoke test', 'npm', ['run', 'smoke:attributes']); +run('Run block scoped asset smoke test', 'npm', ['run', 'smoke:block-assets']); run('Run child theme generator smoke test', 'npm', ['run', 'smoke:child-theme-generator']); run('Run component locator smoke test', 'npm', ['run', 'smoke:component-locator']); run('Run core block Twig renderer smoke test', 'npm', ['run', 'smoke:core-block-twig']); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 932df3f..d50c8ed 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -355,6 +355,7 @@ function runStaticChecks() { '.github/scripts/acf-local-json-smoke.php', '.github/scripts/asset-manifest-smoke.php', '.github/scripts/attribute-helper-smoke.php', + '.github/scripts/block-scoped-assets-smoke.php', '.github/scripts/bootstrap-loader-smoke.php', '.github/scripts/child-theme-generator-smoke.php', '.github/scripts/component-locator-smoke.php', @@ -399,6 +400,7 @@ function runStaticChecks() { ensure(rootPackage.scripts['smoke:acf-json'] === 'php .github/scripts/acf-local-json-smoke.php', 'package.json should expose npm run smoke:acf-json.'); ensure(rootPackage.scripts['smoke:asset-manifest'] === 'php .github/scripts/asset-manifest-smoke.php', 'package.json should expose npm run smoke:asset-manifest.'); ensure(rootPackage.scripts['smoke:attributes'] === 'php .github/scripts/attribute-helper-smoke.php', 'package.json should expose npm run smoke:attributes.'); + ensure(rootPackage.scripts['smoke:block-assets'] === 'php .github/scripts/block-scoped-assets-smoke.php', 'package.json should expose npm run smoke:block-assets.'); ensure(rootPackage.scripts['smoke:bootstrap-loader'] === 'php .github/scripts/bootstrap-loader-smoke.php', 'package.json should expose npm run smoke:bootstrap-loader.'); ensure(rootPackage.scripts['smoke:child-theme-generator'] === 'php .github/scripts/child-theme-generator-smoke.php', 'package.json should expose npm run smoke:child-theme-generator.'); ensure(rootPackage.scripts['smoke:component-locator'] === 'php .github/scripts/component-locator-smoke.php', 'package.json should expose npm run smoke:component-locator.'); @@ -584,11 +586,12 @@ function runStaticChecks() { const nativeBlocks = readFile('includes/Blocks/NativeBlocks.php'); const acfJsonSmoke = readFile('.github/scripts/acf-local-json-smoke.php'); const assetManifestSmoke = readFile('.github/scripts/asset-manifest-smoke.php'); + const blockAssetSmoke = readFile('.github/scripts/block-scoped-assets-smoke.php'); const editorEnhancementsSmoke = readFile('.github/scripts/editor-enhancements-smoke.php'); const smoke = readFile('.github/scripts/theme-filters-smoke.php'); const editorPolicySmoke = readFile('.github/scripts/editor-policy-smoke.php'); const patternSmoke = readFile('.github/scripts/pattern-registry-smoke.php'); - const smokeText = [acfJsonSmoke, assetManifestSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); + const smokeText = [acfJsonSmoke, assetManifestSmoke, blockAssetSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); const expectedFilters = [ 'emulsify_theme_acf_json_enabled', 'emulsify_theme_acf_json_save_path', @@ -605,6 +608,7 @@ function runStaticChecks() { 'emulsify_theme_context', 'emulsify_theme_acf_block_metadata', 'emulsify_theme_acf_block_args', + 'emulsify_theme_acf_block_asset_records', 'emulsify_theme_native_block_directories', 'emulsify_theme_pattern_directories', 'emulsify_theme_pattern_data', @@ -627,6 +631,7 @@ function runStaticChecks() { ensure(acfJsonSmoke.includes('ACF Local JSON smoke checks passed'), 'ACF Local JSON smoke should have a clear success message.'); ensure(assetManifestSmoke.includes('Asset manifest smoke checks passed'), 'Asset manifest smoke should have a clear success message.'); + ensure(blockAssetSmoke.includes('Block scoped asset smoke checks passed'), 'Block scoped asset smoke should have a clear success message.'); ensure(editorEnhancementsSmoke.includes('Editor enhancements smoke checks passed'), 'Editor enhancements smoke should have a clear success message.'); ensure(smoke.includes('Theme filter smoke checks passed'), 'Runtime filter smoke should have a clear success message.'); ensure(editorPolicySmoke.includes('Editor policy smoke checks passed'), 'Editor policy smoke should have a clear success message.'); @@ -643,8 +648,11 @@ function runStaticChecks() { ensure(assets.includes("'global'") && editorEnhancements.includes("'editor'") && assetManifest.includes("'components'") && assetManifest.includes("'blocks'"), 'Asset manifest support should cover global, editor, component, and block-specific sections.'); ensure(assets.includes('FileDiscovery::theme_roots') && assets.includes('FileDiscovery::file_records'), 'Runtime assets should use shared filesystem discovery helpers.'); ensure(assets.includes('AssetManifest') && assets.includes('manifest_asset_files') && assets.includes("enqueue_scripts( 'emulsify-global', 'dist/global' )"), 'Runtime assets should support manifest-backed global and component assets.'); + ensure(assets.includes('is_scoped_component_asset') && assets.includes('*.component.json'), 'Runtime assets should skip component files declared as block-scoped metadata.'); ensure(editorEnhancements.includes('FileDiscovery::theme_roots') && editorEnhancements.includes('FileDiscovery::file_records'), 'Editor assets should use shared filesystem discovery helpers.'); ensure(editorEnhancements.includes('AssetManifest') && editorEnhancements.includes("asset_records( 'editor'"), 'Editor assets should support manifest-backed editor assets.'); + ensure(acfBlocks.includes('scoped_asset_records') && acfBlocks.includes('enqueue_assets') && acfBlocks.includes('emulsify_theme_acf_block_asset_records'), 'ACF/Twig blocks should support metadata-driven scoped assets.'); + ensure(nativeBlocks.includes("register_block_type( (string) $component['path'] )"), 'Native blocks should delegate block.json asset fields to WordPress register_block_type().'); ensure(locator.includes('FileDiscovery::theme_roots') && locator.includes('FileDiscovery::file_records'), 'Component locator should use shared filesystem discovery helpers.'); ensure(patterns.includes('FileDiscovery::theme_roots') && patterns.includes("FileDiscovery::file_records( $this->pattern_directories(), array( 'json' ), false )"), 'Pattern discovery should use shared helpers while staying shallow.'); ensure(twig.includes('FileDiscovery::normalize_roots'), 'Twig project component roots should use shared root normalization.'); @@ -652,6 +660,7 @@ function runStaticChecks() { ensure(editorEnhancementsSmoke.includes('skip duplicate parent relative paths'), 'Editor enhancements smoke should verify editor asset duplicate handling.'); ensure(patternSmoke.includes('Child pattern JSON files should override parent files with the same basename'), 'Pattern smoke should verify child-first pattern discovery.'); ensure(assetManifestSmoke.includes('No manifest should fall back') && assetManifestSmoke.includes('Invalid manifest should fall back') && assetManifestSmoke.includes('Child manifest should take priority'), 'Asset manifest smoke should cover fallback, invalid, and child-priority paths.'); + ensure(blockAssetSmoke.includes('Manifest block assets should take priority') && blockAssetSmoke.includes('Native block.json asset fields should be left for WordPress') && blockAssetSmoke.includes('Global component scanning should remain the fallback'), 'Block scoped asset smoke should cover manifest priority, native block.json delegation, and scanner fallback.'); return 'Parent runtime exposes documented filters with smoke coverage.'; }); @@ -899,6 +908,7 @@ function runStaticChecks() { ensure(prValidationScript.includes('smoke:acf-json'), 'PR validation should run the ACF Local JSON smoke test.'); ensure(prValidationScript.includes('smoke:asset-manifest'), 'PR validation should run the asset manifest smoke test.'); ensure(prValidationScript.includes('smoke:attributes'), 'PR validation should run the attribute helper smoke test.'); + ensure(prValidationScript.includes('smoke:block-assets'), 'PR validation should run the block scoped asset smoke test.'); ensure(prValidationScript.includes('smoke:bootstrap-loader'), 'PR validation should run the Bootstrap loader smoke test.'); ensure(prValidationScript.indexOf('composer') < prValidationScript.indexOf('smoke:bootstrap-loader'), 'PR validation should install Composer dependencies before checking runtime autoloading.'); ensure(prValidationScript.includes('smoke:child-theme-generator'), 'PR validation should run the child theme generator smoke test.'); @@ -1017,9 +1027,11 @@ function runStaticChecks() { ensure(docs.acfBlocks.includes('Whisk does not include an active ACF/Twig block example'), 'ACF/Twig blocks doc should explain that starter metadata is documentation-only.'); ensure(docs.acfBlocks.includes('[Component recipes](component-recipes.md)'), 'ACF/Twig blocks doc should link to component recipes.'); ensure(docs.acfBlocks.includes('emulsify_theme_acf_block_args'), 'ACF/Twig blocks doc should document the block args filter.'); + ensure(docs.acfBlocks.includes('Scoped assets') && docs.acfBlocks.includes('emulsify_theme_acf_block_asset_records'), 'ACF/Twig blocks doc should document scoped block assets.'); ensure(docs.nativeBlocks.includes('The starter does not include an active native block example'), 'Native blocks doc should avoid over-claiming a native example.'); ensure(docs.nativeBlocks.includes('[Component recipes](component-recipes.md)'), 'Native block doc should link to component recipes.'); ensure(docs.nativeBlocks.includes('emulsify_theme_native_block_directories'), 'Native blocks doc should document the native block directories filter.'); + ensure(docs.nativeBlocks.includes('style`, `script`, `viewScript`') && docs.nativeBlocks.includes('register_block_type()'), 'Native blocks doc should document WordPress-owned block.json asset loading.'); ensure(docs.blockPatterns.includes('patterns/*.json'), 'Block patterns doc should document JSON pattern discovery.'); ensure(docs.blockPatterns.includes('[Component recipes](component-recipes.md)'), 'Block patterns doc should link to component recipes.'); ensure(docs.blockPatterns.includes('emulsify_theme_pattern_directories'), 'Block patterns doc should document directory filtering.'); @@ -1034,6 +1046,7 @@ function runStaticChecks() { ensure(docs.editorPolicy.includes('emulsify_theme_block_support_overrides'), 'Editor policy doc should document block support overrides.'); ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); ensure(docs.assets.includes('dist/emulsify-assets.json') && docs.assets.includes('emulsify_theme_asset_manifest_path'), 'Asset loading doc should document optional manifest loading.'); + ensure(docs.assets.includes('block-scoped assets') && docs.assets.includes('emulsify_theme_acf_block_asset_records'), 'Asset loading doc should document block-scoped asset behavior.'); ensure(docs.coreBlockTwig.includes('[Component recipes](component-recipes.md)'), 'Core block Twig rendering doc should link to component recipes.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); ensure(docs.cli.includes('Force replacement safety') && docs.cli.includes('Emulsify-generated child theme markers'), 'WP-CLI doc should document force replacement safety.'); diff --git a/docs/acf-twig-blocks.md b/docs/acf-twig-blocks.md index a7b9e83..4812147 100644 --- a/docs/acf-twig-blocks.md +++ b/docs/acf-twig-blocks.md @@ -55,6 +55,48 @@ add_filter( Component metadata can be adjusted before defaults are merged with `emulsify_theme_acf_block_metadata`. +## Scoped assets + +ACF/Twig blocks can declare block-scoped frontend and editor assets. Prefer `dist/emulsify-assets.json` when build tooling can write one, because it keeps hashed filenames and dependencies in one place. If no matching manifest entry exists, the parent reads optional `assets` metadata from `*.component.json`. + +Component metadata paths are relative to the built component directory: + +```json +{ + "title": "Example Card", + "assets": { + "frontend": { + "css": [{ "path": "example-card.css", "version": "card-css-123" }], + "js": [{ "path": "example-card.js", "module": true }] + }, + "editor": { + "css": [{ "path": "example-card.editor.css" }], + "js": [{ "path": "example-card.editor.js", "dependencies": ["wp-blocks"] }] + } + } +} +``` + +When scoped assets are declared, they are enqueued from the ACF block `enqueue_assets` callback instead of being loaded globally for every page. Components without scoped metadata continue to use the global `dist/components` scanner fallback. + +Manifest block entries can target the final ACF block name: + +```json +{ + "assets": { + "blocks": { + "acf/emulsify-example-card": { + "frontend": { + "css": [{ "path": "components/example-card/example-card.css" }] + } + } + } + } +} +``` + +Scoped asset records can be adjusted before registration with `emulsify_theme_acf_block_asset_records`. + ## Duplicate handling The child theme is scanned before the parent theme. Duplicate component paths, duplicate component slugs, and duplicate final ACF block `name` values are skipped instead of being registered twice. diff --git a/docs/asset-loading.md b/docs/asset-loading.md index 294fdec..5cb8607 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -15,6 +15,8 @@ Global frontend CSS and JavaScript are loaded from `dist/global`. Component CSS Scanner asset versions use `filemtime()` so browsers receive updated files after a rebuild. If the child and parent themes both contain a built asset with the same relative path, the child asset wins. Assets are sorted by root priority and relative path before they are enqueued. +For ACF/Twig components, files declared in `*.component.json` `assets` metadata or keyed manifest block/component entries are treated as block-scoped assets and skipped by the global component scanner. Component files without scoped metadata still load through the global `dist/components` scanner. + ## Manifest behavior Projects can optionally ship `dist/emulsify-assets.json` to avoid recursive discovery on every request. The manifest is child-first: a valid child manifest is used before a parent manifest. If no manifest exists, if the manifest is invalid JSON, or if a scope is not declared, the current scanner path remains the fallback for that scope. @@ -69,7 +71,7 @@ Manifest records may include: - `dependencies` or `deps` - `module` or `"type": "module"` for frontend scripts -Block-specific entries are accepted under `blocks` and are enqueued with component assets today. That keeps the manifest format ready for more precise block-aware loading later without making it a 2.0 requirement. +Block-specific entries are accepted under `blocks` and are used by ACF/Twig block registration when a matching block is rendered. Broad `components.css` and `components.js` entries still load globally. Keyed `components` or `blocks` entries are not auto-loaded on pages that do not render those blocks. If a manifest only declares keyed block/component entries, undeclared component files still use the scanner fallback. ## Performance @@ -128,3 +130,5 @@ add_filter( ``` Final frontend asset records still pass through `emulsify_theme_asset_files`. Editor asset records pass through `emulsify_theme_editor_asset_files`. + +ACF/Twig block-scoped asset records pass through `emulsify_theme_acf_block_asset_records` before the block registration callback is attached. diff --git a/docs/native-gutenberg-blocks.md b/docs/native-gutenberg-blocks.md index e71e947..39a7445 100644 --- a/docs/native-gutenberg-blocks.md +++ b/docs/native-gutenberg-blocks.md @@ -10,6 +10,12 @@ The starter does not include an active native block example. Native blocks usual See [Component recipes](component-recipes.md) for a short `block.json` example and guidance on when to choose native blocks. +## Block assets + +Native blocks should use standard `block.json` asset fields such as `style`, `script`, `viewScript`, `editorStyle`, `editorScript`, and `viewScriptModule`. The parent theme passes the block directory to `register_block_type()` and does not manually enqueue assets that WordPress can register from `block.json`. + +Use `dist/emulsify-assets.json` or ACF/Twig component metadata only for ACF/Twig blocks. Native block asset loading should stay in native block metadata so WordPress can load assets only when the block is present. + ## When to use native blocks Use native blocks when the project needs block editor APIs such as: diff --git a/includes/Blocks/AcfBlocks.php b/includes/Blocks/AcfBlocks.php index 57c76ab..196732a 100644 --- a/includes/Blocks/AcfBlocks.php +++ b/includes/Blocks/AcfBlocks.php @@ -7,6 +7,9 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\AssetManifest; +use Emulsify\Theme\Support\FileDiscovery; + /** * ACF/Twig block integration. */ @@ -106,6 +109,7 @@ public function register_blocks(): void { $args['name'] ?? '', isset( $component['slug'] ) ? (string) $component['slug'] : 'block' ); + $args = $this->with_asset_records( $args, $component, $metadata ); $name = $this->block_name( $args ); if ( '' !== $name && isset( $seen_names[ $name ] ) ) { @@ -243,6 +247,578 @@ private function block_args( array $component, array $metadata ): array { return $args; } + /** + * Adds a scoped asset callback when a component declares block assets. + * + * @param array $args ACF block registration arguments. + * @param array $component Component record. + * @param array $metadata Component metadata. + * @return array ACF block registration arguments. + */ + private function with_asset_records( array $args, array $component, array $metadata ): array { + $asset_records = $this->block_asset_records( $component, $metadata, $args ); + + /** + * Filters scoped ACF/Twig block asset records before registration. + * + * Records are grouped by frontend/editor context and css/js type. Return + * an empty record set to leave the block without scoped asset enqueueing. + * + * @param array $asset_records Scoped asset records. + * @param array $component Component discovery record. + * @param array $metadata Component metadata. + * @param array $args ACF block registration arguments. + */ + $filtered = apply_filters( 'emulsify_theme_acf_block_asset_records', $asset_records, $component, $metadata, $args ); + + if ( is_array( $filtered ) ) { + $asset_records = $this->normalize_asset_context_records( $filtered ); + } + + if ( ! $this->has_asset_records( $asset_records ) ) { + return $args; + } + + $existing_callback = isset( $args['enqueue_assets'] ) && is_callable( $args['enqueue_assets'] ) ? $args['enqueue_assets'] : null; + $args['enqueue_assets'] = $this->enqueue_assets_callback( $asset_records, $existing_callback, $this->block_name( $args ) ); + + return $args; + } + + /** + * Gets scoped block asset records. + * + * @param array $component Component record. + * @param array $metadata Component metadata. + * @param array $args ACF block registration arguments. + * @return array Scoped asset records. + */ + private function block_asset_records( array $component, array $metadata, array $args ): array { + $manifest_records = ( new AssetManifest() )->scoped_asset_records( + $this->asset_identifiers( $component, $metadata, $args ) + ); + + if ( is_array( $manifest_records ) ) { + return $this->normalize_asset_context_records( $manifest_records ); + } + + $metadata_records = $this->metadata_asset_records( $component, $metadata ); + + return is_array( $metadata_records ) ? $metadata_records : $this->empty_asset_context_records(); + } + + /** + * Gets identifiers used to match manifest scoped assets. + * + * @param array $component Component record. + * @param array $metadata Component metadata. + * @param array $args ACF block registration arguments. + * @return array Asset identifiers. + */ + private function asset_identifiers( array $component, array $metadata, array $args ): array { + $identifiers = array( + $args['name'] ?? '', + 'acf/' . ( $args['name'] ?? '' ), + $component['relative'] ?? '', + $component['slug'] ?? '', + ); + + if ( isset( $metadata['name'] ) && is_scalar( $metadata['name'] ) ) { + $metadata_name = trim( (string) $metadata['name'] ); + $normalized = $this->normalize_block_name( $metadata_name, isset( $component['slug'] ) ? (string) $component['slug'] : 'block' ); + $identifiers[] = $metadata_name; + $identifiers[] = $normalized; + $identifiers[] = 'acf/' . $normalized; + } + + return array_values( + array_unique( + array_filter( + $identifiers, + static function ( $identifier ): bool { + return is_scalar( $identifier ) && '' !== trim( (string) $identifier ); + } + ) + ) + ); + } + + /** + * Gets component metadata asset records. + * + * @param array $component Component record. + * @param array $metadata Component metadata. + * @return array|null Scoped asset records, or null when undeclared. + */ + private function metadata_asset_records( array $component, array $metadata ): ?array { + if ( empty( $metadata['assets'] ) || ! is_array( $metadata['assets'] ) ) { + return null; + } + + $base_path = ! empty( $component['path'] ) && is_scalar( $component['path'] ) + ? rtrim( (string) $component['path'], '/\\' ) + : dirname( (string) ( $component['metadata_path'] ?? '' ) ); + $base_uri = $this->component_base_uri( $component, $base_path ); + + if ( '' === $base_uri || '' === $base_path ) { + return $this->empty_asset_context_records(); + } + + return $this->metadata_context_records( $metadata['assets'], $base_path, $base_uri, $component ); + } + + /** + * Gets frontend/editor records from component metadata. + * + * @param array $assets Component metadata assets. + * @param string $base_path Component base path. + * @param string $base_uri Component base URI. + * @param array $component Component record. + * @return array Scoped asset records. + */ + private function metadata_context_records( array $assets, string $base_path, string $base_uri, array $component ): array { + $records = $this->empty_asset_context_records(); + + if ( isset( $assets['frontend'] ) || isset( $assets['editor'] ) ) { + if ( isset( $assets['frontend'] ) && is_array( $assets['frontend'] ) ) { + $records['frontend'] = $this->metadata_type_records( $assets['frontend'], $base_path, $base_uri, $component ); + } + + if ( isset( $assets['editor'] ) && is_array( $assets['editor'] ) ) { + $records['editor'] = $this->metadata_type_records( $assets['editor'], $base_path, $base_uri, $component ); + } + + return $records; + } + + $records['frontend'] = $this->metadata_type_records( $assets, $base_path, $base_uri, $component ); + + return $records; + } + + /** + * Gets css/js records from component metadata. + * + * @param array $assets Metadata asset section. + * @param string $base_path Component base path. + * @param string $base_uri Component base URI. + * @param array $component Component record. + * @return array Asset records grouped by type. + */ + private function metadata_type_records( array $assets, string $base_path, string $base_uri, array $component ): array { + $records = array( + 'css' => array(), + 'js' => array(), + ); + + foreach ( array( 'css', 'js' ) as $type ) { + if ( empty( $assets[ $type ] ) || ! is_array( $assets[ $type ] ) ) { + continue; + } + + foreach ( $assets[ $type ] as $entry ) { + $record = $this->metadata_asset_record( $entry, $type, $base_path, $base_uri, $component ); + + if ( null !== $record ) { + $records[ $type ][] = $record; + } + } + } + + $records['css'] = FileDiscovery::sort_by_priority_and_relative( $records['css'] ); + $records['js'] = FileDiscovery::sort_by_priority_and_relative( $records['js'] ); + + return $records; + } + + /** + * Normalizes one component metadata asset record. + * + * @param mixed $entry Metadata asset entry. + * @param string $type Asset type. + * @param string $base_path Component base path. + * @param string $base_uri Component base URI. + * @param array $component Component record. + * @return array|null Asset record, or null when invalid. + */ + private function metadata_asset_record( $entry, string $type, string $base_path, string $base_uri, array $component ): ?array { + $data = is_array( $entry ) ? $entry : array( 'path' => $entry ); + $relative = $this->entry_path( $data ); + + if ( '' === $relative || strtolower( pathinfo( $relative, PATHINFO_EXTENSION ) ) !== $type ) { + return null; + } + + $path = $base_path . '/' . $relative; + + if ( ! is_readable( $path ) ) { + return null; + } + + return array( + 'path' => $path, + 'priority' => 0, + 'relative' => $this->entry_relative( $data, $relative ), + 'uri' => rtrim( $base_uri, '/' ) . '/' . $relative, + 'version' => $this->entry_version( $data, $path ), + 'dependencies' => $this->entry_dependencies( $data ), + 'module' => $this->entry_module( $data ), + 'source' => isset( $component['source'] ) ? (string) $component['source'] : '', + ); + } + + /** + * Builds a component base URI from discovery metadata. + * + * @param array $component Component record. + * @param string $base_path Component base path. + * @return string Component base URI, or empty string when unavailable. + */ + private function component_base_uri( array $component, string $base_path ): string { + if ( empty( $component['root_uri'] ) || empty( $component['root_path'] ) ) { + return ''; + } + + $relative = FileDiscovery::relative_path( (string) $component['root_path'], $base_path ); + + return rtrim( (string) $component['root_uri'], '/' ) . ( '' === $relative ? '' : '/' . $relative ); + } + + /** + * Builds the ACF enqueue callback for scoped block assets. + * + * @param array $asset_records Scoped asset records. + * @param callable|null $existing_callback Existing enqueue callback. + * @param string $block_name ACF block name. + * @return callable Enqueue callback. + */ + private function enqueue_assets_callback( array $asset_records, ?callable $existing_callback, string $block_name ): callable { + return function ( ...$callback_args ) use ( $asset_records, $existing_callback, $block_name ): void { + if ( is_callable( $existing_callback ) ) { + call_user_func_array( $existing_callback, $callback_args ); + } + + $this->enqueue_asset_records( $asset_records['frontend'] ?? array(), $block_name, 'frontend' ); + + if ( $this->is_editor_context() ) { + $this->enqueue_asset_records( $asset_records['editor'] ?? array(), $block_name, 'editor' ); + } + }; + } + + /** + * Enqueues a grouped asset record set. + * + * @param array $records Asset records grouped by css/js. + * @param string $block_name ACF block name. + * @param string $context Asset context. + * @return void + */ + private function enqueue_asset_records( array $records, string $block_name, string $context ): void { + foreach ( $records['css'] ?? array() as $record ) { + $this->enqueue_style_record( $record, $block_name, $context ); + } + + foreach ( $records['js'] ?? array() as $record ) { + $this->enqueue_script_record( $record, $block_name, $context ); + } + } + + /** + * Enqueues a style record. + * + * @param array $record Asset record. + * @param string $block_name ACF block name. + * @param string $context Asset context. + * @return void + */ + private function enqueue_style_record( array $record, string $block_name, string $context ): void { + if ( empty( $record['uri'] ) || ! function_exists( 'wp_enqueue_style' ) ) { + return; + } + + wp_enqueue_style( + $this->asset_handle( 'emulsify-acf-' . $context, $block_name, $record ), + (string) $record['uri'], + $this->asset_dependencies( $record ), + $record['version'] ?? null + ); + } + + /** + * Enqueues a script record. + * + * @param array $record Asset record. + * @param string $block_name ACF block name. + * @param string $context Asset context. + * @return void + */ + private function enqueue_script_record( array $record, string $block_name, string $context ): void { + if ( empty( $record['uri'] ) ) { + return; + } + + $handle = $this->asset_handle( 'emulsify-acf-' . $context, $block_name, $record ); + + if ( $this->is_module_script( $record ) && function_exists( 'wp_enqueue_script_module' ) ) { + wp_enqueue_script_module( + $handle, + (string) $record['uri'], + $this->asset_dependencies( $record ), + $record['version'] ?? null + ); + return; + } + + if ( ! function_exists( 'wp_enqueue_script' ) ) { + return; + } + + wp_enqueue_script( + $handle, + (string) $record['uri'], + $this->asset_dependencies( $record ), + $record['version'] ?? null, + array( + 'in_footer' => true, + ) + ); + } + + /** + * Builds a WordPress-safe scoped asset handle. + * + * @param string $prefix Handle prefix. + * @param string $block_name ACF block name. + * @param array $record Asset record. + * @return string Asset handle. + */ + private function asset_handle( string $prefix, string $block_name, array $record ): string { + if ( ! empty( $record['handle'] ) && is_scalar( $record['handle'] ) ) { + return sanitize_key( (string) $record['handle'] ); + } + + $relative = isset( $record['relative'] ) && is_scalar( $record['relative'] ) ? (string) $record['relative'] : basename( (string) ( $record['path'] ?? 'asset' ) ); + $name = preg_replace( '/\.(css|js)$/', '', $relative ); + $name = preg_replace( '/[^A-Za-z0-9_-]+/', '-', (string) $name ); + + return sanitize_key( $prefix . '-' . $block_name . '-' . trim( (string) $name, '-' ) ); + } + + /** + * Gets dependency handles from an asset record. + * + * @param array $record Asset record. + * @return array Dependency handles. + */ + private function asset_dependencies( array $record ): array { + return isset( $record['dependencies'] ) && is_array( $record['dependencies'] ) ? $record['dependencies'] : array(); + } + + /** + * Checks whether a script should be enqueued as a module. + * + * @param array $record Asset record. + * @return bool TRUE when the script is a module. + */ + private function is_module_script( array $record ): bool { + return ! array_key_exists( 'module', $record ) || null === $record['module'] ? true : (bool) $record['module']; + } + + /** + * Checks whether the current request is an editor/admin context. + * + * @return bool TRUE when editor/admin-only assets should load. + */ + private function is_editor_context(): bool { + return function_exists( 'is_admin' ) && is_admin(); + } + + /** + * Gets an empty scoped asset record set. + * + * @return array Empty records. + */ + private function empty_asset_context_records(): array { + return array( + 'frontend' => array( + 'css' => array(), + 'js' => array(), + ), + 'editor' => array( + 'css' => array(), + 'js' => array(), + ), + ); + } + + /** + * Normalizes scoped asset record groups. + * + * @param array $records Candidate records. + * @return array Normalized records. + */ + private function normalize_asset_context_records( array $records ): array { + $normalized = $this->empty_asset_context_records(); + + foreach ( array( 'frontend', 'editor' ) as $context ) { + if ( empty( $records[ $context ] ) || ! is_array( $records[ $context ] ) ) { + continue; + } + + foreach ( array( 'css', 'js' ) as $type ) { + $normalized[ $context ][ $type ] = isset( $records[ $context ][ $type ] ) && is_array( $records[ $context ][ $type ] ) + ? array_values( $records[ $context ][ $type ] ) + : array(); + } + } + + return $normalized; + } + + /** + * Checks whether any scoped asset records are present. + * + * @param array $records Scoped records. + * @return bool TRUE when records are present. + */ + private function has_asset_records( array $records ): bool { + foreach ( array( 'frontend', 'editor' ) as $context ) { + foreach ( array( 'css', 'js' ) as $type ) { + if ( ! empty( $records[ $context ][ $type ] ) ) { + return true; + } + } + } + + return false; + } + + /** + * Gets an asset entry path. + * + * @param array $entry Asset entry. + * @return string Relative path. + */ + private function entry_path( array $entry ): string { + foreach ( array( 'path', 'file', 'src', 'href' ) as $key ) { + if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) ) { + return $this->normalize_relative_asset_path( (string) $entry[ $key ] ); + } + } + + return ''; + } + + /** + * Gets the record-relative path. + * + * @param array $entry Asset entry. + * @param string $fallback Fallback relative path. + * @return string Relative path. + */ + private function entry_relative( array $entry, string $fallback ): string { + if ( isset( $entry['relative'] ) && is_scalar( $entry['relative'] ) ) { + $relative = $this->normalize_relative_asset_path( (string) $entry['relative'] ); + + if ( '' !== $relative ) { + return $relative; + } + } + + return $fallback; + } + + /** + * Gets the asset version. + * + * @param array $entry Asset entry. + * @param string $path Absolute asset path. + * @return string|null Version. + */ + private function entry_version( array $entry, string $path ): ?string { + foreach ( array( 'version', 'hash' ) as $key ) { + if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) && '' !== trim( (string) $entry[ $key ] ) ) { + return (string) $entry[ $key ]; + } + } + + $modified = filemtime( $path ); + + return false === $modified ? null : (string) $modified; + } + + /** + * Gets normalized dependency handles. + * + * @param array $entry Asset entry. + * @return array Dependency handles. + */ + private function entry_dependencies( array $entry ): array { + $dependencies = $entry['dependencies'] ?? ( $entry['deps'] ?? array() ); + + if ( ! is_array( $dependencies ) ) { + return array(); + } + + return array_values( + array_filter( + array_map( + static function ( $dependency ) { + return is_scalar( $dependency ) ? trim( (string) $dependency ) : null; + }, + $dependencies + ), + static function ( $dependency ): bool { + return is_string( $dependency ) && '' !== $dependency; + } + ) + ); + } + + /** + * Gets an optional script module flag. + * + * @param array $entry Asset entry. + * @return bool|null Module flag, or null to use service default. + */ + private function entry_module( array $entry ): ?bool { + if ( array_key_exists( 'module', $entry ) ) { + return ! empty( $entry['module'] ); + } + + if ( array_key_exists( 'type', $entry ) && is_scalar( $entry['type'] ) ) { + return 'module' === strtolower( trim( (string) $entry['type'] ) ); + } + + return null; + } + + /** + * Normalizes a relative asset path. + * + * @param string $path Candidate path. + * @return string Safe relative path. + */ + private function normalize_relative_asset_path( string $path ): string { + $path = trim( str_replace( '\\', '/', $path ) ); + + if ( + '' === $path + || false !== strpos( $path, "\0" ) + || 0 === strpos( $path, '/' ) + || preg_match( '#(^|/)\.\.(/|$)#', $path ) + ) { + return ''; + } + + while ( 0 === strpos( $path, './' ) ) { + $path = substr( $path, 2 ); + } + + return $path; + } + /** * Normalizes an ACF PHP block name to an ACF-safe un-namespaced slug. * diff --git a/includes/Blocks/ComponentLocator.php b/includes/Blocks/ComponentLocator.php index 3c38fd8..6f6299a 100644 --- a/includes/Blocks/ComponentLocator.php +++ b/includes/Blocks/ComponentLocator.php @@ -123,8 +123,11 @@ public function acf_components(): array { $seen_slugs[ $slug ] = $record; $components[] = array( + 'path' => $directory, 'metadata_path' => $path, 'relative' => $relative, + 'root_path' => $file['root_path'], + 'root_uri' => $file['root_uri'] ?? '', 'slug' => $slug, 'source' => $file['root_source'], 'template' => $template, @@ -198,6 +201,8 @@ public function native_block_directories(): array { $directories[] = array( 'path' => $directory, 'relative' => $relative, + 'root_path' => $file['root_path'], + 'root_uri' => $file['root_uri'] ?? '', 'source' => $file['root_source'], 'metadata_path' => $path, 'name' => $name, @@ -263,7 +268,7 @@ private function component_roots(): array { return $this->component_roots; } - $candidates = FileDiscovery::theme_roots( self::COMPONENTS_DIRECTORY ); + $candidates = FileDiscovery::theme_roots( self::COMPONENTS_DIRECTORY, true ); /** * Filters built component discovery roots before scanning. @@ -303,6 +308,8 @@ private function component_record( array $file, string $directory, string $relat return array( 'path' => $directory, 'relative' => $relative, + 'root_path' => $file['root_path'], + 'root_uri' => $file['root_uri'] ?? '', 'source' => $file['root_source'], 'metadata_path' => $metadata_path, ); diff --git a/includes/Runtime/Assets.php b/includes/Runtime/Assets.php index 36d6c77..a42a895 100644 --- a/includes/Runtime/Assets.php +++ b/includes/Runtime/Assets.php @@ -15,6 +15,20 @@ */ final class Assets { + /** + * Memoized component metadata asset paths. + * + * @var array + */ + private $component_asset_paths = array(); + + /** + * Memoized manifest-scoped component asset paths. + * + * @var array|null + */ + private $manifest_component_asset_paths; + /** * Registers asset hooks. * @@ -181,6 +195,13 @@ private function scanned_asset_files( string $directory, array $extensions ): ar continue; } + if ( $this->is_scoped_component_asset( $directory, $file ) ) { + // Component metadata can opt assets into block-scoped loading. Leave + // those files to the block registration callback instead of loading + // them globally from every request's component scanner pass. + continue; + } + if ( isset( $seen[ $relative ] ) ) { // Discovery roots are child-first. Once a relative path is seen, // later parent files with the same name are intentional fallbacks @@ -280,6 +301,165 @@ private function is_reserved_editor_asset( string $directory, string $relative ) && 0 === strpos( ltrim( $relative, '/\\' ), 'editor/' ); } + /** + * Checks whether a component asset is declared as block-scoped metadata. + * + * @param string $directory Theme-relative asset directory being scanned. + * @param array $file File discovery record. + * @return bool TRUE when the asset should not be globally loaded. + */ + private function is_scoped_component_asset( string $directory, array $file ): bool { + if ( 'dist/components' !== trim( $directory, '/\\' ) || empty( $file['path'] ) ) { + return false; + } + + $root_relative = isset( $file['relative'] ) && is_scalar( $file['relative'] ) + ? $this->normalize_relative_asset_path( (string) $file['relative'] ) + : ''; + + if ( '' !== $root_relative && isset( $this->manifest_component_asset_paths()[ $root_relative ] ) ) { + return true; + } + + $component_dir = dirname( (string) $file['path'] ); + $relative = FileDiscovery::relative_path( $component_dir, (string) $file['path'] ); + $asset_paths = $this->component_asset_paths( $component_dir ); + + return isset( $asset_paths[ $relative ] ); + } + + /** + * Gets manifest-scoped component asset paths. + * + * @return array Scoped paths keyed by dist/components-relative path. + */ + private function manifest_component_asset_paths(): array { + if ( is_array( $this->manifest_component_asset_paths ) ) { + return $this->manifest_component_asset_paths; + } + + $this->manifest_component_asset_paths = ( new AssetManifest() )->scoped_component_asset_paths(); + + return $this->manifest_component_asset_paths; + } + + /** + * Gets declared component metadata asset paths for a component directory. + * + * @param string $component_dir Absolute component directory path. + * @return array Declared asset paths keyed by relative path. + */ + private function component_asset_paths( string $component_dir ): array { + if ( isset( $this->component_asset_paths[ $component_dir ] ) ) { + return $this->component_asset_paths[ $component_dir ]; + } + + $paths = array(); + $metadata_files = glob( rtrim( $component_dir, '/\\' ) . '/*.component.json' ); + + if ( ! is_array( $metadata_files ) ) { + $this->component_asset_paths[ $component_dir ] = $paths; + return $paths; + } + + foreach ( $metadata_files as $metadata_file ) { + $contents = is_readable( $metadata_file ) ? file_get_contents( $metadata_file ) : false; + + if ( ! is_string( $contents ) ) { + continue; + } + + $metadata = json_decode( $contents, true ); + + if ( ! is_array( $metadata ) || JSON_ERROR_NONE !== json_last_error() || empty( $metadata['assets'] ) || ! is_array( $metadata['assets'] ) ) { + continue; + } + + foreach ( $this->metadata_asset_paths( $metadata['assets'] ) as $path ) { + $paths[ $path ] = true; + } + } + + $this->component_asset_paths[ $component_dir ] = $paths; + + return $paths; + } + + /** + * Collects asset paths from component metadata asset sections. + * + * @param array $assets Component metadata assets. + * @return array Relative asset paths. + */ + private function metadata_asset_paths( array $assets ): array { + $paths = array(); + + foreach ( array( 'frontend', 'editor' ) as $context ) { + if ( isset( $assets[ $context ] ) && is_array( $assets[ $context ] ) ) { + $paths = array_merge( $paths, $this->metadata_asset_paths( $assets[ $context ] ) ); + } + } + + foreach ( array( 'css', 'js' ) as $type ) { + if ( empty( $assets[ $type ] ) || ! is_array( $assets[ $type ] ) ) { + continue; + } + + foreach ( $assets[ $type ] as $entry ) { + $path = $this->metadata_asset_path( $entry ); + + if ( '' !== $path ) { + $paths[] = $path; + } + } + } + + return array_values( array_unique( $paths ) ); + } + + /** + * Gets one component metadata asset path. + * + * @param mixed $entry Metadata asset entry. + * @return string Relative asset path, or empty string when invalid. + */ + private function metadata_asset_path( $entry ): string { + $entry = is_array( $entry ) ? $entry : array( 'path' => $entry ); + + foreach ( array( 'path', 'file', 'src', 'href' ) as $key ) { + if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) ) { + return $this->normalize_relative_asset_path( (string) $entry[ $key ] ); + } + } + + return ''; + } + + /** + * Normalizes a safe relative asset path. + * + * @param string $path Candidate path. + * @return string Safe relative path, or empty string when invalid. + */ + private function normalize_relative_asset_path( string $path ): string { + $path = trim( str_replace( '\\', '/', $path ) ); + + if ( + '' === $path + || false !== strpos( $path, "\0" ) + || 0 === strpos( $path, '/' ) + || preg_match( '#(^|/)\.\.(/|$)#', $path ) + ) { + return ''; + } + + while ( 0 === strpos( $path, './' ) ) { + $path = substr( $path, 2 ); + } + + return $path; + } + /** * Gets a filemtime-based asset version. * diff --git a/includes/Support/AssetManifest.php b/includes/Support/AssetManifest.php index 029b9e7..e2041df 100644 --- a/includes/Support/AssetManifest.php +++ b/includes/Support/AssetManifest.php @@ -36,10 +36,7 @@ public function asset_records( string $scope, array $extensions ): ?array { return null; } - $assets = isset( $manifest['data']['assets'] ) && is_array( $manifest['data']['assets'] ) - ? $manifest['data']['assets'] - : $manifest['data']; - $records = $this->scope_records( $scope, $extensions, $assets, $manifest ); + $records = $this->scope_records( $scope, $extensions, $this->asset_data( $manifest ), $manifest ); if ( null === $records ) { return null; @@ -48,6 +45,86 @@ public function asset_records( string $scope, array $extensions ): ?array { return FileDiscovery::sort_by_priority_and_relative( $records ); } + /** + * Gets manifest-backed asset records for specific block/component keys. + * + * A null return means the manifest does not declare any matching scoped + * assets and callers may use component metadata or scanner fallbacks. + * + * @param array $identifiers Block and component identifiers. + * @return array|null Asset records grouped by frontend/editor and css/js. + */ + public function scoped_asset_records( array $identifiers ): ?array { + $manifest = $this->manifest(); + + if ( null === $manifest ) { + return null; + } + + $assets = $this->asset_data( $manifest ); + $identifiers = $this->normalize_identifiers( $identifiers ); + $records = $this->empty_context_records(); + $declared = false; + + foreach ( $identifiers as $identifier ) { + if ( isset( $assets['blocks'] ) && is_array( $assets['blocks'] ) && array_key_exists( $identifier, $assets['blocks'] ) ) { + $declared = true; + $records = $this->merge_context_records( + $records, + $this->context_section_records( $assets['blocks'][ $identifier ], $manifest, $identifier ) + ); + } + + if ( isset( $assets['components'] ) && is_array( $assets['components'] ) && array_key_exists( $identifier, $assets['components'] ) ) { + $declared = true; + $records = $this->merge_context_records( + $records, + $this->context_section_records( $assets['components'][ $identifier ], $manifest, $identifier ) + ); + } + } + + return $declared ? $this->sort_context_records( $records ) : null; + } + + /** + * Gets manifest-scoped component asset paths. + * + * These paths are relative to dist/components and are used by the recursive + * scanner to avoid globally loading files that a block/component manifest + * entry will load only when the block renders. + * + * @return array Scoped component asset paths keyed by relative path. + */ + public function scoped_component_asset_paths(): array { + $manifest = $this->manifest(); + + if ( null === $manifest ) { + return array(); + } + + $assets = $this->asset_data( $manifest ); + $paths = array(); + + if ( isset( $assets['blocks'] ) && is_array( $assets['blocks'] ) ) { + foreach ( $assets['blocks'] as $section ) { + foreach ( $this->context_section_paths( $section ) as $path ) { + $paths[ $path ] = true; + } + } + } + + if ( isset( $assets['components'] ) && is_array( $assets['components'] ) && ! $this->is_asset_section( $assets['components'] ) ) { + foreach ( $assets['components'] as $section ) { + foreach ( $this->context_section_paths( $section ) as $path ) { + $paths[ $path ] = true; + } + } + } + + return $paths; + } + /** * Gets records for a manifest asset scope. * @@ -64,23 +141,9 @@ private function scope_records( string $scope, array $extensions, array $assets, if ( array_key_exists( 'components', $assets ) ) { $declared = true; - $records = array_merge( $records, $this->section_records( $assets['components'], $extensions, $manifest ) ); - } - - if ( array_key_exists( 'blocks', $assets ) && is_array( $assets['blocks'] ) ) { - $declared = true; - - foreach ( $assets['blocks'] as $block_name => $section ) { - $records = array_merge( - $records, - $this->section_records( - $section, - $extensions, - $manifest, - is_scalar( $block_name ) ? (string) $block_name : '' - ) - ); - } + $records = $this->is_asset_section( $assets['components'] ) + ? array_merge( $records, $this->section_records( $assets['components'], $extensions, $manifest ) ) + : $records; } return $declared ? $records : null; @@ -132,6 +195,208 @@ private function section_records( $section, array $extensions, array $manifest, return $records; } + /** + * Gets frontend/editor records from a manifest section. + * + * @param mixed $section Manifest section. + * @param array $manifest Active manifest record. + * @param string $block_name Optional block/component identifier. + * @return array Context asset records. + */ + private function context_section_records( $section, array $manifest, string $block_name = '' ): array { + $records = $this->empty_context_records(); + + if ( ! is_array( $section ) ) { + return $records; + } + + if ( isset( $section['frontend'] ) || isset( $section['editor'] ) ) { + if ( isset( $section['frontend'] ) && is_array( $section['frontend'] ) ) { + $records['frontend']['css'] = $this->section_records( $section['frontend'], array( 'css' ), $manifest, $block_name ); + $records['frontend']['js'] = $this->section_records( $section['frontend'], array( 'js' ), $manifest, $block_name ); + } + + if ( isset( $section['editor'] ) && is_array( $section['editor'] ) ) { + $records['editor']['css'] = $this->section_records( $section['editor'], array( 'css' ), $manifest, $block_name ); + $records['editor']['js'] = $this->section_records( $section['editor'], array( 'js' ), $manifest, $block_name ); + } + + return $records; + } + + $records['frontend']['css'] = $this->section_records( $section, array( 'css' ), $manifest, $block_name ); + $records['frontend']['js'] = $this->section_records( $section, array( 'js' ), $manifest, $block_name ); + + return $records; + } + + /** + * Gets scoped asset paths from a frontend/editor manifest section. + * + * @param mixed $section Manifest section. + * @return array Component-relative paths. + */ + private function context_section_paths( $section ): array { + if ( ! is_array( $section ) ) { + return array(); + } + + $paths = array(); + + if ( isset( $section['frontend'] ) || isset( $section['editor'] ) ) { + foreach ( array( 'frontend', 'editor' ) as $context ) { + if ( isset( $section[ $context ] ) && is_array( $section[ $context ] ) ) { + $paths = array_merge( $paths, $this->section_paths( $section[ $context ] ) ); + } + } + + return array_values( array_unique( $paths ) ); + } + + return $this->section_paths( $section ); + } + + /** + * Gets scoped asset paths from a css/js manifest section. + * + * @param array $section Manifest section. + * @return array Component-relative paths. + */ + private function section_paths( array $section ): array { + $paths = array(); + + foreach ( array( 'css', 'js' ) as $type ) { + if ( empty( $section[ $type ] ) || ! is_array( $section[ $type ] ) ) { + continue; + } + + foreach ( $section[ $type ] as $entry ) { + $data = is_array( $entry ) ? $entry : array( 'path' => $entry ); + $path = $this->component_relative_path( $this->entry_path( $data ) ); + + if ( '' !== $path ) { + $paths[] = $path; + } + } + } + + return array_values( array_unique( $paths ) ); + } + + /** + * Converts a manifest entry path into a dist/components-relative path. + * + * @param string $path Manifest entry path. + * @return string Component-relative path. + */ + private function component_relative_path( string $path ): string { + if ( 0 === strpos( $path, 'components/' ) ) { + return substr( $path, strlen( 'components/' ) ); + } + + return $path; + } + + /** + * Gets manifest asset data. + * + * @param array $manifest Active manifest record. + * @return array Asset data. + */ + private function asset_data( array $manifest ): array { + return isset( $manifest['data']['assets'] ) && is_array( $manifest['data']['assets'] ) + ? $manifest['data']['assets'] + : $manifest['data']; + } + + /** + * Checks whether a section directly declares css/js records. + * + * @param mixed $section Manifest section. + * @return bool TRUE when the section is a broad asset section. + */ + private function is_asset_section( $section ): bool { + return is_array( $section ) && ( array_key_exists( 'css', $section ) || array_key_exists( 'js', $section ) ); + } + + /** + * Gets an empty frontend/editor record set. + * + * @return array Empty context records. + */ + private function empty_context_records(): array { + return array( + 'frontend' => array( + 'css' => array(), + 'js' => array(), + ), + 'editor' => array( + 'css' => array(), + 'js' => array(), + ), + ); + } + + /** + * Merges two context asset record sets. + * + * @param array $base Base records. + * @param array $add Records to add. + * @return array Merged records. + */ + private function merge_context_records( array $base, array $add ): array { + foreach ( array( 'frontend', 'editor' ) as $context ) { + foreach ( array( 'css', 'js' ) as $type ) { + $base[ $context ][ $type ] = array_merge( + $base[ $context ][ $type ] ?? array(), + $add[ $context ][ $type ] ?? array() + ); + } + } + + return $base; + } + + /** + * Sorts context asset records. + * + * @param array $records Context records. + * @return array Sorted context records. + */ + private function sort_context_records( array $records ): array { + foreach ( array( 'frontend', 'editor' ) as $context ) { + foreach ( array( 'css', 'js' ) as $type ) { + $records[ $context ][ $type ] = FileDiscovery::sort_by_priority_and_relative( $records[ $context ][ $type ] ?? array() ); + } + } + + return $records; + } + + /** + * Normalizes scoped asset identifiers. + * + * @param array $identifiers Candidate identifiers. + * @return array Normalized identifiers. + */ + private function normalize_identifiers( array $identifiers ): array { + $normalized = array(); + + foreach ( $identifiers as $identifier ) { + if ( ! is_scalar( $identifier ) ) { + continue; + } + + $identifier = trim( (string) $identifier ); + + if ( '' !== $identifier ) { + $normalized[] = $identifier; + } + } + + return array_values( array_unique( $normalized ) ); + } + /** * Normalizes a manifest asset entry. * diff --git a/package.json b/package.json index df1bb75..b2843df 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "smoke:acf-json": "php .github/scripts/acf-local-json-smoke.php", "smoke:asset-manifest": "php .github/scripts/asset-manifest-smoke.php", "smoke:attributes": "php .github/scripts/attribute-helper-smoke.php", + "smoke:block-assets": "php .github/scripts/block-scoped-assets-smoke.php", "smoke:bootstrap-loader": "php .github/scripts/bootstrap-loader-smoke.php", "smoke:child-theme-generator": "php .github/scripts/child-theme-generator-smoke.php", "smoke:component-locator": "php .github/scripts/component-locator-smoke.php", From f4b646a4b0cdab0542874a3e50fc1730fc5e94e0 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Mon, 6 Jul 2026 15:01:41 -0500 Subject: [PATCH 43/55] feat: add optional component discovery cache --- .github/scripts/component-locator-smoke.php | 277 +++++++++++++ .github/scripts/release-check.cjs | 19 +- docs/acf-twig-blocks.md | 2 + docs/native-gutenberg-blocks.md | 2 + docs/parent-child-architecture.md | 23 +- docs/post-2x-optimization-roadmap.md | 4 +- includes/Blocks/ComponentLocator.php | 418 +++++++++++++++++++- 7 files changed, 737 insertions(+), 8 deletions(-) diff --git a/.github/scripts/component-locator-smoke.php b/.github/scripts/component-locator-smoke.php index 4f588f3..c85e87e 100644 --- a/.github/scripts/component-locator-smoke.php +++ b/.github/scripts/component-locator-smoke.php @@ -10,24 +10,145 @@ exit( 1 ); } +$GLOBALS['emulsify_locator_smoke_hooks'] = array(); +$GLOBALS['emulsify_locator_transients'] = array(); +$GLOBALS['emulsify_locator_environment'] = 'production'; + +if ( ! function_exists( 'add_filter' ) ) { + function add_filter( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): bool { + $GLOBALS['emulsify_locator_smoke_hooks'][ $hook ][ $priority ][] = array( + 'accepted_args' => $accepted_args, + 'callback' => $callback, + ); + + ksort( $GLOBALS['emulsify_locator_smoke_hooks'][ $hook ] ); + + return true; + } +} + if ( ! function_exists( 'apply_filters' ) ) { function apply_filters( string $hook, $value, ...$arguments ) { + if ( empty( $GLOBALS['emulsify_locator_smoke_hooks'][ $hook ] ) ) { + return $value; + } + + foreach ( $GLOBALS['emulsify_locator_smoke_hooks'][ $hook ] as $callbacks ) { + foreach ( $callbacks as $callback ) { + $value = call_user_func_array( + $callback['callback'], + array_slice( + array_merge( array( $value ), $arguments ), + 0, + $callback['accepted_args'] + ) + ); + } + } + return $value; } } +if ( ! function_exists( 'get_stylesheet' ) ) { + function get_stylesheet(): string { + return $GLOBALS['emulsify_locator_stylesheet']; + } +} + if ( ! function_exists( 'get_stylesheet_directory' ) ) { function get_stylesheet_directory(): string { return $GLOBALS['emulsify_locator_child_theme']; } } +if ( ! function_exists( 'get_template' ) ) { + function get_template(): string { + return $GLOBALS['emulsify_locator_template']; + } +} + if ( ! function_exists( 'get_template_directory' ) ) { function get_template_directory(): string { return $GLOBALS['emulsify_locator_parent_theme']; } } +if ( ! function_exists( 'wp_get_theme' ) ) { + function wp_get_theme( string $stylesheet = '' ) { + return new class( $stylesheet ) { + /** + * Theme stylesheet slug. + * + * @var string + */ + private $stylesheet; + + /** + * Constructor. + * + * @param string $stylesheet Theme stylesheet slug. + */ + public function __construct( string $stylesheet ) { + $this->stylesheet = $stylesheet; + } + + /** + * Gets theme metadata. + * + * @param string $header Metadata header. + * @return string Metadata value. + */ + public function get( string $header ): string { + if ( 'Version' !== $header ) { + return ''; + } + + return isset( $GLOBALS['emulsify_locator_theme_versions'][ $this->stylesheet ] ) + ? $GLOBALS['emulsify_locator_theme_versions'][ $this->stylesheet ] + : ''; + } + }; + } +} + +if ( ! function_exists( 'wp_get_environment_type' ) ) { + function wp_get_environment_type(): string { + return $GLOBALS['emulsify_locator_environment']; + } +} + +if ( ! function_exists( 'get_transient' ) ) { + function get_transient( string $key ) { + if ( array_key_exists( 'emulsify_locator_transient_override', $GLOBALS ) ) { + return $GLOBALS['emulsify_locator_transient_override']; + } + + return array_key_exists( $key, $GLOBALS['emulsify_locator_transients'] ) + ? $GLOBALS['emulsify_locator_transients'][ $key ] + : false; + } +} + +if ( ! function_exists( 'set_transient' ) ) { + function set_transient( string $key, $value, int $expiration = 0 ): bool { + unset( $expiration ); + + $GLOBALS['emulsify_locator_transients'][ $key ] = $value; + + return true; + } +} + +if ( ! function_exists( 'delete_transient' ) ) { + function delete_transient( string $key ): bool { + $exists = array_key_exists( $key, $GLOBALS['emulsify_locator_transients'] ); + unset( $GLOBALS['emulsify_locator_transients'][ $key ] ); + + return $exists; + } +} + if ( ! function_exists( 'sanitize_title' ) ) { function sanitize_title( string $title ): string { $slug = preg_replace( '/[^a-z0-9]+/i', '-', strtolower( $title ) ); @@ -167,6 +288,12 @@ static function ( array $args ): string { $GLOBALS['emulsify_locator_child_theme'] = $child; $GLOBALS['emulsify_locator_parent_theme'] = $parent; +$GLOBALS['emulsify_locator_stylesheet'] = 'child-theme'; +$GLOBALS['emulsify_locator_template'] = 'parent-theme'; +$GLOBALS['emulsify_locator_theme_versions'] = array( + 'child-theme' => '1.0.0', + 'parent-theme' => '2.0.0', +); $GLOBALS['emulsify_locator_acf_registered'] = array(); $GLOBALS['emulsify_locator_existing_acf_blocks'] = array(); @@ -277,6 +404,156 @@ static function ( array $args ): string { 'ACF block registration should report duplicate normalized final block names.' ); + add_filter( + 'emulsify_theme_component_discovery_cache_enabled', + static function (): bool { + return true; + } + ); + add_filter( + 'emulsify_theme_component_discovery_cache_key_parts', + static function ( array $key_parts ): array { + $key_parts['smoke'] = 'component-locator'; + + return $key_parts; + } + ); + add_filter( + 'emulsify_theme_component_discovery_cache_ttl', + static function (): int { + return 3600; + } + ); + + $cache_child = $work_root . '/cache-child'; + $cache_parent = $work_root . '/cache-parent'; + $GLOBALS['emulsify_locator_child_theme'] = $cache_child; + $GLOBALS['emulsify_locator_parent_theme'] = $cache_parent; + $GLOBALS['emulsify_locator_stylesheet'] = 'cache-child'; + $GLOBALS['emulsify_locator_template'] = 'cache-parent'; + $GLOBALS['emulsify_locator_theme_versions'] = array( + 'cache-child' => '1.0.0', + 'cache-parent' => '2.0.0', + ); + $GLOBALS['emulsify_locator_transients'] = array(); + emulsify_locator_smoke_write( $cache_child . '/dist/components/cache-card/cache-card.component.json', '{"title":"Cache Card"}' ); + emulsify_locator_smoke_write( $cache_child . '/dist/components/cache-card/cache-card.twig', '
    Cache card
    ' ); + + $cache_locator = new Emulsify\Theme\Blocks\ComponentLocator(); + + emulsify_locator_smoke_assert( + array( 'cache-card' ) === emulsify_locator_smoke_relatives( $cache_locator->acf_components() ), + 'Missing persistent discovery cache should fall back to filesystem scanning.' + ); + + emulsify_locator_smoke_write( $cache_child . '/dist/components/cache-late/cache-late.component.json', '{"title":"Cache Late"}' ); + emulsify_locator_smoke_write( $cache_child . '/dist/components/cache-late/cache-late.twig', '
    Cache late
    ' ); + + $cached_locator = new Emulsify\Theme\Blocks\ComponentLocator(); + + emulsify_locator_smoke_assert( + ! in_array( 'cache-late', emulsify_locator_smoke_relatives( $cached_locator->acf_components() ), true ), + 'Enabled persistent discovery cache should be reused across locator instances.' + ); + emulsify_locator_smoke_assert( + Emulsify\Theme\Blocks\ComponentLocator::clear_discovery_cache(), + 'Component discovery cache clear method should delete the active transient.' + ); + + $cleared_locator = new Emulsify\Theme\Blocks\ComponentLocator(); + + emulsify_locator_smoke_assert( + in_array( 'cache-late', emulsify_locator_smoke_relatives( $cleared_locator->acf_components() ), true ), + 'Clearing persistent discovery cache should force the next locator to scan.' + ); + + $version_child = $work_root . '/version-child'; + $version_parent = $work_root . '/version-parent'; + $GLOBALS['emulsify_locator_child_theme'] = $version_child; + $GLOBALS['emulsify_locator_parent_theme'] = $version_parent; + $GLOBALS['emulsify_locator_stylesheet'] = 'version-child'; + $GLOBALS['emulsify_locator_template'] = 'version-parent'; + $GLOBALS['emulsify_locator_theme_versions'] = array( + 'version-child' => '1.0.0', + 'version-parent' => '2.0.0', + ); + $GLOBALS['emulsify_locator_transients'] = array(); + emulsify_locator_smoke_write( $version_child . '/dist/components/version-card/version-card.component.json', '{"title":"Version Card"}' ); + emulsify_locator_smoke_write( $version_child . '/dist/components/version-card/version-card.twig', '
    Version card
    ' ); + ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components(); + emulsify_locator_smoke_write( $version_child . '/dist/components/version-late/version-late.component.json', '{"title":"Version Late"}' ); + emulsify_locator_smoke_write( $version_child . '/dist/components/version-late/version-late.twig', '
    Version late
    ' ); + + emulsify_locator_smoke_assert( + ! in_array( 'version-late', emulsify_locator_smoke_relatives( ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components() ), true ), + 'Persistent discovery cache should hold until an invalidating key part changes.' + ); + + $GLOBALS['emulsify_locator_theme_versions']['version-child'] = '1.0.1'; + + emulsify_locator_smoke_assert( + in_array( 'version-late', emulsify_locator_smoke_relatives( ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components() ), true ), + 'Changing the child theme version should change the persistent discovery cache key.' + ); + + $mtime_child = $work_root . '/mtime-child'; + $mtime_parent = $work_root . '/mtime-parent'; + $GLOBALS['emulsify_locator_child_theme'] = $mtime_child; + $GLOBALS['emulsify_locator_parent_theme'] = $mtime_parent; + $GLOBALS['emulsify_locator_stylesheet'] = 'mtime-child'; + $GLOBALS['emulsify_locator_template'] = 'mtime-parent'; + $GLOBALS['emulsify_locator_theme_versions'] = array( + 'mtime-child' => '1.0.0', + 'mtime-parent' => '2.0.0', + ); + $GLOBALS['emulsify_locator_transients'] = array(); + emulsify_locator_smoke_write( $mtime_child . '/dist/emulsify-assets.json', '{"assets":{}}' ); + emulsify_locator_smoke_write( $mtime_child . '/dist/components/mtime-card/mtime-card.component.json', '{"title":"Mtime Card"}' ); + emulsify_locator_smoke_write( $mtime_child . '/dist/components/mtime-card/mtime-card.twig', '
    Mtime card
    ' ); + ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components(); + emulsify_locator_smoke_write( $mtime_child . '/dist/components/mtime-late/mtime-late.component.json', '{"title":"Mtime Late"}' ); + emulsify_locator_smoke_write( $mtime_child . '/dist/components/mtime-late/mtime-late.twig', '
    Mtime late
    ' ); + + emulsify_locator_smoke_assert( + ! in_array( 'mtime-late', emulsify_locator_smoke_relatives( ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components() ), true ), + 'Persistent discovery cache should include the manifest mtime in its key.' + ); + + touch( $mtime_child . '/dist/emulsify-assets.json', time() + 10 ); + clearstatcache( true, $mtime_child . '/dist/emulsify-assets.json' ); + + emulsify_locator_smoke_assert( + in_array( 'mtime-late', emulsify_locator_smoke_relatives( ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components() ), true ), + 'Changing the asset manifest mtime should change the persistent discovery cache key.' + ); + + $invalid_child = $work_root . '/invalid-cache-child'; + $invalid_parent = $work_root . '/invalid-cache-parent'; + $GLOBALS['emulsify_locator_child_theme'] = $invalid_child; + $GLOBALS['emulsify_locator_parent_theme'] = $invalid_parent; + $GLOBALS['emulsify_locator_stylesheet'] = 'invalid-cache-child'; + $GLOBALS['emulsify_locator_template'] = 'invalid-cache-parent'; + $GLOBALS['emulsify_locator_theme_versions'] = array( + 'invalid-cache-child' => '1.0.0', + 'invalid-cache-parent' => '2.0.0', + ); + $GLOBALS['emulsify_locator_transients'] = array(); + $GLOBALS['emulsify_locator_transient_override'] = array( + 'files' => array( + array( + 'path' => $invalid_child . '/broken.component.json', + ), + ), + ); + emulsify_locator_smoke_write( $invalid_child . '/dist/components/invalid-card/invalid-card.component.json', '{"title":"Invalid Card"}' ); + emulsify_locator_smoke_write( $invalid_child . '/dist/components/invalid-card/invalid-card.twig', '
    Invalid card
    ' ); + + emulsify_locator_smoke_assert( + array( 'invalid-card' ) === emulsify_locator_smoke_relatives( ( new Emulsify\Theme\Blocks\ComponentLocator() )->acf_components() ), + 'Invalid persistent discovery cache data should fall back to filesystem scanning.' + ); + unset( $GLOBALS['emulsify_locator_transient_override'] ); + echo "Component locator smoke checks passed.\n"; } catch ( Throwable $throwable ) { fwrite( STDERR, $throwable->getMessage() . "\n" ); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index d50c8ed..7a268ee 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -553,7 +553,10 @@ function runStaticChecks() { ensure(locator.includes('native_block_name'), 'Component locator should detect duplicate native block names.'); ensure(fileDiscovery.includes('get_stylesheet_directory()') && fileDiscovery.includes('get_template_directory()'), 'File discovery helper should build child and parent theme roots.'); ensure(fileDiscovery.indexOf('get_stylesheet_directory()') < fileDiscovery.indexOf('get_template_directory()'), 'File discovery helper should keep child roots before parent roots.'); - ensure(!/wp_cache_|transient/i.test(locator), 'Component locator should not use persistent caching without invalidation.'); + ensure(locator.includes('emulsify_theme_component_discovery_cache_enabled') && locator.includes('get_transient') && locator.includes('set_transient'), 'Component locator should expose opt-in transient-backed persistent discovery caching.'); + ensure(locator.includes('clear_discovery_cache') && locator.includes('delete_transient'), 'Component locator should provide a clear method for the active discovery cache key.'); + ensure(locator.includes('stylesheet_version') && locator.includes('template_version') && locator.includes('manifest_file_signature'), 'Component locator cache key should include theme versions and manifest filemtime.'); + ensure(locator.includes('wp_get_environment_type') && locator.includes('WP_DEBUG'), 'Component locator should keep active development uncached unless explicitly enabled.'); ensure(registry.includes('$components = new ComponentLocator()'), 'Block registry should share one ComponentLocator instance.'); ensure(acfBlocks.includes('$this->components->acf_components()'), 'ACF/Twig block discovery should use ComponentLocator.'); ensure(acfBlocks.includes('acf_block_name'), 'ACF/Twig block registration should skip duplicate final ACF block names.'); @@ -567,7 +570,10 @@ function runStaticChecks() { ensure(smoke.includes('duplicate component slugs'), 'Component locator smoke should verify duplicate ACF/Twig component slug reporting.'); ensure(smoke.includes('duplicate block.json name values'), 'Component locator smoke should verify duplicate native block name reporting.'); ensure(smoke.includes('emulsify-shared-acf'), 'Component locator smoke should verify duplicate normalized final ACF block name handling.'); - return 'Component locator memoizes request-local discovery and keeps child-theme-first block priority with duplicate safety.'; + ensure(smoke.includes('Missing persistent discovery cache should fall back') && smoke.includes('Enabled persistent discovery cache should be reused'), 'Component locator smoke should cover enabled persistent cache behavior.'); + ensure(smoke.includes('Changing the child theme version should change') && smoke.includes('Changing the asset manifest mtime should change'), 'Component locator smoke should cover cache key invalidation.'); + ensure(smoke.includes('Invalid persistent discovery cache data should fall back'), 'Component locator smoke should cover invalid cache fallback.'); + return 'Component locator memoizes request-local discovery and offers opt-in persistent caching with duplicate safety.'; }); runStaticCheck('Runtime filters', () => { @@ -587,11 +593,12 @@ function runStaticChecks() { const acfJsonSmoke = readFile('.github/scripts/acf-local-json-smoke.php'); const assetManifestSmoke = readFile('.github/scripts/asset-manifest-smoke.php'); const blockAssetSmoke = readFile('.github/scripts/block-scoped-assets-smoke.php'); + const componentLocatorSmoke = readFile('.github/scripts/component-locator-smoke.php'); const editorEnhancementsSmoke = readFile('.github/scripts/editor-enhancements-smoke.php'); const smoke = readFile('.github/scripts/theme-filters-smoke.php'); const editorPolicySmoke = readFile('.github/scripts/editor-policy-smoke.php'); const patternSmoke = readFile('.github/scripts/pattern-registry-smoke.php'); - const smokeText = [acfJsonSmoke, assetManifestSmoke, blockAssetSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); + const smokeText = [acfJsonSmoke, assetManifestSmoke, blockAssetSmoke, componentLocatorSmoke, editorEnhancementsSmoke, smoke, editorPolicySmoke, patternSmoke].join('\n'); const expectedFilters = [ 'emulsify_theme_acf_json_enabled', 'emulsify_theme_acf_json_save_path', @@ -615,6 +622,9 @@ function runStaticChecks() { 'emulsify_theme_pattern_categories', 'emulsify_theme_pattern_args', 'emulsify_theme_component_roots', + 'emulsify_theme_component_discovery_cache_enabled', + 'emulsify_theme_component_discovery_cache_key_parts', + 'emulsify_theme_component_discovery_cache_ttl', 'emulsify_theme_setup_options', 'emulsify_theme_editor_policy_options', 'emulsify_theme_allowed_block_types', @@ -998,6 +1008,7 @@ function runStaticChecks() { ensure(docs.architecture.includes('generatedFrom: "emulsify-wordpress"') && docs.architecture.includes('safer replacement checks'), 'Architecture doc should document generated child theme lineage metadata.'); ensure(docs.architecture.includes('@emulsify-tpl'), 'Architecture doc should document the parent-only template namespace.'); ensure(docs.architecture.includes('{% include "project_machine_name:component_name" %}') && docs.architecture.includes('The legacy `@components/component-name/component-name.twig` namespace remains supported'), 'Architecture doc should document generic project machine-name component includes while preserving @components compatibility.'); + ensure(docs.architecture.includes('Optional persistent discovery cache') && docs.architecture.includes('clear_discovery_cache'), 'Architecture doc should document the optional component discovery cache and clear method.'); ensure(docs.twig.includes('@templates') && docs.twig.includes('@components'), 'Twig doc should document core namespaces.'); ensure(docs.twig.includes('Install or author project components in the structure defined by the selected Emulsify component system'), 'Twig doc should avoid prescribing a component source structure.'); ensure(docs.twig.includes('The general form is `project_machine_name:component_name`'), 'Twig doc should document the generic project component include form.'); @@ -1067,7 +1078,7 @@ function runStaticChecks() { ensure(docs.post2xRoadmap.includes('Build on Composer PSR-4 autoloading with grouped runtime directories'), 'Post-2.x roadmap should include the code organization milestone.'); ensure(docs.post2xRoadmap.includes('optional manifest-driven asset loading'), 'Post-2.x roadmap should include the asset manifest milestone.'); ensure(docs.post2xRoadmap.includes('wp emulsify doctor'), 'Post-2.x roadmap should include CLI diagnostics.'); - ensure(docs.post2xRoadmap.includes('persistent discovery caching after manifest behavior is stable'), 'Post-2.x roadmap should place persistent caching after manifests.'); + ensure(docs.post2xRoadmap.includes('optional persistent discovery caching') && docs.post2xRoadmap.includes('invalidation guidance'), 'Post-2.x roadmap should keep cache follow-up work focused on diagnostics and guidance.'); for (const heading of [ '## Code organization', '## Runtime architecture', diff --git a/docs/acf-twig-blocks.md b/docs/acf-twig-blocks.md index 4812147..34b3a9b 100644 --- a/docs/acf-twig-blocks.md +++ b/docs/acf-twig-blocks.md @@ -11,6 +11,8 @@ The parent theme scans built component output under `dist/components`. A compone The Twig template can match the metadata filename or the component directory name. +Discovery uses request-only memoization by default. See [Parent and child theme architecture](parent-child-architecture.md#optional-persistent-discovery-cache) for the optional persistent discovery cache. + ## Example Whisk does not include an active ACF/Twig block example. A project component system can add metadata like this and build it into `dist/components`: diff --git a/docs/native-gutenberg-blocks.md b/docs/native-gutenberg-blocks.md index 39a7445..f121b35 100644 --- a/docs/native-gutenberg-blocks.md +++ b/docs/native-gutenberg-blocks.md @@ -10,6 +10,8 @@ The starter does not include an active native block example. Native blocks usual See [Component recipes](component-recipes.md) for a short `block.json` example and guidance on when to choose native blocks. +Discovery uses request-only memoization by default. See [Parent and child theme architecture](parent-child-architecture.md#optional-persistent-discovery-cache) for the optional persistent discovery cache. + ## Block assets Native blocks should use standard `block.json` asset fields such as `style`, `script`, `viewScript`, `editorStyle`, `editorScript`, and `viewScriptModule`. The parent theme passes the block directory to `register_block_type()` and does not manually enqueue assets that WordPress can register from `block.json`. diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index 8c462f3..3eafaff 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -48,7 +48,25 @@ The parent-only `@emulsify-tpl` namespace points at parent templates. Use it whe Built child theme components are discovered before built parent theme components. When a child and parent component use the same relative path under `dist/components`, the child component wins for both ACF/Twig `*.component.json` metadata and native `block.json` metadata. -Discovery is memoized for the current PHP request. ACF/Twig and native block registration share one filesystem scan without adding persistent cache invalidation problems. +Discovery is memoized for the current PHP request. ACF/Twig and native block registration share one filesystem scan by default. + +## Optional persistent discovery cache + +Persistent component discovery caching is available but disabled by default. Enable it only when a project has stable build/version invalidation: + +```php +add_filter( 'emulsify_theme_component_discovery_cache_enabled', '__return_true' ); +``` + +The transient cache key includes the active stylesheet, parent template, child theme version, parent theme version, WordPress environment type, and `dist/emulsify-assets.json` filemtime when that manifest exists. Local and `WP_DEBUG` environments stay uncached unless the filter above explicitly enables caching. + +Projects that customize component roots dynamically can add their own invalidation token with `emulsify_theme_component_discovery_cache_key_parts`. The default cache lifetime can be adjusted with `emulsify_theme_component_discovery_cache_ttl`. + +To clear the current cache key from project code or a maintenance command, call: + +```php +\Emulsify\Theme\Blocks\ComponentLocator::clear_discovery_cache(); +``` For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`, using the general form `{% include "project_machine_name:component_name" %}`. The legacy `@components/component-name/component-name.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work. @@ -68,6 +86,9 @@ Child themes and project plugins can extend parent behavior with focused WordPre - `emulsify_theme_acf_json_load_paths` - `emulsify_theme_acf_json_remove_default_load_path` - `emulsify_theme_component_roots` +- `emulsify_theme_component_discovery_cache_enabled` +- `emulsify_theme_component_discovery_cache_key_parts` +- `emulsify_theme_component_discovery_cache_ttl` - `emulsify_theme_acf_block_metadata` - `emulsify_theme_acf_block_args` - `emulsify_theme_native_block_directories` diff --git a/docs/post-2x-optimization-roadmap.md b/docs/post-2x-optimization-roadmap.md index 7677825..d79e627 100644 --- a/docs/post-2x-optimization-roadmap.md +++ b/docs/post-2x-optimization-roadmap.md @@ -8,7 +8,7 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child the 2. Build on Composer PSR-4 autoloading with grouped runtime directories in a minor release. 3. Add optional manifest-driven asset loading. 4. Add CLI diagnostics such as `wp emulsify doctor`. -5. Add optional persistent discovery caching after manifest behavior is stable. +5. Expand diagnostics around optional persistent discovery caching once projects have real-world cache invalidation feedback. ## Code organization @@ -31,7 +31,7 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child the ## Component/block discovery - Refine child-first discovery diagnostics for duplicate ACF/Twig and native block definitions. -- Consider persistent discovery caching only after manifest behavior is stable and cache invalidation is explicit. +- Keep optional persistent discovery caching filter-controlled; future work should focus on diagnostics and invalidation guidance, not UI. - Keep discovery safe when ACF or native block APIs are unavailable. ## CLI diagnostics diff --git a/includes/Blocks/ComponentLocator.php b/includes/Blocks/ComponentLocator.php index 6f6299a..38e0292 100644 --- a/includes/Blocks/ComponentLocator.php +++ b/includes/Blocks/ComponentLocator.php @@ -15,7 +15,7 @@ * Discovery is memoized on this object for the lifetime of the current PHP * request. The registry shares one locator between ACF/Twig and native block * registration, so both paths reuse the same deterministic child-first file - * index without introducing persistent cache invalidation concerns. + * index. Persistent caching is optional and disabled by default. */ final class ComponentLocator { @@ -26,6 +26,27 @@ final class ComponentLocator { */ private const COMPONENTS_DIRECTORY = 'dist/components'; + /** + * Theme-relative built asset manifest path. + * + * @var string + */ + private const DEFAULT_MANIFEST_PATH = 'dist/emulsify-assets.json'; + + /** + * Transient key prefix for optional persistent discovery caching. + * + * @var string + */ + private const CACHE_TRANSIENT_PREFIX = 'emulsify_component_discovery_'; + + /** + * Default persistent cache lifetime in seconds. + * + * @var int + */ + private const DEFAULT_CACHE_TTL = 86400; + /** * Memoized component root records. * @@ -61,6 +82,15 @@ final class ComponentLocator { */ private $skipped_duplicates = array(); + /** + * Clears the optional persistent discovery cache for the current theme state. + * + * @return bool TRUE when WordPress deleted a transient, otherwise false. + */ + public static function clear_discovery_cache(): bool { + return ( new self() )->delete_component_files_cache(); + } + /** * Gets components that can be registered as ACF/Twig blocks. * @@ -249,11 +279,397 @@ private function component_files(): array { return $this->component_files; } + $cached = $this->cached_component_files(); + + if ( is_array( $cached ) ) { + $this->component_files = $cached; + return $this->component_files; + } + $this->component_files = FileDiscovery::file_records( $this->component_roots() ); + $this->cache_component_files( $this->component_files ); return $this->component_files; } + /** + * Gets cached component file records when optional caching is enabled. + * + * @return array|null Component file records, or null when scanning should run. + */ + private function cached_component_files(): ?array { + if ( ! $this->persistent_cache_enabled() || ! function_exists( 'get_transient' ) ) { + return null; + } + + $cached = get_transient( $this->component_files_cache_key() ); + + if ( ! is_array( $cached ) || ! isset( $cached['files'] ) || ! is_array( $cached['files'] ) ) { + return null; + } + + return $this->normalize_cached_component_files( $cached['files'] ); + } + + /** + * Stores component file records in the optional persistent cache. + * + * @param array $files Component file records. + * @return void + */ + private function cache_component_files( array $files ): void { + if ( ! $this->persistent_cache_enabled() || ! function_exists( 'set_transient' ) ) { + return; + } + + set_transient( + $this->component_files_cache_key(), + array( + 'files' => $files, + ), + $this->component_files_cache_ttl() + ); + } + + /** + * Deletes the optional persistent component file cache. + * + * @return bool TRUE when WordPress deleted a transient, otherwise false. + */ + private function delete_component_files_cache(): bool { + if ( ! function_exists( 'delete_transient' ) ) { + return false; + } + + return (bool) delete_transient( $this->component_files_cache_key() ); + } + + /** + * Checks whether optional persistent discovery caching is enabled. + * + * @return bool TRUE when persistent caching should be used. + */ + private function persistent_cache_enabled(): bool { + $enabled = false; + + if ( $this->is_development_environment() ) { + $enabled = false; + } + + /** + * Filters whether component discovery should use persistent caching. + * + * The default is false. Return true to explicitly enable persistent + * caching, including in local or WP_DEBUG environments. + * + * @param bool $enabled Whether persistent caching is enabled. + * @param ComponentLocator $locator Component locator instance. + * @param string $environment Current WordPress environment type. + */ + $filtered = apply_filters( 'emulsify_theme_component_discovery_cache_enabled', $enabled, $this, $this->environment_type() ); + + return (bool) $filtered; + } + + /** + * Builds the transient cache key for the current theme state. + * + * @return string Transient key. + */ + private function component_files_cache_key(): string { + $stylesheet = $this->stylesheet(); + $template = $this->template(); + $stylesheet_directory = function_exists( 'get_stylesheet_directory' ) ? get_stylesheet_directory() : ''; + $template_directory = function_exists( 'get_template_directory' ) ? get_template_directory() : ''; + $key_parts = array( + 'stylesheet' => $stylesheet, + 'template' => $template, + 'stylesheet_version' => $this->theme_version( $stylesheet, $stylesheet_directory ), + 'template_version' => $this->theme_version( $template, $template_directory ), + 'environment' => $this->environment_type(), + 'manifest' => $this->manifest_file_signature(), + ); + + /** + * Filters the component discovery persistent cache key parts. + * + * Projects that alter component roots dynamically can add their own + * version token here, or change it to invalidate cached discovery. + * + * @param array $key_parts Cache key parts. + * @param ComponentLocator $locator Component locator instance. + */ + $filtered = apply_filters( 'emulsify_theme_component_discovery_cache_key_parts', $key_parts, $this ); + + if ( is_array( $filtered ) ) { + $key_parts = $filtered; + } + + $encoded = function_exists( 'wp_json_encode' ) ? wp_json_encode( $key_parts ) : json_encode( $key_parts ); + + if ( ! is_string( $encoded ) ) { + $encoded = serialize( $key_parts ); + } + + return self::CACHE_TRANSIENT_PREFIX . md5( $encoded ); + } + + /** + * Gets the optional persistent cache lifetime. + * + * @return int Cache lifetime in seconds. + */ + private function component_files_cache_ttl(): int { + $ttl = defined( 'DAY_IN_SECONDS' ) ? (int) DAY_IN_SECONDS : self::DEFAULT_CACHE_TTL; + + /** + * Filters the component discovery persistent cache lifetime. + * + * @param int $ttl Cache lifetime in seconds. + * @param ComponentLocator $locator Component locator instance. + */ + $filtered = apply_filters( 'emulsify_theme_component_discovery_cache_ttl', $ttl, $this ); + + return is_numeric( $filtered ) ? max( 0, (int) $filtered ) : $ttl; + } + + /** + * Normalizes cached file records and rejects invalid cache payloads. + * + * @param array $files Cached component file records. + * @return array|null Normalized records, or null for invalid cache data. + */ + private function normalize_cached_component_files( array $files ): ?array { + $normalized = array(); + + foreach ( $files as $file ) { + if ( + ! is_array( $file ) + || empty( $file['path'] ) + || empty( $file['relative'] ) + || empty( $file['root_path'] ) + || empty( $file['root_source'] ) + || ! is_scalar( $file['path'] ) + || ! is_scalar( $file['relative'] ) + || ! is_scalar( $file['root_path'] ) + || ! is_scalar( $file['root_source'] ) + ) { + return null; + } + + $record = array( + 'path' => (string) $file['path'], + 'priority' => isset( $file['priority'] ) ? (int) $file['priority'] : 0, + 'relative' => (string) $file['relative'], + 'root_path' => (string) $file['root_path'], + 'root_source' => (string) $file['root_source'], + ); + + if ( isset( $file['root_uri'] ) && is_scalar( $file['root_uri'] ) ) { + $record['root_uri'] = (string) $file['root_uri']; + } + + $normalized[] = $record; + } + + return $normalized; + } + + /** + * Checks whether active development should keep the default cache disabled. + * + * @return bool TRUE when the environment looks like active development. + */ + private function is_development_environment(): bool { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + return true; + } + + return in_array( $this->environment_type(), array( 'local', 'development' ), true ); + } + + /** + * Gets the current WordPress environment type. + * + * @return string Environment type. + */ + private function environment_type(): string { + if ( function_exists( 'wp_get_environment_type' ) ) { + $environment = wp_get_environment_type(); + + if ( is_scalar( $environment ) && '' !== trim( (string) $environment ) ) { + return (string) $environment; + } + } + + return defined( 'WP_ENVIRONMENT_TYPE' ) && is_scalar( WP_ENVIRONMENT_TYPE ) ? (string) WP_ENVIRONMENT_TYPE : 'production'; + } + + /** + * Gets the active child stylesheet slug. + * + * @return string Stylesheet slug. + */ + private function stylesheet(): string { + if ( function_exists( 'get_stylesheet' ) ) { + $stylesheet = get_stylesheet(); + + if ( is_scalar( $stylesheet ) && '' !== trim( (string) $stylesheet ) ) { + return (string) $stylesheet; + } + } + + return function_exists( 'get_stylesheet_directory' ) ? basename( get_stylesheet_directory() ) : ''; + } + + /** + * Gets the active parent template slug. + * + * @return string Template slug. + */ + private function template(): string { + if ( function_exists( 'get_template' ) ) { + $template = get_template(); + + if ( is_scalar( $template ) && '' !== trim( (string) $template ) ) { + return (string) $template; + } + } + + return function_exists( 'get_template_directory' ) ? basename( get_template_directory() ) : ''; + } + + /** + * Gets a theme version from WordPress metadata or style.css. + * + * @param string $stylesheet Theme stylesheet slug. + * @param string $directory Theme directory. + * @return string Theme version. + */ + private function theme_version( string $stylesheet, string $directory ): string { + if ( '' !== $stylesheet && function_exists( 'wp_get_theme' ) ) { + $theme = wp_get_theme( $stylesheet ); + + if ( is_object( $theme ) && method_exists( $theme, 'get' ) ) { + $version = $theme->get( 'Version' ); + + if ( is_scalar( $version ) && '' !== trim( (string) $version ) ) { + return (string) $version; + } + } + } + + return $this->style_css_version( $directory ); + } + + /** + * Reads a Version header from a theme style.css file. + * + * @param string $directory Theme directory. + * @return string Version header, or empty string. + */ + private function style_css_version( string $directory ): string { + $path = rtrim( $directory, '/\\' ) . '/style.css'; + + if ( '' === $directory || ! is_readable( $path ) ) { + return ''; + } + + $contents = file_get_contents( $path ); + + if ( ! is_string( $contents ) || ! preg_match( '/^\s*\*?\s*Version:\s*(.+)$/mi', $contents, $matches ) ) { + return ''; + } + + return trim( $matches[1] ); + } + + /** + * Gets a child/parent manifest file signature for cache invalidation. + * + * @return string Manifest signature. + */ + private function manifest_file_signature(): string { + $relative_path = $this->manifest_relative_path(); + + if ( '' === $relative_path ) { + return ''; + } + + $signatures = array(); + $themes = array( + 'child' => function_exists( 'get_stylesheet_directory' ) ? get_stylesheet_directory() : '', + 'parent' => function_exists( 'get_template_directory' ) ? get_template_directory() : '', + ); + + foreach ( $themes as $source => $directory ) { + if ( ! is_scalar( $directory ) || '' === trim( (string) $directory ) ) { + continue; + } + + $path = rtrim( (string) $directory, '/\\' ) . '/' . $relative_path; + + if ( ! is_readable( $path ) ) { + continue; + } + + $mtime = filemtime( $path ); + $signatures[] = $source . ':' . $relative_path . ':' . ( false === $mtime ? '' : (string) $mtime ); + } + + return implode( '|', $signatures ); + } + + /** + * Gets the theme-relative asset manifest path. + * + * @return string Manifest path, or empty string when disabled. + */ + private function manifest_relative_path(): string { + $relative_path = self::DEFAULT_MANIFEST_PATH; + + /** + * Filters the theme-relative asset manifest path. + * + * This mirrors asset loading so changing the manifest path also changes + * the optional component discovery cache key. + * + * @param string $relative_path Theme-relative manifest path. + */ + $filtered = apply_filters( 'emulsify_theme_asset_manifest_path', $relative_path ); + + if ( ! is_scalar( $filtered ) ) { + return ''; + } + + return $this->normalize_relative_path( (string) $filtered ); + } + + /** + * Normalizes a safe relative path. + * + * @param string $path Candidate path. + * @return string Normalized path, or empty string when invalid. + */ + private function normalize_relative_path( string $path ): string { + $path = trim( str_replace( '\\', '/', $path ) ); + + if ( + '' === $path + || false !== strpos( $path, "\0" ) + || 0 === strpos( $path, '/' ) + || preg_match( '#(^|/)\.\.(/|$)#', $path ) + ) { + return ''; + } + + while ( 0 === strpos( $path, './' ) ) { + $path = substr( $path, 2 ); + } + + return $path; + } + /** * Gets child theme component roots first, then parent theme fallbacks. * From 62cf982194dd9e71e72054b8b8842c54388c63af Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 09:08:39 -0500 Subject: [PATCH 44/55] refactor: split editor policy services --- .github/scripts/bootstrap-loader-smoke.php | 6 + .github/scripts/editor-policy-smoke.php | 6 + .github/scripts/release-check.cjs | 31 +- docs/editor-policy.md | 2 +- includes/Editor/AllowedBlockTypes.php | 160 ++++++ includes/Editor/BlockNames.php | 58 ++ includes/Editor/BlockSupportOverrides.php | 123 +++++ includes/Editor/PatternGovernance.php | 240 ++++++++ includes/Editor/Policy.php | 606 +++------------------ includes/Editor/PolicyOptions.php | 71 +++ includes/Editor/UserPatternPermissions.php | 93 ++++ 11 files changed, 854 insertions(+), 542 deletions(-) create mode 100644 includes/Editor/AllowedBlockTypes.php create mode 100644 includes/Editor/BlockNames.php create mode 100644 includes/Editor/BlockSupportOverrides.php create mode 100644 includes/Editor/PatternGovernance.php create mode 100644 includes/Editor/PolicyOptions.php create mode 100644 includes/Editor/UserPatternPermissions.php diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php index 88e709e..ba9fea1 100644 --- a/.github/scripts/bootstrap-loader-smoke.php +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -62,8 +62,14 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { 'Emulsify\\Theme\\Blocks\\NativeBlocks', 'Emulsify\\Theme\\Blocks\\CoreBlockTwigRenderer', 'Emulsify\\Theme\\Blocks\\Patterns', + 'Emulsify\\Theme\\Editor\\AllowedBlockTypes', + 'Emulsify\\Theme\\Editor\\BlockNames', + 'Emulsify\\Theme\\Editor\\BlockSupportOverrides', 'Emulsify\\Theme\\Editor\\Enhancements', + 'Emulsify\\Theme\\Editor\\PatternGovernance', 'Emulsify\\Theme\\Editor\\Policy', + 'Emulsify\\Theme\\Editor\\PolicyOptions', + 'Emulsify\\Theme\\Editor\\UserPatternPermissions', 'Emulsify\\Theme\\Acf\\LocalJson', 'Emulsify\\Theme\\Cli\\GenerateChildThemeCommand', 'Emulsify\\Theme\\Support\\AttributeBag', diff --git a/.github/scripts/editor-policy-smoke.php b/.github/scripts/editor-policy-smoke.php index f464a62..3ba0946 100644 --- a/.github/scripts/editor-policy-smoke.php +++ b/.github/scripts/editor-policy-smoke.php @@ -147,6 +147,12 @@ function emulsify_editor_policy_smoke_remove( string $path ): void { $GLOBALS['emulsify_editor_policy_smoke_parent'] = $parent; try { + require_once $repo_root . '/includes/Editor/BlockNames.php'; + require_once $repo_root . '/includes/Editor/PolicyOptions.php'; + require_once $repo_root . '/includes/Editor/PatternGovernance.php'; + require_once $repo_root . '/includes/Editor/AllowedBlockTypes.php'; + require_once $repo_root . '/includes/Editor/UserPatternPermissions.php'; + require_once $repo_root . '/includes/Editor/BlockSupportOverrides.php'; require_once $repo_root . '/includes/Editor/Policy.php'; $policy = new Emulsify\Theme\Editor\Policy(); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 7a268ee..f40fcd6 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -330,8 +330,14 @@ function runStaticChecks() { 'includes/Blocks/Patterns.php', 'includes/Blocks/Registry.php', 'includes/Cli/GenerateChildThemeCommand.php', + 'includes/Editor/AllowedBlockTypes.php', + 'includes/Editor/BlockNames.php', + 'includes/Editor/BlockSupportOverrides.php', 'includes/Editor/Enhancements.php', + 'includes/Editor/PatternGovernance.php', 'includes/Editor/Policy.php', + 'includes/Editor/PolicyOptions.php', + 'includes/Editor/UserPatternPermissions.php', 'includes/Runtime/Assets.php', 'includes/Runtime/Context.php', 'includes/Runtime/MissingTimber.php', @@ -584,6 +590,11 @@ function runStaticChecks() { const setup = readFile('includes/Runtime/Setup.php'); const editorEnhancements = readFile('includes/Editor/Enhancements.php'); const editorPolicy = readFile('includes/Editor/Policy.php'); + const editorAllowedBlocks = readFile('includes/Editor/AllowedBlockTypes.php'); + const editorBlockSupport = readFile('includes/Editor/BlockSupportOverrides.php'); + const editorPatterns = readFile('includes/Editor/PatternGovernance.php'); + const editorPolicyOptions = readFile('includes/Editor/PolicyOptions.php'); + const editorUserPatterns = readFile('includes/Editor/UserPatternPermissions.php'); const patterns = readFile('includes/Blocks/Patterns.php'); const locator = readFile('includes/Blocks/ComponentLocator.php'); const assetManifest = readFile('includes/Support/AssetManifest.php'); @@ -631,7 +642,25 @@ function runStaticChecks() { 'emulsify_theme_pattern_namespaces', 'emulsify_theme_block_support_overrides', ]; - const runtimeText = [acfJson, assets, twig, context, setup, editorEnhancements, editorPolicy, patterns, locator, assetManifest, acfBlocks, nativeBlocks].join('\n'); + const runtimeText = [ + acfJson, + assets, + twig, + context, + setup, + editorEnhancements, + editorPolicy, + editorAllowedBlocks, + editorBlockSupport, + editorPatterns, + editorPolicyOptions, + editorUserPatterns, + patterns, + locator, + assetManifest, + acfBlocks, + nativeBlocks, + ].join('\n'); for (const filter of expectedFilters) { ensure(runtimeText.includes(filter), `${filter} should be registered in runtime PHP code.`); diff --git a/docs/editor-policy.md b/docs/editor-policy.md index 5a16409..3b074d3 100644 --- a/docs/editor-policy.md +++ b/docs/editor-policy.md @@ -1,6 +1,6 @@ # Editor policy -The parent theme includes an `Editor\Policy` service for project-specific block editor governance. It is intentionally no-op by default, so activating the parent theme does not change allowed blocks, visible patterns, user-created pattern behavior, `wp_block` capabilities, or registered block supports unless a child theme or project plugin configures the policy with filters. +The parent theme includes an `Editor\Policy` coordinator for project-specific block editor governance. It is intentionally no-op by default, so activating the parent theme does not change allowed blocks, visible patterns, user-created pattern behavior, `wp_block` capabilities, or registered block supports unless a child theme or project plugin configures the policy with filters. ## Configure policy options diff --git a/includes/Editor/AllowedBlockTypes.php b/includes/Editor/AllowedBlockTypes.php new file mode 100644 index 0000000..75cf7d2 --- /dev/null +++ b/includes/Editor/AllowedBlockTypes.php @@ -0,0 +1,160 @@ +options = $options; + $this->patterns = $patterns; + } + + /** + * Filters allowed block types for the current editor context. + * + * @param array|bool $allowed_block_types Existing WordPress allowed block types value. + * @param mixed $context Block editor context. + * @return array|bool Filtered allowed block types. + */ + public function filter( $allowed_block_types, $context ) { + $options = $this->options->get( $context ); + $post_type = $this->post_type( $context ); + $blocks = $this->configured_allowed_block_types( $options['allowed_block_types'] ?? null, $post_type ); + + /** + * Filters the resolved allowed block type list before WordPress receives it. + * + * Return null to preserve the incoming WordPress value. Return an array + * of block names to enable an allow list for the current editor context. + * + * @param array|null $blocks Resolved configured block names, or null for no policy. + * @param string $post_type Current post type when available. + * @param mixed $context Block editor context. + * @param array|bool $allowed_block_types Incoming WordPress allowed block types value. + * @param array $options Editor policy options. + */ + $filtered = apply_filters( 'emulsify_theme_allowed_block_types', $blocks, $post_type, $context, $allowed_block_types, $options ); + + if ( null === $filtered || ! is_array( $filtered ) ) { + // Null is the "no opinion" value. Returning the incoming WordPress value + // preserves default editor behavior unless a child theme configures a policy. + return $allowed_block_types; + } + + $blocks = BlockNames::normalize( $filtered ); + + if ( ! empty( $options['auto_allow_pattern_blocks'] ) ) { + // Pattern JSON can reference supporting blocks that are easy to forget + // in a manual allow list. This opt-in merge prevents configured patterns + // from becoming impossible to insert. + $blocks = array_merge( $blocks, $this->patterns->pattern_block_names( $options ) ); + } + + if ( is_array( $allowed_block_types ) && ! empty( $options['merge_allowed_block_types'] ) ) { + $blocks = array_merge( $allowed_block_types, $blocks ); + } + + return array_values( array_unique( BlockNames::normalize( $blocks ) ) ); + } + + /** + * Resolves configured allowed blocks for a post type. + * + * @param mixed $config Allowed block type configuration. + * @param string $post_type Post type when available. + * @return array|null Block names, or null when no policy is configured. + */ + private function configured_allowed_block_types( $config, string $post_type ): ?array { + if ( ! is_array( $config ) ) { + return null; + } + + if ( array_is_list( $config ) ) { + return $config; + } + + $blocks = array(); + $configured = false; + + foreach ( array( 'default', '*' ) as $key ) { + if ( array_key_exists( $key, $config ) && is_array( $config[ $key ] ) ) { + $blocks = array_merge( $blocks, $config[ $key ] ); + $configured = true; + } + } + + foreach ( array( 'by_post_type', 'post_types' ) as $group_key ) { + if ( '' === $post_type || ! isset( $config[ $group_key ] ) || ! is_array( $config[ $group_key ] ) ) { + continue; + } + + $post_type_blocks = array_key_exists( $post_type, $config[ $group_key ] ) ? $config[ $group_key ][ $post_type ] : null; + + if ( is_array( $post_type_blocks ) ) { + $blocks = array_merge( $blocks, $post_type_blocks ); + $configured = true; + } + } + + if ( '' !== $post_type && array_key_exists( $post_type, $config ) && is_array( $config[ $post_type ] ) ) { + $blocks = array_merge( $blocks, $config[ $post_type ] ); + $configured = true; + } + + return $configured ? $blocks : null; + } + + /** + * Gets the post type from a block editor context. + * + * @param mixed $context Block editor context. + * @return string Post type or an empty string. + */ + private function post_type( $context ): string { + if ( is_object( $context ) && isset( $context->post ) ) { + if ( function_exists( 'get_post_type' ) ) { + $post_type = get_post_type( $context->post ); + + return is_string( $post_type ) ? $post_type : ''; + } + + if ( is_object( $context->post ) && isset( $context->post->post_type ) && is_scalar( $context->post->post_type ) ) { + return (string) $context->post->post_type; + } + } + + if ( is_object( $context ) && isset( $context->post_type ) && is_scalar( $context->post_type ) ) { + return (string) $context->post_type; + } + + return ''; + } +} diff --git a/includes/Editor/BlockNames.php b/includes/Editor/BlockNames.php new file mode 100644 index 0000000..38cc5de --- /dev/null +++ b/includes/Editor/BlockNames.php @@ -0,0 +1,58 @@ +options = $options; + } + + /** + * Applies configured support/style overrides to metadata-derived settings. + * + * @param array $settings Block type metadata settings. + * @param array $metadata Block metadata. + * @return array Filtered metadata settings. + */ + public function block_type_metadata_settings( array $settings, array $metadata ): array { + $block_name = isset( $metadata['name'] ) && is_scalar( $metadata['name'] ) ? (string) $metadata['name'] : ''; + + return $this->apply( $settings, $block_name, 'block_type_metadata_settings' ); + } + + /** + * Applies configured support/style overrides to final block registration args. + * + * @param array $args Block type registration arguments. + * @param string $block_type Block type name. + * @return array Filtered block type registration arguments. + */ + public function register_block_type_args( array $args, string $block_type ): array { + return $this->apply( $args, $block_type, 'register_block_type_args' ); + } + + /** + * Applies configured block support/style overrides. + * + * @param array $settings Block type settings or registration arguments. + * @param string $block_name Block type name. + * @param string $source Current WordPress filter name. + * @return array Filtered block settings. + */ + private function apply( array $settings, string $block_name, string $source ): array { + $options = $this->options->get(); + $overrides = isset( $options['block_support_overrides'] ) && is_array( $options['block_support_overrides'] ) + ? $options['block_support_overrides'] + : array(); + + /** + * Filters block support/style overrides for a block registration pass. + * + * Use the "*" key for all blocks and a block name key such as + * "core/button" for block-specific changes. + * + * @param array $overrides Configured override map. + * @param string $block_name Current block name. + * @param array $settings Current settings or registration arguments. + * @param string $source Current WordPress filter name. + * @param array $options Editor policy options. + */ + $filtered = apply_filters( 'emulsify_theme_block_support_overrides', $overrides, $block_name, $settings, $source, $options ); + + if ( is_array( $filtered ) ) { + $overrides = $filtered; + } + + foreach ( array( '*', $block_name ) as $key ) { + if ( isset( $overrides[ $key ] ) && is_array( $overrides[ $key ] ) ) { + // Apply global overrides first and block-specific overrides second so + // a child theme can set broad defaults with targeted exceptions. + $settings = $this->merge_override( $settings, $overrides[ $key ] ); + } + } + + return $settings; + } + + /** + * Recursively applies an override array. + * + * @param array $base Base array. + * @param array $override Override array. + * @return array Merged array. + */ + private function merge_override( array $base, array $override ): array { + foreach ( $override as $key => $value ) { + if ( + array_key_exists( $key, $base ) + && is_array( $base[ $key ] ) + && is_array( $value ) + && ! array_is_list( $base[ $key ] ) + && ! array_is_list( $value ) + ) { + $base[ $key ] = $this->merge_override( $base[ $key ], $value ); + continue; + } + + $base[ $key ] = $value; + } + + return $base; + } +} diff --git a/includes/Editor/PatternGovernance.php b/includes/Editor/PatternGovernance.php new file mode 100644 index 0000000..97a3630 --- /dev/null +++ b/includes/Editor/PatternGovernance.php @@ -0,0 +1,240 @@ +pattern_namespaces( $options, $settings, $context ); + + if ( ! empty( $namespaces ) ) { + $settings['blockPatterns'] = $this->filter_block_patterns( $settings['blockPatterns'] ?? null, $namespaces ); + } + + return $settings; + } + + /** + * Gets block names referenced by configured pattern JSON files. + * + * @param array $options Editor policy options. + * @return array Block names. + */ + public function pattern_block_names( array $options ): array { + $names = array(); + + foreach ( $this->pattern_json_files( $options ) as $file ) { + $contents = file_get_contents( $file ); + + if ( ! is_string( $contents ) || '' === trim( $contents ) ) { + continue; + } + + $data = json_decode( $contents, true ); + + if ( ! is_array( $data ) || empty( $data['content'] ) || ! is_string( $data['content'] ) ) { + continue; + } + + $names = array_merge( $names, $this->block_names_from_content( $data['content'] ) ); + } + + return array_values( array_unique( $names ) ); + } + + /** + * Gets configured block pattern namespaces. + * + * @param array $options Editor policy options. + * @param array $settings Block editor settings. + * @param mixed $context Block editor context. + * @return array Pattern namespaces. + */ + private function pattern_namespaces( array $options, array $settings, $context ): array { + $namespaces = isset( $options['pattern_namespaces'] ) && is_array( $options['pattern_namespaces'] ) + ? $options['pattern_namespaces'] + : array(); + + /** + * Filters the block pattern namespaces that should stay visible. + * + * Return an empty array to preserve the default pattern list. + * + * @param array $namespaces Pattern namespaces, without trailing slashes. + * @param array $settings Block editor settings. + * @param mixed $context Block editor context. + * @param array $options Editor policy options. + */ + $filtered = apply_filters( 'emulsify_theme_pattern_namespaces', $namespaces, $settings, $context, $options ); + + if ( is_array( $filtered ) ) { + $namespaces = $filtered; + } + + return array_values( + array_unique( + array_filter( + array_map( + static function ( $namespace ): string { + return is_scalar( $namespace ) ? trim( strtolower( (string) $namespace ), " \t\n\r\0\x0B/" ) : ''; + }, + $namespaces + ) + ) + ) + ); + } + + /** + * Filters visible block pattern records by namespace. + * + * @param mixed $patterns Block pattern records. + * @param array $namespaces Allowed namespaces. + * @return array Filtered block pattern records. + */ + private function filter_block_patterns( $patterns, array $namespaces ): array { + if ( ! is_array( $patterns ) && class_exists( '\WP_Block_Patterns_Registry' ) ) { + $patterns = \WP_Block_Patterns_Registry::get_instance()->get_all_registered(); + } + + if ( ! is_array( $patterns ) ) { + return array(); + } + + $keep = array(); + + foreach ( $patterns as $key => $pattern ) { + $name = is_string( $key ) ? $key : ''; + + if ( is_array( $pattern ) && isset( $pattern['name'] ) && is_scalar( $pattern['name'] ) ) { + $name = (string) $pattern['name']; + } + + if ( $this->matches_namespace( $name, $namespaces ) ) { + $keep[] = $pattern; + } + } + + return $keep; + } + + /** + * Checks whether a pattern name is in an allowed namespace. + * + * @param string $name Pattern name. + * @param array $namespaces Allowed namespaces. + * @return bool TRUE when the namespace is allowed. + */ + private function matches_namespace( string $name, array $namespaces ): bool { + $name = strtolower( trim( $name ) ); + + foreach ( $namespaces as $namespace ) { + if ( 0 === strpos( $name, $namespace . '/' ) ) { + return true; + } + } + + return false; + } + + /** + * Gets pattern JSON files from configured pattern directories. + * + * @param array $options Editor policy options. + * @return array Absolute file paths. + */ + private function pattern_json_files( array $options ): array { + $directories = isset( $options['pattern_directories'] ) && is_array( $options['pattern_directories'] ) + ? $options['pattern_directories'] + : $this->default_pattern_directories(); + $files = array(); + $seen = array(); + + foreach ( $directories as $directory ) { + if ( ! is_scalar( $directory ) ) { + continue; + } + + $path = rtrim( (string) $directory, '/\\' ); + + if ( '' === $path || ! is_dir( $path ) || ! is_readable( $path ) ) { + continue; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( $path, \RecursiveDirectoryIterator::SKIP_DOTS ) + ); + + foreach ( $iterator as $file ) { + if ( ! $file->isFile() || 'json' !== strtolower( $file->getExtension() ) ) { + continue; + } + + $file_path = $file->getPathname(); + $key = realpath( $file_path ) ?: $file_path; + + if ( isset( $seen[ $key ] ) ) { + continue; + } + + $seen[ $key ] = true; + $files[] = $file_path; + } + } + + sort( $files ); + + return $files; + } + + /** + * Gets child and parent pattern directories. + * + * @return array Pattern directories. + */ + private function default_pattern_directories(): array { + $directories = array(); + + if ( function_exists( 'get_stylesheet_directory' ) ) { + $directories[] = rtrim( get_stylesheet_directory(), '/\\' ) . '/patterns'; + } + + if ( function_exists( 'get_template_directory' ) ) { + $directories[] = rtrim( get_template_directory(), '/\\' ) . '/patterns'; + } + + return array_values( array_unique( $directories ) ); + } + + /** + * Extracts block names from block comment markup. + * + * @param string $content Pattern block content. + * @return array Block names. + */ + private function block_names_from_content( string $content ): array { + if ( ! preg_match_all( '/

    Not a block pattern.

    ', + 'starter' => array( + 'label' => 'Child Starter', + ), + ) + ) + ); emulsify_pattern_smoke_write( $child . '/patterns/override.json', (string) json_encode( @@ -307,7 +331,7 @@ static function ( array $args, array $data ): array { emulsify_pattern_smoke_assert( array( 'child/hero', 'child/override', 'extra/promo' ) === array_keys( $GLOBALS['emulsify_pattern_smoke_patterns'] ), - 'Patterns service should register valid child-first and filtered JSON patterns.' + 'Patterns service should register valid child-first and filtered JSON patterns without registering category metadata files.' ); emulsify_pattern_smoke_assert( ! isset( $GLOBALS['emulsify_pattern_smoke_patterns']['parent/override'] ), @@ -325,10 +349,18 @@ static function ( array $args, array $data ): array { array( 'starter', 'extra' ) === array_keys( $GLOBALS['emulsify_pattern_smoke_categories'] ), 'Patterns service should register categories discovered from valid pattern metadata.' ); + emulsify_pattern_smoke_assert( + 'Child Starter' === $GLOBALS['emulsify_pattern_smoke_categories']['starter']['label'], + 'Child pattern category metadata should override parent category labels.' + ); emulsify_pattern_smoke_assert( 'Starter category from smoke coverage.' === $GLOBALS['emulsify_pattern_smoke_categories']['starter']['description'], 'Pattern categories filter should alter category registration args.' ); + emulsify_pattern_smoke_assert( + 'Extra' === $GLOBALS['emulsify_pattern_smoke_categories']['extra']['label'], + 'Pattern categories without metadata should fall back to a readable slug label.' + ); emulsify_pattern_smoke_assert( in_array( 'filtered', $GLOBALS['emulsify_pattern_smoke_patterns']['extra/promo']['keywords'], true ), 'Pattern args filter should alter final registration args.' diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index f40fcd6..aad1376 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -694,10 +694,12 @@ function runStaticChecks() { ensure(nativeBlocks.includes("register_block_type( (string) $component['path'] )"), 'Native blocks should delegate block.json asset fields to WordPress register_block_type().'); ensure(locator.includes('FileDiscovery::theme_roots') && locator.includes('FileDiscovery::file_records'), 'Component locator should use shared filesystem discovery helpers.'); ensure(patterns.includes('FileDiscovery::theme_roots') && patterns.includes("FileDiscovery::file_records( $this->pattern_directories(), array( 'json' ), false )"), 'Pattern discovery should use shared helpers while staying shallow.'); + ensure(patterns.includes('CATEGORY_METADATA_FILES') && patterns.includes('category_metadata'), 'Pattern discovery should support optional category metadata files.'); ensure(twig.includes('FileDiscovery::normalize_roots'), 'Twig project component roots should use shared root normalization.'); ensure(smoke.includes('Asset discovery should keep child roots before parent fallback roots'), 'Runtime filter smoke should verify asset root priority.'); ensure(editorEnhancementsSmoke.includes('skip duplicate parent relative paths'), 'Editor enhancements smoke should verify editor asset duplicate handling.'); ensure(patternSmoke.includes('Child pattern JSON files should override parent files with the same basename'), 'Pattern smoke should verify child-first pattern discovery.'); + ensure(patternSmoke.includes('Child pattern category metadata should override parent category labels') && patternSmoke.includes('metadata files'), 'Pattern smoke should cover category metadata and metadata-file skips.'); ensure(assetManifestSmoke.includes('No manifest should fall back') && assetManifestSmoke.includes('Invalid manifest should fall back') && assetManifestSmoke.includes('Child manifest should take priority'), 'Asset manifest smoke should cover fallback, invalid, and child-priority paths.'); ensure(blockAssetSmoke.includes('Manifest block assets should take priority') && blockAssetSmoke.includes('Native block.json asset fields should be left for WordPress') && blockAssetSmoke.includes('Global component scanning should remain the fallback'), 'Block scoped asset smoke should cover manifest priority, native block.json delegation, and scanner fallback.'); return 'Parent runtime exposes documented filters with smoke coverage.'; @@ -1073,6 +1075,7 @@ function runStaticChecks() { ensure(docs.nativeBlocks.includes('emulsify_theme_native_block_directories'), 'Native blocks doc should document the native block directories filter.'); ensure(docs.nativeBlocks.includes('style`, `script`, `viewScript`') && docs.nativeBlocks.includes('register_block_type()'), 'Native blocks doc should document WordPress-owned block.json asset loading.'); ensure(docs.blockPatterns.includes('patterns/*.json'), 'Block patterns doc should document JSON pattern discovery.'); + ensure(docs.blockPatterns.includes('patterns/categories.json') && docs.blockPatterns.includes('patterns/_categories.json'), 'Block patterns doc should document category metadata files.'); ensure(docs.blockPatterns.includes('[Component recipes](component-recipes.md)'), 'Block patterns doc should link to component recipes.'); ensure(docs.blockPatterns.includes('emulsify_theme_pattern_directories'), 'Block patterns doc should document directory filtering.'); ensure(docs.blockPatterns.includes('emulsify_theme_pattern_args'), 'Block patterns doc should document final args filtering.'); diff --git a/docs/block-patterns.md b/docs/block-patterns.md index c4ab0a9..2b3151c 100644 --- a/docs/block-patterns.md +++ b/docs/block-patterns.md @@ -1,6 +1,6 @@ # Block patterns -The parent theme registers JSON block patterns from active child and parent theme `patterns` directories. Discovery is child-first and scans direct `patterns/*.json` files only, so a child theme can override a parent pattern file by using the same JSON filename. +The parent theme registers JSON block patterns from active child and parent theme `patterns` directories. Discovery is child-first and scans direct `patterns/*.json` files only, so a child theme can override a parent pattern file by using the same JSON filename. `patterns/categories.json` and `patterns/_categories.json` are reserved for optional category metadata and are not registered as block patterns. If no pattern directory exists, or if WordPress pattern registration functions are unavailable, the service exits without changing editor behavior. @@ -37,6 +37,21 @@ Optional fields: Invalid JSON files, missing required fields, duplicate filenames, and duplicate pattern names are skipped safely. Diagnostics are logged only when `WP_DEBUG` is enabled. +## Category metadata + +Categories discovered from pattern JSON receive a readable fallback label from the slug. Projects can refine labels and descriptions with `patterns/categories.json` or `patterns/_categories.json`: + +```json +{ + "featured": { + "label": "Featured", + "description": "Featured content layouts." + } +} +``` + +Child theme category metadata extends parent metadata and overrides matching fields. Category metadata files are optional; only categories referenced by registered patterns are registered. + ## Filters Use `emulsify_theme_pattern_directories` to add or replace scanned directories: diff --git a/includes/Blocks/Patterns.php b/includes/Blocks/Patterns.php index e222442..8d351a5 100644 --- a/includes/Blocks/Patterns.php +++ b/includes/Blocks/Patterns.php @@ -14,6 +14,13 @@ */ final class Patterns { + /** + * Supported pattern category metadata filenames. + * + * @var array + */ + private const CATEGORY_METADATA_FILES = array( 'categories.json', '_categories.json' ); + /** * Registers pattern hooks. * @@ -109,6 +116,10 @@ private function pattern_files(): array { $relative = basename( $path ); $real = realpath( $path ) ?: $path; + if ( $this->is_category_metadata_file( $relative ) ) { + continue; + } + if ( isset( $seen_file_path[ $real ] ) ) { continue; } @@ -322,6 +333,7 @@ private function metadata_value( string $key, $value ) { */ private function category_records( array $patterns ): array { $categories = array(); + $metadata = $this->category_metadata(); foreach ( $patterns as $pattern ) { foreach ( $pattern['args']['categories'] ?? array() as $category ) { @@ -335,6 +347,10 @@ private function category_records( array $patterns ): array { 'label' => $this->label_from_slug( $category ), 'description' => '', ); + + if ( isset( $metadata[ $category ] ) ) { + $categories[ $category ] = array_merge( $categories[ $category ], $metadata[ $category ] ); + } } } @@ -351,6 +367,93 @@ private function category_records( array $patterns ): array { return is_array( $filtered ) ? $filtered : $categories; } + /** + * Gets merged child-over-parent pattern category metadata. + * + * @return array Category metadata keyed by slug. + */ + private function category_metadata(): array { + $metadata = array(); + + foreach ( array_reverse( $this->pattern_directories() ) as $directory ) { + if ( empty( $directory['path'] ) || ! is_scalar( $directory['path'] ) ) { + continue; + } + + foreach ( self::CATEGORY_METADATA_FILES as $filename ) { + $path = rtrim( (string) $directory['path'], '/\\' ) . '/' . $filename; + + if ( ! is_readable( $path ) ) { + continue; + } + + foreach ( $this->category_metadata_file( $path ) as $slug => $category ) { + $metadata[ $slug ] = array_merge( $metadata[ $slug ] ?? array(), $category ); + } + } + } + + return $metadata; + } + + /** + * Reads one category metadata file. + * + * @param string $path Metadata file path. + * @return array Category metadata keyed by slug. + */ + private function category_metadata_file( string $path ): array { + $contents = file_get_contents( $path ); + + if ( ! is_string( $contents ) ) { + $this->debug( sprintf( 'Could not read block pattern category metadata file: %s.', $path ) ); + return array(); + } + + $data = json_decode( $contents, true ); + + if ( ! is_array( $data ) ) { + $this->debug( sprintf( 'Could not decode block pattern category metadata file: %s.', $path ) ); + return array(); + } + + $metadata = array(); + + foreach ( $data as $slug => $category ) { + $slug = $this->category_name( $slug ); + + if ( '' === $slug || ! is_array( $category ) ) { + continue; + } + + $record = array_filter( + array( + 'label' => $this->string_value( $category['label'] ?? ( $category['title'] ?? '' ) ), + 'description' => $this->string_value( $category['description'] ?? '' ), + ), + static function ( string $value ): bool { + return '' !== $value; + } + ); + + if ( ! empty( $record ) ) { + $metadata[ $slug ] = $record; + } + } + + return $metadata; + } + + /** + * Checks whether a pattern JSON file is category metadata. + * + * @param string $relative File basename. + * @return bool TRUE when the file stores category metadata. + */ + private function is_category_metadata_file( string $relative ): bool { + return in_array( strtolower( $relative ), self::CATEGORY_METADATA_FILES, true ); + } + /** * Registers block pattern categories when WordPress supports them. * From 855199beea6676a2b0ef94800b63d21faa852331 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 20:28:03 -0500 Subject: [PATCH 46/55] fix(blocks): use block-scoped acf field values in twig context --- includes/Blocks/AcfBlocks.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/includes/Blocks/AcfBlocks.php b/includes/Blocks/AcfBlocks.php index 196732a..01ad74b 100644 --- a/includes/Blocks/AcfBlocks.php +++ b/includes/Blocks/AcfBlocks.php @@ -193,7 +193,7 @@ public function render_block( $block, $content = '', $is_preview = false, $post_ $context = \Timber\Timber::context(); $context['block'] = $block; $context['content'] = is_scalar( $content ) ? (string) $content : ''; - $context['fields'] = $this->fields( $post_id ); + $context['fields'] = $this->fields(); $context['is_preview'] = (bool) $is_preview; try { @@ -980,17 +980,16 @@ private function duplicate_message( array $duplicate ): string { } /** - * Gets ACF fields when ACF is active. + * Gets ACF field values for the block being rendered. * - * @param mixed $post_id Current post ID. * @return array ACF field values. */ - private function fields( $post_id ): array { + private function fields(): array { if ( ! function_exists( 'get_fields' ) ) { return array(); } - $fields = get_fields( $post_id ); + $fields = get_fields(); return is_array( $fields ) ? $fields : array(); } From 56a258a5b134d1ac0c873e874cf33571254550ac Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 20:42:32 -0500 Subject: [PATCH 47/55] refactor(support): consolidate shared asset and diagnostics helpers --- .github/scripts/asset-manifest-smoke.php | 2 + .github/scripts/block-scoped-assets-smoke.php | 5 +- .github/scripts/bootstrap-loader-smoke.php | 3 + .github/scripts/component-locator-smoke.php | 4 + .github/scripts/editor-enhancements-smoke.php | 4 +- .github/scripts/theme-filters-smoke.php | 5 +- includes/Blocks/AcfBlocks.php | 389 +----------------- includes/Blocks/ComponentLocator.php | 57 +-- includes/Blocks/NativeBlocks.php | 100 +---- includes/Editor/Enhancements.php | 36 +- includes/Runtime/Assets.php | 102 +---- includes/Support/AssetEnqueuer.php | 129 ++++++ includes/Support/AssetManifest.php | 203 +-------- includes/Support/AssetRecord.php | 235 +++++++++++ includes/Support/Diagnostics.php | 119 ++++++ 15 files changed, 570 insertions(+), 823 deletions(-) create mode 100644 includes/Support/AssetEnqueuer.php create mode 100644 includes/Support/AssetRecord.php create mode 100644 includes/Support/Diagnostics.php diff --git a/.github/scripts/asset-manifest-smoke.php b/.github/scripts/asset-manifest-smoke.php index a3c1ba9..d4a89ef 100644 --- a/.github/scripts/asset-manifest-smoke.php +++ b/.github/scripts/asset-manifest-smoke.php @@ -205,6 +205,8 @@ function emulsify_asset_manifest_smoke_remove( string $path ): void { try { require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Support/AssetRecord.php'; + require_once $repo_root . '/includes/Support/AssetEnqueuer.php'; require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Runtime/Assets.php'; require_once $repo_root . '/includes/Editor/Enhancements.php'; diff --git a/.github/scripts/block-scoped-assets-smoke.php b/.github/scripts/block-scoped-assets-smoke.php index 9f98ca5..173c975 100644 --- a/.github/scripts/block-scoped-assets-smoke.php +++ b/.github/scripts/block-scoped-assets-smoke.php @@ -332,8 +332,11 @@ function emulsify_block_assets_smoke_remove( string $path ): void { ) ); - require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Support/AssetRecord.php'; + require_once $repo_root . '/includes/Support/AssetEnqueuer.php'; + require_once $repo_root . '/includes/Support/Diagnostics.php'; + require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Runtime/Assets.php'; require_once $repo_root . '/includes/Blocks/ComponentLocator.php'; require_once $repo_root . '/includes/Blocks/AcfBlocks.php'; diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php index ba9fea1..dc92f49 100644 --- a/.github/scripts/bootstrap-loader-smoke.php +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -73,6 +73,9 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { 'Emulsify\\Theme\\Acf\\LocalJson', 'Emulsify\\Theme\\Cli\\GenerateChildThemeCommand', 'Emulsify\\Theme\\Support\\AttributeBag', + 'Emulsify\\Theme\\Support\\AssetRecord', + 'Emulsify\\Theme\\Support\\AssetEnqueuer', + 'Emulsify\\Theme\\Support\\Diagnostics', ); $legacy_classes = array( 'Emulsify\\Theme\\Setup', diff --git a/.github/scripts/component-locator-smoke.php b/.github/scripts/component-locator-smoke.php index c85e87e..5d57aa6 100644 --- a/.github/scripts/component-locator-smoke.php +++ b/.github/scripts/component-locator-smoke.php @@ -314,6 +314,8 @@ static function ( array $args ): string { emulsify_locator_smoke_write( $parent . '/dist/components/parent-native/block.json', '{"name":"emulsify/parent-native"}' ); require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Support/AssetRecord.php'; + require_once $repo_root . '/includes/Support/Diagnostics.php'; require_once $repo_root . '/includes/Blocks/ComponentLocator.php'; $locator = new Emulsify\Theme\Blocks\ComponentLocator(); @@ -388,6 +390,8 @@ static function ( array $args ): string { emulsify_locator_smoke_write( $child . '/dist/components/acf-name-b/acf-name-b.component.json', '{"name":"emulsify/shared-acf","title":"Shared B"}' ); emulsify_locator_smoke_write( $child . '/dist/components/acf-name-b/acf-name-b.twig', '
    Shared B
    ' ); + require_once $repo_root . '/includes/Support/AssetEnqueuer.php'; + require_once $repo_root . '/includes/Support/Diagnostics.php'; require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Blocks/AcfBlocks.php'; diff --git a/.github/scripts/editor-enhancements-smoke.php b/.github/scripts/editor-enhancements-smoke.php index 03e13f3..05a1f5d 100644 --- a/.github/scripts/editor-enhancements-smoke.php +++ b/.github/scripts/editor-enhancements-smoke.php @@ -218,8 +218,10 @@ function emulsify_editor_enhancements_smoke_remove( string $path ): void { $GLOBALS['emulsify_editor_enhancements_smoke_parent'] = $parent; try { - require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Support/AssetRecord.php'; + require_once $repo_root . '/includes/Support/AssetEnqueuer.php'; + require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Editor/Enhancements.php'; $service = new Emulsify\Theme\Editor\Enhancements(); diff --git a/.github/scripts/theme-filters-smoke.php b/.github/scripts/theme-filters-smoke.php index 58a308c..502a885 100644 --- a/.github/scripts/theme-filters-smoke.php +++ b/.github/scripts/theme-filters-smoke.php @@ -306,8 +306,11 @@ function emulsify_filter_smoke_remove( string $path ): void { emulsify_filter_smoke_write( $extra . '/native/block.json', '{"name":"emulsify/filter-native"}' ); require_once $repo_root . '/includes/Support/AttributeBag.php'; - require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Support/FileDiscovery.php'; + require_once $repo_root . '/includes/Support/AssetRecord.php'; + require_once $repo_root . '/includes/Support/AssetEnqueuer.php'; + require_once $repo_root . '/includes/Support/Diagnostics.php'; + require_once $repo_root . '/includes/Support/AssetManifest.php'; require_once $repo_root . '/includes/Runtime/Assets.php'; require_once $repo_root . '/includes/Runtime/Context.php'; require_once $repo_root . '/includes/Runtime/Setup.php'; diff --git a/includes/Blocks/AcfBlocks.php b/includes/Blocks/AcfBlocks.php index 01ad74b..f8df973 100644 --- a/includes/Blocks/AcfBlocks.php +++ b/includes/Blocks/AcfBlocks.php @@ -7,7 +7,10 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\AssetEnqueuer; use Emulsify\Theme\Support\AssetManifest; +use Emulsify\Theme\Support\AssetRecord; +use Emulsify\Theme\Support\Diagnostics; use Emulsify\Theme\Support\FileDiscovery; /** @@ -115,7 +118,7 @@ public function register_blocks(): void { if ( '' !== $name && isset( $seen_names[ $name ] ) ) { // Duplicate names can happen after filters alter metadata. Register // the first child-first record and report the skipped one in debug. - $this->record_skipped_duplicate( + $this->skipped_duplicates[] = Diagnostics::duplicate_record( 'acf_block_name', $name, $seen_names[ $name ], @@ -126,7 +129,7 @@ public function register_blocks(): void { } if ( '' !== $name && $this->acf_block_registered( $name ) ) { - $this->record_skipped_duplicate( + $this->skipped_duplicates[] = Diagnostics::duplicate_record( 'acf_registered_block_name', $name, array( @@ -146,11 +149,12 @@ public function register_blocks(): void { acf_register_block_type( $args ); } - $this->debug_skipped_duplicates( + Diagnostics::report_duplicates( array_merge( $this->components->skipped_duplicates( 'acf' ), $this->skipped_duplicates - ) + ), + function_exists( '__' ) ? __( 'Emulsify skipped duplicate ACF block definitions.', 'emulsify' ) : 'Emulsify skipped duplicate ACF block definitions.' ); } @@ -272,10 +276,10 @@ private function with_asset_records( array $args, array $component, array $metad $filtered = apply_filters( 'emulsify_theme_acf_block_asset_records', $asset_records, $component, $metadata, $args ); if ( is_array( $filtered ) ) { - $asset_records = $this->normalize_asset_context_records( $filtered ); + $asset_records = AssetRecord::normalize_context_records( $filtered ); } - if ( ! $this->has_asset_records( $asset_records ) ) { + if ( ! AssetRecord::has_context_records( $asset_records ) ) { return $args; } @@ -299,12 +303,12 @@ private function block_asset_records( array $component, array $metadata, array $ ); if ( is_array( $manifest_records ) ) { - return $this->normalize_asset_context_records( $manifest_records ); + return AssetRecord::normalize_context_records( $manifest_records ); } $metadata_records = $this->metadata_asset_records( $component, $metadata ); - return is_array( $metadata_records ) ? $metadata_records : $this->empty_asset_context_records(); + return is_array( $metadata_records ) ? $metadata_records : AssetRecord::empty_context_records(); } /** @@ -361,7 +365,7 @@ private function metadata_asset_records( array $component, array $metadata ): ?a $base_uri = $this->component_base_uri( $component, $base_path ); if ( '' === $base_uri || '' === $base_path ) { - return $this->empty_asset_context_records(); + return AssetRecord::empty_context_records(); } return $this->metadata_context_records( $metadata['assets'], $base_path, $base_uri, $component ); @@ -377,7 +381,7 @@ private function metadata_asset_records( array $component, array $metadata ): ?a * @return array Scoped asset records. */ private function metadata_context_records( array $assets, string $base_path, string $base_uri, array $component ): array { - $records = $this->empty_asset_context_records(); + $records = AssetRecord::empty_context_records(); if ( isset( $assets['frontend'] ) || isset( $assets['editor'] ) ) { if ( isset( $assets['frontend'] ) && is_array( $assets['frontend'] ) ) { @@ -443,7 +447,7 @@ private function metadata_type_records( array $assets, string $base_path, string */ private function metadata_asset_record( $entry, string $type, string $base_path, string $base_uri, array $component ): ?array { $data = is_array( $entry ) ? $entry : array( 'path' => $entry ); - $relative = $this->entry_path( $data ); + $relative = AssetRecord::entry_path( $data ); if ( '' === $relative || strtolower( pathinfo( $relative, PATHINFO_EXTENSION ) ) !== $type ) { return null; @@ -458,11 +462,11 @@ private function metadata_asset_record( $entry, string $type, string $base_path, return array( 'path' => $path, 'priority' => 0, - 'relative' => $this->entry_relative( $data, $relative ), + 'relative' => AssetRecord::entry_relative( $data, $relative ), 'uri' => rtrim( $base_uri, '/' ) . '/' . $relative, - 'version' => $this->entry_version( $data, $path ), - 'dependencies' => $this->entry_dependencies( $data ), - 'module' => $this->entry_module( $data ), + 'version' => AssetRecord::entry_version( $data, $path ), + 'dependencies' => AssetRecord::entry_dependencies( $data ), + 'module' => AssetRecord::entry_module( $data ), 'source' => isset( $component['source'] ) ? (string) $component['source'] : '', ); } @@ -533,16 +537,7 @@ private function enqueue_asset_records( array $records, string $block_name, stri * @return void */ private function enqueue_style_record( array $record, string $block_name, string $context ): void { - if ( empty( $record['uri'] ) || ! function_exists( 'wp_enqueue_style' ) ) { - return; - } - - wp_enqueue_style( - $this->asset_handle( 'emulsify-acf-' . $context, $block_name, $record ), - (string) $record['uri'], - $this->asset_dependencies( $record ), - $record['version'] ?? null - ); + AssetEnqueuer::enqueue_style( 'emulsify-acf-' . $context . '-' . $block_name, $record ); } /** @@ -554,75 +549,7 @@ private function enqueue_style_record( array $record, string $block_name, string * @return void */ private function enqueue_script_record( array $record, string $block_name, string $context ): void { - if ( empty( $record['uri'] ) ) { - return; - } - - $handle = $this->asset_handle( 'emulsify-acf-' . $context, $block_name, $record ); - - if ( $this->is_module_script( $record ) && function_exists( 'wp_enqueue_script_module' ) ) { - wp_enqueue_script_module( - $handle, - (string) $record['uri'], - $this->asset_dependencies( $record ), - $record['version'] ?? null - ); - return; - } - - if ( ! function_exists( 'wp_enqueue_script' ) ) { - return; - } - - wp_enqueue_script( - $handle, - (string) $record['uri'], - $this->asset_dependencies( $record ), - $record['version'] ?? null, - array( - 'in_footer' => true, - ) - ); - } - - /** - * Builds a WordPress-safe scoped asset handle. - * - * @param string $prefix Handle prefix. - * @param string $block_name ACF block name. - * @param array $record Asset record. - * @return string Asset handle. - */ - private function asset_handle( string $prefix, string $block_name, array $record ): string { - if ( ! empty( $record['handle'] ) && is_scalar( $record['handle'] ) ) { - return sanitize_key( (string) $record['handle'] ); - } - - $relative = isset( $record['relative'] ) && is_scalar( $record['relative'] ) ? (string) $record['relative'] : basename( (string) ( $record['path'] ?? 'asset' ) ); - $name = preg_replace( '/\.(css|js)$/', '', $relative ); - $name = preg_replace( '/[^A-Za-z0-9_-]+/', '-', (string) $name ); - - return sanitize_key( $prefix . '-' . $block_name . '-' . trim( (string) $name, '-' ) ); - } - - /** - * Gets dependency handles from an asset record. - * - * @param array $record Asset record. - * @return array Dependency handles. - */ - private function asset_dependencies( array $record ): array { - return isset( $record['dependencies'] ) && is_array( $record['dependencies'] ) ? $record['dependencies'] : array(); - } - - /** - * Checks whether a script should be enqueued as a module. - * - * @param array $record Asset record. - * @return bool TRUE when the script is a module. - */ - private function is_module_script( array $record ): bool { - return ! array_key_exists( 'module', $record ) || null === $record['module'] ? true : (bool) $record['module']; + AssetEnqueuer::enqueue_script( 'emulsify-acf-' . $context . '-' . $block_name, $record ); } /** @@ -634,191 +561,6 @@ private function is_editor_context(): bool { return function_exists( 'is_admin' ) && is_admin(); } - /** - * Gets an empty scoped asset record set. - * - * @return array Empty records. - */ - private function empty_asset_context_records(): array { - return array( - 'frontend' => array( - 'css' => array(), - 'js' => array(), - ), - 'editor' => array( - 'css' => array(), - 'js' => array(), - ), - ); - } - - /** - * Normalizes scoped asset record groups. - * - * @param array $records Candidate records. - * @return array Normalized records. - */ - private function normalize_asset_context_records( array $records ): array { - $normalized = $this->empty_asset_context_records(); - - foreach ( array( 'frontend', 'editor' ) as $context ) { - if ( empty( $records[ $context ] ) || ! is_array( $records[ $context ] ) ) { - continue; - } - - foreach ( array( 'css', 'js' ) as $type ) { - $normalized[ $context ][ $type ] = isset( $records[ $context ][ $type ] ) && is_array( $records[ $context ][ $type ] ) - ? array_values( $records[ $context ][ $type ] ) - : array(); - } - } - - return $normalized; - } - - /** - * Checks whether any scoped asset records are present. - * - * @param array $records Scoped records. - * @return bool TRUE when records are present. - */ - private function has_asset_records( array $records ): bool { - foreach ( array( 'frontend', 'editor' ) as $context ) { - foreach ( array( 'css', 'js' ) as $type ) { - if ( ! empty( $records[ $context ][ $type ] ) ) { - return true; - } - } - } - - return false; - } - - /** - * Gets an asset entry path. - * - * @param array $entry Asset entry. - * @return string Relative path. - */ - private function entry_path( array $entry ): string { - foreach ( array( 'path', 'file', 'src', 'href' ) as $key ) { - if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) ) { - return $this->normalize_relative_asset_path( (string) $entry[ $key ] ); - } - } - - return ''; - } - - /** - * Gets the record-relative path. - * - * @param array $entry Asset entry. - * @param string $fallback Fallback relative path. - * @return string Relative path. - */ - private function entry_relative( array $entry, string $fallback ): string { - if ( isset( $entry['relative'] ) && is_scalar( $entry['relative'] ) ) { - $relative = $this->normalize_relative_asset_path( (string) $entry['relative'] ); - - if ( '' !== $relative ) { - return $relative; - } - } - - return $fallback; - } - - /** - * Gets the asset version. - * - * @param array $entry Asset entry. - * @param string $path Absolute asset path. - * @return string|null Version. - */ - private function entry_version( array $entry, string $path ): ?string { - foreach ( array( 'version', 'hash' ) as $key ) { - if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) && '' !== trim( (string) $entry[ $key ] ) ) { - return (string) $entry[ $key ]; - } - } - - $modified = filemtime( $path ); - - return false === $modified ? null : (string) $modified; - } - - /** - * Gets normalized dependency handles. - * - * @param array $entry Asset entry. - * @return array Dependency handles. - */ - private function entry_dependencies( array $entry ): array { - $dependencies = $entry['dependencies'] ?? ( $entry['deps'] ?? array() ); - - if ( ! is_array( $dependencies ) ) { - return array(); - } - - return array_values( - array_filter( - array_map( - static function ( $dependency ) { - return is_scalar( $dependency ) ? trim( (string) $dependency ) : null; - }, - $dependencies - ), - static function ( $dependency ): bool { - return is_string( $dependency ) && '' !== $dependency; - } - ) - ); - } - - /** - * Gets an optional script module flag. - * - * @param array $entry Asset entry. - * @return bool|null Module flag, or null to use service default. - */ - private function entry_module( array $entry ): ?bool { - if ( array_key_exists( 'module', $entry ) ) { - return ! empty( $entry['module'] ); - } - - if ( array_key_exists( 'type', $entry ) && is_scalar( $entry['type'] ) ) { - return 'module' === strtolower( trim( (string) $entry['type'] ) ); - } - - return null; - } - - /** - * Normalizes a relative asset path. - * - * @param string $path Candidate path. - * @return string Safe relative path. - */ - private function normalize_relative_asset_path( string $path ): string { - $path = trim( str_replace( '\\', '/', $path ) ); - - if ( - '' === $path - || false !== strpos( $path, "\0" ) - || 0 === strpos( $path, '/' ) - || preg_match( '#(^|/)\.\.(/|$)#', $path ) - ) { - return ''; - } - - while ( 0 === strpos( $path, './' ) ) { - $path = substr( $path, 2 ); - } - - return $path; - } - /** * Normalizes an ACF PHP block name to an ACF-safe un-namespaced slug. * @@ -890,95 +632,6 @@ private function component_record( array $component, array $args ): array { ); } - /** - * Records a skipped duplicate ACF block. - * - * @param string $type Duplicate type. - * @param string $name Duplicate block name. - * @param array $kept Higher-priority record. - * @param array $skipped Lower-priority skipped record. - * @param string $reason Human-readable reason. - * @return void - */ - private function record_skipped_duplicate( string $type, string $name, array $kept, array $skipped, string $reason ): void { - $this->skipped_duplicates[] = array( - 'type' => $type, - 'name' => $name, - 'reason' => $reason, - 'kept' => $kept, - 'skipped' => $skipped, - ); - } - - /** - * Logs duplicate block records and optionally exposes admin notices. - * - * @param array $duplicates Duplicate records. - * @return void - */ - private function debug_skipped_duplicates( array $duplicates ): void { - if ( empty( $duplicates ) || ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { - return; - } - - $messages = array(); - - foreach ( $duplicates as $duplicate ) { - $messages[] = $this->duplicate_message( $duplicate ); - error_log( '[Emulsify] ' . end( $messages ) ); - } - - $this->admin_notice( $messages ); - } - - /** - * Adds an admin-only notice for skipped duplicate blocks. - * - * @param array $messages Notice messages. - * @return void - */ - private function admin_notice( array $messages ): void { - if ( empty( $messages ) || ! function_exists( 'add_action' ) || ! function_exists( 'is_admin' ) || ! is_admin() ) { - return; - } - - add_action( - 'admin_notices', - static function () use ( $messages ): void { - if ( function_exists( 'current_user_can' ) && ! current_user_can( 'edit_theme_options' ) ) { - return; - } - - echo '

    ' . esc_html__( 'Emulsify skipped duplicate ACF block definitions.', 'emulsify' ) . '

      '; - - foreach ( $messages as $message ) { - echo '
    • ' . esc_html( $message ) . '
    • '; - } - - echo '
    '; - } - ); - } - - /** - * Formats a duplicate debug message. - * - * @param array $duplicate Duplicate record. - * @return string Debug message. - */ - private function duplicate_message( array $duplicate ): string { - $kept = isset( $duplicate['kept']['metadata_path'] ) ? $duplicate['kept']['metadata_path'] : ( $duplicate['kept']['name'] ?? 'unknown' ); - $skipped = isset( $duplicate['skipped']['metadata_path'] ) ? $duplicate['skipped']['metadata_path'] : ( $duplicate['skipped']['name'] ?? 'unknown' ); - - return sprintf( - '%s "%s" skipped %s in favor of %s.', - isset( $duplicate['reason'] ) ? $duplicate['reason'] : 'Duplicate block definition.', - isset( $duplicate['name'] ) ? $duplicate['name'] : 'unknown', - $skipped, - $kept - ); - } - /** * Gets ACF field values for the block being rendered. * diff --git a/includes/Blocks/ComponentLocator.php b/includes/Blocks/ComponentLocator.php index 38e0292..367b06f 100644 --- a/includes/Blocks/ComponentLocator.php +++ b/includes/Blocks/ComponentLocator.php @@ -7,6 +7,8 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\AssetRecord; +use Emulsify\Theme\Support\Diagnostics; use Emulsify\Theme\Support\FileDiscovery; /** @@ -118,7 +120,7 @@ public function acf_components(): array { $record = $this->component_record( $file, $directory, $relative, $path ); if ( isset( $seen_paths[ $key ] ) ) { - $this->record_skipped_duplicate( + $this->skipped_duplicates[] = Diagnostics::duplicate_record( 'acf_component_path', $key, $seen_paths[ $key ], @@ -140,7 +142,7 @@ public function acf_components(): array { $slug = $this->slug( $relative, $path ); if ( isset( $seen_slugs[ $slug ] ) ) { - $this->record_skipped_duplicate( + $this->skipped_duplicates[] = Diagnostics::duplicate_record( 'acf_component_slug', $slug, $seen_slugs[ $slug ], @@ -201,7 +203,7 @@ public function native_block_directories(): array { } if ( isset( $seen_paths[ $key ] ) ) { - $this->record_skipped_duplicate( + $this->skipped_duplicates[] = Diagnostics::duplicate_record( 'native_component_path', $key, $seen_paths[ $key ], @@ -212,7 +214,7 @@ public function native_block_directories(): array { } if ( '' !== $name && isset( $seen_names[ $name ] ) ) { - $this->record_skipped_duplicate( + $this->skipped_duplicates[] = Diagnostics::duplicate_record( 'native_block_name', $name, $seen_names[ $name ], @@ -642,32 +644,7 @@ private function manifest_relative_path(): string { return ''; } - return $this->normalize_relative_path( (string) $filtered ); - } - - /** - * Normalizes a safe relative path. - * - * @param string $path Candidate path. - * @return string Normalized path, or empty string when invalid. - */ - private function normalize_relative_path( string $path ): string { - $path = trim( str_replace( '\\', '/', $path ) ); - - if ( - '' === $path - || false !== strpos( $path, "\0" ) - || 0 === strpos( $path, '/' ) - || preg_match( '#(^|/)\.\.(/|$)#', $path ) - ) { - return ''; - } - - while ( 0 === strpos( $path, './' ) ) { - $path = substr( $path, 2 ); - } - - return $path; + return AssetRecord::normalize_relative_path( (string) $filtered ); } /** @@ -731,26 +708,6 @@ private function component_record( array $file, string $directory, string $relat ); } - /** - * Records a skipped duplicate component artifact. - * - * @param string $type Duplicate type. - * @param string $name Duplicate key or block name. - * @param array $kept Higher-priority record. - * @param array $skipped Lower-priority skipped record. - * @param string $reason Human-readable reason. - * @return void - */ - private function record_skipped_duplicate( string $type, string $name, array $kept, array $skipped, string $reason ): void { - $this->skipped_duplicates[] = array( - 'type' => $type, - 'name' => $name, - 'reason' => $reason, - 'kept' => $kept, - 'skipped' => $skipped, - ); - } - /** * Gets the declared block name from a native block.json file. * diff --git a/includes/Blocks/NativeBlocks.php b/includes/Blocks/NativeBlocks.php index 5e5e63a..43b2d2e 100644 --- a/includes/Blocks/NativeBlocks.php +++ b/includes/Blocks/NativeBlocks.php @@ -7,6 +7,8 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\Diagnostics; + /** * Native WordPress Block API integration. */ @@ -80,26 +82,26 @@ public function register_blocks(): void { if ( '' !== $name && isset( $seen_names[ $name ] ) ) { // Filters may merge or rename block directories. Keep the first // discovered name and surface later duplicates only in debug/admin contexts. - $skipped[] = array( - 'type' => 'native_filtered_block_name', - 'name' => $name, - 'reason' => 'Duplicate filtered native block name.', - 'kept' => $seen_names[ $name ], - 'skipped' => $component, + $skipped[] = Diagnostics::duplicate_record( + 'native_filtered_block_name', + $name, + $seen_names[ $name ], + $component, + 'Duplicate filtered native block name.' ); continue; } if ( '' !== $name && $this->native_block_registered( $name ) ) { - $skipped[] = array( - 'type' => 'native_registered_block_name', - 'name' => $name, - 'reason' => 'Native block name is already registered.', - 'kept' => array( + $skipped[] = Diagnostics::duplicate_record( + 'native_registered_block_name', + $name, + array( 'name' => $name, 'source' => 'existing', ), - 'skipped' => $component, + $component, + 'Native block name is already registered.' ); continue; } @@ -111,11 +113,12 @@ public function register_blocks(): void { register_block_type( (string) $component['path'] ); } - $this->debug_skipped_duplicates( + Diagnostics::report_duplicates( array_merge( $this->components->skipped_duplicates( 'native' ), $skipped - ) + ), + function_exists( '__' ) ? __( 'Emulsify skipped duplicate native block definitions.', 'emulsify' ) : 'Emulsify skipped duplicate native block definitions.' ); } @@ -129,73 +132,4 @@ private function native_block_registered( string $name ): bool { return class_exists( '\WP_Block_Type_Registry' ) && \WP_Block_Type_Registry::get_instance()->is_registered( $name ); } - - /** - * Logs duplicate block records and optionally exposes admin notices. - * - * @param array $duplicates Duplicate records. - * @return void - */ - private function debug_skipped_duplicates( array $duplicates ): void { - if ( empty( $duplicates ) || ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { - return; - } - - $messages = array(); - - foreach ( $duplicates as $duplicate ) { - $messages[] = $this->duplicate_message( $duplicate ); - error_log( '[Emulsify] ' . end( $messages ) ); - } - - $this->admin_notice( $messages ); - } - - /** - * Adds an admin-only notice for skipped duplicate blocks. - * - * @param array $messages Notice messages. - * @return void - */ - private function admin_notice( array $messages ): void { - if ( empty( $messages ) || ! function_exists( 'add_action' ) || ! function_exists( 'is_admin' ) || ! is_admin() ) { - return; - } - - add_action( - 'admin_notices', - static function () use ( $messages ): void { - if ( function_exists( 'current_user_can' ) && ! current_user_can( 'edit_theme_options' ) ) { - return; - } - - echo '

    ' . esc_html__( 'Emulsify skipped duplicate native block definitions.', 'emulsify' ) . '

      '; - - foreach ( $messages as $message ) { - echo '
    • ' . esc_html( $message ) . '
    • '; - } - - echo '
    '; - } - ); - } - - /** - * Formats a duplicate debug message. - * - * @param array $duplicate Duplicate record. - * @return string Debug message. - */ - private function duplicate_message( array $duplicate ): string { - $kept = isset( $duplicate['kept']['metadata_path'] ) ? $duplicate['kept']['metadata_path'] : ( $duplicate['kept']['name'] ?? 'unknown' ); - $skipped = isset( $duplicate['skipped']['metadata_path'] ) ? $duplicate['skipped']['metadata_path'] : ( $duplicate['skipped']['name'] ?? 'unknown' ); - - return sprintf( - '%s "%s" skipped %s in favor of %s.', - isset( $duplicate['reason'] ) ? $duplicate['reason'] : 'Duplicate block definition.', - isset( $duplicate['name'] ) ? $duplicate['name'] : 'unknown', - $skipped, - $kept - ); - } } diff --git a/includes/Editor/Enhancements.php b/includes/Editor/Enhancements.php index 04c6b30..38998e0 100644 --- a/includes/Editor/Enhancements.php +++ b/includes/Editor/Enhancements.php @@ -7,6 +7,7 @@ namespace Emulsify\Theme\Editor; +use Emulsify\Theme\Support\AssetEnqueuer; use Emulsify\Theme\Support\AssetManifest; use Emulsify\Theme\Support\FileDiscovery; @@ -37,16 +38,11 @@ public function editor_assets(): void { // The parent ships the enqueue/runtime support only. A child theme or // selected component system decides whether any editor bundle exists. foreach ( $this->asset_files( 'dist/global/editor', array( 'css' ) ) as $asset ) { - wp_enqueue_style( - $this->handle( 'emulsify-editor', $asset['relative'] ), - $asset['uri'], - $this->dependencies( $asset ), - $asset['version'] - ); + AssetEnqueuer::enqueue_style( 'emulsify-editor', $asset ); } foreach ( $this->asset_files( 'dist/global/editor', array( 'js' ) ) as $asset ) { - $handle = $this->handle( 'emulsify-editor', $asset['relative'] ); + $handle = AssetEnqueuer::handle( 'emulsify-editor', $asset ); wp_enqueue_script( $handle, @@ -374,7 +370,7 @@ private function editor_script_dependencies( array $asset = array() ): array { array_unique( array_merge( $dependencies, - $this->dependencies( $asset ) + AssetEnqueuer::dependencies( $asset ) ) ) ); @@ -476,30 +472,6 @@ private function string_list( $values ): array { return array_values( array_unique( $strings ) ); } - /** - * Builds a WordPress-safe asset handle. - * - * @param string $prefix Handle prefix. - * @param string $relative Asset path relative to its built directory. - * @return string Asset handle. - */ - private function handle( string $prefix, string $relative ): string { - $name = preg_replace( '/\.(css|js)$/', '', $relative ); - $name = preg_replace( '/[^A-Za-z0-9_-]+/', '-', (string) $name ); - - return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); - } - - /** - * Gets dependency handles from an asset record. - * - * @param array $asset Asset record. - * @return array Dependency handles. - */ - private function dependencies( array $asset ): array { - return isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(); - } - /** * Gets a filemtime-based asset version. * diff --git a/includes/Runtime/Assets.php b/includes/Runtime/Assets.php index a42a895..341ac22 100644 --- a/includes/Runtime/Assets.php +++ b/includes/Runtime/Assets.php @@ -7,7 +7,9 @@ namespace Emulsify\Theme\Runtime; +use Emulsify\Theme\Support\AssetEnqueuer; use Emulsify\Theme\Support\AssetManifest; +use Emulsify\Theme\Support\AssetRecord; use Emulsify\Theme\Support\FileDiscovery; /** @@ -68,14 +70,7 @@ public function frontend_scripts(): void { */ private function enqueue_styles( string $prefix, string $directory ): void { foreach ( $this->asset_files( $directory, array( 'css' ) ) as $asset ) { - $handle = $this->handle( $prefix, $asset['relative'] ); - - wp_enqueue_style( - $handle, - $asset['uri'], - $this->dependencies( $asset ), - $asset['version'] - ); + AssetEnqueuer::enqueue_style( $prefix, $asset ); } } @@ -88,27 +83,7 @@ private function enqueue_styles( string $prefix, string $directory ): void { */ private function enqueue_scripts( string $prefix, string $directory ): void { foreach ( $this->asset_files( $directory, array( 'js' ) ) as $asset ) { - $handle = $this->handle( $prefix, $asset['relative'] ); - - if ( $this->is_module_script( $asset ) && function_exists( 'wp_enqueue_script_module' ) ) { - wp_enqueue_script_module( - $handle, - $asset['uri'], - $this->dependencies( $asset ), - $asset['version'] - ); - continue; - } - - wp_enqueue_script( - $handle, - $asset['uri'], - $this->dependencies( $asset ), - $asset['version'], - array( - 'in_footer' => true, - ) - ); + AssetEnqueuer::enqueue_script( $prefix, $asset ); } } @@ -255,40 +230,6 @@ private function asset_roots( string $directory ): array { ); } - /** - * Builds a WordPress-safe asset handle. - * - * @param string $prefix Handle prefix. - * @param string $relative Asset path relative to its built directory. - * @return string Asset handle. - */ - private function handle( string $prefix, string $relative ): string { - $name = preg_replace( '/\.(css|js)$/', '', $relative ); - $name = preg_replace( '/[^A-Za-z0-9_-]+/', '-', (string) $name ); - - return sanitize_key( $prefix . '-' . trim( (string) $name, '-' ) ); - } - - /** - * Gets dependency handles from an asset record. - * - * @param array $asset Asset record. - * @return array Dependency handles. - */ - private function dependencies( array $asset ): array { - return isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] ) ? $asset['dependencies'] : array(); - } - - /** - * Checks whether a script should be enqueued as a script module. - * - * @param array $asset Asset record. - * @return bool TRUE when script module enqueueing should be used. - */ - private function is_module_script( array $asset ): bool { - return ! array_key_exists( 'module', $asset ) || null === $asset['module'] ? true : (bool) $asset['module']; - } - /** * Checks whether a built asset is reserved for editor-only loading. * @@ -314,7 +255,7 @@ private function is_scoped_component_asset( string $directory, array $file ): bo } $root_relative = isset( $file['relative'] ) && is_scalar( $file['relative'] ) - ? $this->normalize_relative_asset_path( (string) $file['relative'] ) + ? AssetRecord::normalize_relative_path( (string) $file['relative'] ) : ''; if ( '' !== $root_relative && isset( $this->manifest_component_asset_paths()[ $root_relative ] ) ) { @@ -426,38 +367,7 @@ private function metadata_asset_paths( array $assets ): array { private function metadata_asset_path( $entry ): string { $entry = is_array( $entry ) ? $entry : array( 'path' => $entry ); - foreach ( array( 'path', 'file', 'src', 'href' ) as $key ) { - if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) ) { - return $this->normalize_relative_asset_path( (string) $entry[ $key ] ); - } - } - - return ''; - } - - /** - * Normalizes a safe relative asset path. - * - * @param string $path Candidate path. - * @return string Safe relative path, or empty string when invalid. - */ - private function normalize_relative_asset_path( string $path ): string { - $path = trim( str_replace( '\\', '/', $path ) ); - - if ( - '' === $path - || false !== strpos( $path, "\0" ) - || 0 === strpos( $path, '/' ) - || preg_match( '#(^|/)\.\.(/|$)#', $path ) - ) { - return ''; - } - - while ( 0 === strpos( $path, './' ) ) { - $path = substr( $path, 2 ); - } - - return $path; + return AssetRecord::entry_path( $entry ); } /** diff --git a/includes/Support/AssetEnqueuer.php b/includes/Support/AssetEnqueuer.php new file mode 100644 index 0000000..6cd4466 --- /dev/null +++ b/includes/Support/AssetEnqueuer.php @@ -0,0 +1,129 @@ + true, + ) + ); + } + + /** + * Gets dependency handles from an asset record. + * + * @param array $record Asset record. + * @return array Dependency handles. + */ + public static function dependencies( array $record ): array { + return isset( $record['dependencies'] ) && is_array( $record['dependencies'] ) ? $record['dependencies'] : array(); + } + + /** + * Checks whether a script should be enqueued as a module. + * + * @param array $record Asset record. + * @return bool TRUE when the script is a module. + */ + public static function is_module_script( array $record ): bool { + return ! array_key_exists( 'module', $record ) || null === $record['module'] ? true : (bool) $record['module']; + } + + /** + * Sanitizes a WordPress asset handle. + * + * @param string $handle Raw handle. + * @return string Sanitized handle. + */ + private static function sanitize_key( string $handle ): string { + return function_exists( 'sanitize_key' ) ? sanitize_key( $handle ) : strtolower( preg_replace( '/[^a-z0-9_-]+/', '', $handle ) ); + } +} diff --git a/includes/Support/AssetManifest.php b/includes/Support/AssetManifest.php index e2041df..926cf1a 100644 --- a/includes/Support/AssetManifest.php +++ b/includes/Support/AssetManifest.php @@ -63,13 +63,13 @@ public function scoped_asset_records( array $identifiers ): ?array { $assets = $this->asset_data( $manifest ); $identifiers = $this->normalize_identifiers( $identifiers ); - $records = $this->empty_context_records(); + $records = AssetRecord::empty_context_records(); $declared = false; foreach ( $identifiers as $identifier ) { if ( isset( $assets['blocks'] ) && is_array( $assets['blocks'] ) && array_key_exists( $identifier, $assets['blocks'] ) ) { $declared = true; - $records = $this->merge_context_records( + $records = AssetRecord::merge_context_records( $records, $this->context_section_records( $assets['blocks'][ $identifier ], $manifest, $identifier ) ); @@ -77,14 +77,14 @@ public function scoped_asset_records( array $identifiers ): ?array { if ( isset( $assets['components'] ) && is_array( $assets['components'] ) && array_key_exists( $identifier, $assets['components'] ) ) { $declared = true; - $records = $this->merge_context_records( + $records = AssetRecord::merge_context_records( $records, $this->context_section_records( $assets['components'][ $identifier ], $manifest, $identifier ) ); } } - return $declared ? $this->sort_context_records( $records ) : null; + return $declared ? AssetRecord::sort_context_records( $records ) : null; } /** @@ -204,7 +204,7 @@ private function section_records( $section, array $extensions, array $manifest, * @return array Context asset records. */ private function context_section_records( $section, array $manifest, string $block_name = '' ): array { - $records = $this->empty_context_records(); + $records = AssetRecord::empty_context_records(); if ( ! is_array( $section ) ) { return $records; @@ -272,7 +272,7 @@ private function section_paths( array $section ): array { foreach ( $section[ $type ] as $entry ) { $data = is_array( $entry ) ? $entry : array( 'path' => $entry ); - $path = $this->component_relative_path( $this->entry_path( $data ) ); + $path = $this->component_relative_path( AssetRecord::entry_path( $data ) ); if ( '' !== $path ) { $paths[] = $path; @@ -319,60 +319,6 @@ private function is_asset_section( $section ): bool { return is_array( $section ) && ( array_key_exists( 'css', $section ) || array_key_exists( 'js', $section ) ); } - /** - * Gets an empty frontend/editor record set. - * - * @return array Empty context records. - */ - private function empty_context_records(): array { - return array( - 'frontend' => array( - 'css' => array(), - 'js' => array(), - ), - 'editor' => array( - 'css' => array(), - 'js' => array(), - ), - ); - } - - /** - * Merges two context asset record sets. - * - * @param array $base Base records. - * @param array $add Records to add. - * @return array Merged records. - */ - private function merge_context_records( array $base, array $add ): array { - foreach ( array( 'frontend', 'editor' ) as $context ) { - foreach ( array( 'css', 'js' ) as $type ) { - $base[ $context ][ $type ] = array_merge( - $base[ $context ][ $type ] ?? array(), - $add[ $context ][ $type ] ?? array() - ); - } - } - - return $base; - } - - /** - * Sorts context asset records. - * - * @param array $records Context records. - * @return array Sorted context records. - */ - private function sort_context_records( array $records ): array { - foreach ( array( 'frontend', 'editor' ) as $context ) { - foreach ( array( 'css', 'js' ) as $type ) { - $records[ $context ][ $type ] = FileDiscovery::sort_by_priority_and_relative( $records[ $context ][ $type ] ?? array() ); - } - } - - return $records; - } - /** * Normalizes scoped asset identifiers. * @@ -408,7 +354,7 @@ private function normalize_identifiers( array $identifiers ): array { */ private function asset_record( $entry, string $extension, array $manifest, string $block_name = '' ): ?array { $data = is_array( $entry ) ? $entry : array( 'path' => $entry ); - $relative = $this->entry_path( $data ); + $relative = AssetRecord::entry_path( $data ); if ( '' === $relative || strtolower( pathinfo( $relative, PATHINFO_EXTENSION ) ) !== $extension ) { return null; @@ -423,11 +369,11 @@ private function asset_record( $entry, string $extension, array $manifest, strin $record = array( 'path' => $path, 'priority' => $manifest['priority'], - 'relative' => $this->entry_relative( $data, $relative ), + 'relative' => AssetRecord::entry_relative( $data, $relative ), 'uri' => $manifest['base_uri'] . '/' . $relative, - 'version' => $this->entry_version( $data, $path ), - 'dependencies' => $this->entry_dependencies( $data ), - 'module' => $this->entry_module( $data ), + 'version' => AssetRecord::entry_version( $data, $path ), + 'dependencies' => AssetRecord::entry_dependencies( $data ), + 'module' => AssetRecord::entry_module( $data ), 'source' => $manifest['source'], 'manifest_path' => $manifest['path'], ); @@ -507,7 +453,7 @@ private function manifest_candidates(): array { return array(); } - $relative_path = $this->normalize_relative_path( (string) $filtered ); + $relative_path = AssetRecord::normalize_relative_path( (string) $filtered ); if ( '' === $relative_path ) { return array(); @@ -560,129 +506,4 @@ private function candidate( string $theme_path, string $theme_uri, string $relat 'source' => $source, ); } - - /** - * Gets an asset entry path. - * - * @param array $entry Manifest asset entry. - * @return string Entry path relative to the manifest directory. - */ - private function entry_path( array $entry ): string { - foreach ( array( 'path', 'file', 'src', 'href' ) as $key ) { - if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) ) { - return $this->normalize_relative_path( (string) $entry[ $key ] ); - } - } - - return ''; - } - - /** - * Gets the record-relative path used for handle generation. - * - * @param array $entry Manifest asset entry. - * @param string $fallback Fallback relative path. - * @return string Relative path. - */ - private function entry_relative( array $entry, string $fallback ): string { - if ( isset( $entry['relative'] ) && is_scalar( $entry['relative'] ) ) { - $relative = $this->normalize_relative_path( (string) $entry['relative'] ); - - if ( '' !== $relative ) { - return $relative; - } - } - - return $fallback; - } - - /** - * Gets the asset version from explicit metadata or filemtime. - * - * @param array $entry Manifest asset entry. - * @param string $path Absolute asset path. - * @return string|null Asset version. - */ - private function entry_version( array $entry, string $path ): ?string { - foreach ( array( 'version', 'hash' ) as $key ) { - if ( isset( $entry[ $key ] ) && is_scalar( $entry[ $key ] ) && '' !== trim( (string) $entry[ $key ] ) ) { - return (string) $entry[ $key ]; - } - } - - $modified = filemtime( $path ); - - return false === $modified ? null : (string) $modified; - } - - /** - * Gets normalized dependency handles. - * - * @param array $entry Manifest asset entry. - * @return array Dependency handles. - */ - private function entry_dependencies( array $entry ): array { - $dependencies = $entry['dependencies'] ?? ( $entry['deps'] ?? array() ); - - if ( ! is_array( $dependencies ) ) { - return array(); - } - - return array_values( - array_filter( - array_map( - static function ( $dependency ) { - return is_scalar( $dependency ) ? trim( (string) $dependency ) : null; - }, - $dependencies - ), - static function ( $dependency ): bool { - return is_string( $dependency ) && '' !== $dependency; - } - ) - ); - } - - /** - * Gets an optional script module flag. - * - * @param array $entry Manifest asset entry. - * @return bool|null Module flag, or null to use service default. - */ - private function entry_module( array $entry ): ?bool { - if ( array_key_exists( 'module', $entry ) ) { - return ! empty( $entry['module'] ); - } - - if ( array_key_exists( 'type', $entry ) && is_scalar( $entry['type'] ) ) { - return 'module' === strtolower( trim( (string) $entry['type'] ) ); - } - - return null; - } - - /** - * Normalizes safe relative paths. - * - * @param string $path Relative path. - * @return string Normalized relative path, or empty string when invalid. - */ - private function normalize_relative_path( string $path ): string { - $path = trim( str_replace( '\\', '/', $path ) ); - - if ( - '' === $path - || false !== strpos( $path, "\0" ) - || 0 === strpos( $path, '/' ) - || preg_match( '#(^|/)\.\.(/|$)#', $path ) - ) { - return ''; - } - - while ( 0 === strpos( $path, './' ) ) { - $path = substr( $path, 2 ); - } - - return $path; - } } diff --git a/includes/Support/AssetRecord.php b/includes/Support/AssetRecord.php new file mode 100644 index 0000000..d53efc9 --- /dev/null +++ b/includes/Support/AssetRecord.php @@ -0,0 +1,235 @@ + array( + 'css' => array(), + 'js' => array(), + ), + 'editor' => array( + 'css' => array(), + 'js' => array(), + ), + ); + } + + /** + * Normalizes scoped asset record groups. + * + * @param array $records Candidate records. + * @return array Normalized records. + */ + public static function normalize_context_records( array $records ): array { + $normalized = self::empty_context_records(); + + foreach ( array( 'frontend', 'editor' ) as $context ) { + if ( empty( $records[ $context ] ) || ! is_array( $records[ $context ] ) ) { + continue; + } + + foreach ( array( 'css', 'js' ) as $type ) { + $normalized[ $context ][ $type ] = isset( $records[ $context ][ $type ] ) && is_array( $records[ $context ][ $type ] ) + ? array_values( $records[ $context ][ $type ] ) + : array(); + } + } + + return $normalized; + } + + /** + * Merges two context asset record sets. + * + * @param array $base Base records. + * @param array $add Records to add. + * @return array Merged records. + */ + public static function merge_context_records( array $base, array $add ): array { + foreach ( array( 'frontend', 'editor' ) as $context ) { + foreach ( array( 'css', 'js' ) as $type ) { + $base[ $context ][ $type ] = array_merge( + $base[ $context ][ $type ] ?? array(), + $add[ $context ][ $type ] ?? array() + ); + } + } + + return $base; + } + + /** + * Sorts context asset records. + * + * @param array $records Context records. + * @return array Sorted context records. + */ + public static function sort_context_records( array $records ): array { + foreach ( array( 'frontend', 'editor' ) as $context ) { + foreach ( array( 'css', 'js' ) as $type ) { + $records[ $context ][ $type ] = FileDiscovery::sort_by_priority_and_relative( $records[ $context ][ $type ] ?? array() ); + } + } + + return $records; + } + + /** + * Checks whether any scoped asset records are present. + * + * @param array $records Scoped records. + * @return bool TRUE when records are present. + */ + public static function has_context_records( array $records ): bool { + foreach ( array( 'frontend', 'editor' ) as $context ) { + foreach ( array( 'css', 'js' ) as $type ) { + if ( ! empty( $records[ $context ][ $type ] ) ) { + return true; + } + } + } + + return false; + } +} diff --git a/includes/Support/Diagnostics.php b/includes/Support/Diagnostics.php new file mode 100644 index 0000000..de3c86e --- /dev/null +++ b/includes/Support/Diagnostics.php @@ -0,0 +1,119 @@ + $type, + 'name' => $name, + 'reason' => $reason, + 'kept' => $kept, + 'skipped' => $skipped, + ); + } + + /** + * Logs duplicate records and optionally exposes admin notices. + * + * @param array $duplicates Duplicate records. + * @param string $heading Admin notice heading. + * @return void + */ + public static function report_duplicates( array $duplicates, string $heading ): void { + if ( empty( $duplicates ) || ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { + return; + } + + $messages = array(); + + foreach ( $duplicates as $duplicate ) { + if ( ! is_array( $duplicate ) ) { + continue; + } + + $messages[] = self::duplicate_message( $duplicate ); + error_log( '[Emulsify] ' . end( $messages ) ); + } + + self::admin_notice( $messages, $heading ); + } + + /** + * Adds an admin-only notice for skipped duplicates. + * + * @param array $messages Notice messages. + * @param string $heading Notice heading. + * @return void + */ + private static function admin_notice( array $messages, string $heading ): void { + if ( empty( $messages ) || ! function_exists( 'add_action' ) || ! function_exists( 'is_admin' ) || ! is_admin() ) { + return; + } + + add_action( + 'admin_notices', + static function () use ( $messages, $heading ): void { + if ( function_exists( 'current_user_can' ) && ! current_user_can( 'edit_theme_options' ) ) { + return; + } + + echo '

    ' . self::esc_html( $heading ) . '

      '; + + foreach ( $messages as $message ) { + echo '
    • ' . self::esc_html( $message ) . '
    • '; + } + + echo '
    '; + } + ); + } + + /** + * Formats a duplicate debug message. + * + * @param array $duplicate Duplicate record. + * @return string Debug message. + */ + private static function duplicate_message( array $duplicate ): string { + $kept = isset( $duplicate['kept']['metadata_path'] ) ? $duplicate['kept']['metadata_path'] : ( $duplicate['kept']['name'] ?? 'unknown' ); + $skipped = isset( $duplicate['skipped']['metadata_path'] ) ? $duplicate['skipped']['metadata_path'] : ( $duplicate['skipped']['name'] ?? 'unknown' ); + + return sprintf( + '%s "%s" skipped %s in favor of %s.', + isset( $duplicate['reason'] ) ? $duplicate['reason'] : 'Duplicate block definition.', + isset( $duplicate['name'] ) ? $duplicate['name'] : 'unknown', + $skipped, + $kept + ); + } + + /** + * Escapes HTML text. + * + * @param string $value Raw text. + * @return string Escaped text. + */ + private static function esc_html( string $value ): string { + return function_exists( 'esc_html' ) ? esc_html( $value ) : htmlspecialchars( $value, ENT_QUOTES, 'UTF-8' ); + } +} From 8e3f716378ea32159dd80ea856b4bf286d260606 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 20:49:45 -0500 Subject: [PATCH 48/55] perf(runtime): memoize manifest reads and asset discovery per request --- .github/scripts/bootstrap-loader-smoke.php | 2 + .github/scripts/editor-enhancements-smoke.php | 7 ++ includes/Blocks/AcfBlocks.php | 13 ++- includes/Blocks/Registry.php | 20 +++- includes/Bootstrap.php | 9 +- includes/Editor/Enhancements.php | 33 ++++++- includes/Runtime/Assets.php | 92 ++++++++++++++++++- includes/Support/AssetManifest.php | 30 ++++++ 8 files changed, 195 insertions(+), 11 deletions(-) diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php index dc92f49..d4c03e6 100644 --- a/.github/scripts/bootstrap-loader-smoke.php +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -76,6 +76,8 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { 'Emulsify\\Theme\\Support\\AssetRecord', 'Emulsify\\Theme\\Support\\AssetEnqueuer', 'Emulsify\\Theme\\Support\\Diagnostics', + 'Emulsify\\Theme\\Support\\AssetManifest', + 'Emulsify\\Theme\\Support\\FileDiscovery', ); $legacy_classes = array( 'Emulsify\\Theme\\Setup', diff --git a/.github/scripts/editor-enhancements-smoke.php b/.github/scripts/editor-enhancements-smoke.php index 05a1f5d..ca89421 100644 --- a/.github/scripts/editor-enhancements-smoke.php +++ b/.github/scripts/editor-enhancements-smoke.php @@ -249,6 +249,10 @@ function emulsify_editor_enhancements_smoke_remove( string $path ): void { 'File captions should be no-op by default.' ); + foreach ( array( 'enqueue_block_editor_assets', 'render_block_data', 'render_block' ) as $hook ) { + unset( $GLOBALS['emulsify_editor_enhancements_smoke_hooks'][ $hook ] ); + } + add_filter( 'emulsify_theme_editor_enhancements_config', static function ( array $config ): array { @@ -266,6 +270,9 @@ static function ( array $config ): array { } ); + $service = new Emulsify\Theme\Editor\Enhancements(); + $service->register(); + $asset_directories_seen = false; $asset_files_seen = false; diff --git a/includes/Blocks/AcfBlocks.php b/includes/Blocks/AcfBlocks.php index f8df973..96d222e 100644 --- a/includes/Blocks/AcfBlocks.php +++ b/includes/Blocks/AcfBlocks.php @@ -25,6 +25,13 @@ final class AcfBlocks { */ private $components; + /** + * Asset manifest reader. + * + * @var AssetManifest + */ + private $manifest; + /** * Duplicate ACF block registrations skipped by this registrar. * @@ -36,9 +43,11 @@ final class AcfBlocks { * Constructor. * * @param ComponentLocator|null $components Component locator. + * @param AssetManifest|null $manifest Asset manifest reader. */ - public function __construct( ?ComponentLocator $components = null ) { + public function __construct( ?ComponentLocator $components = null, ?AssetManifest $manifest = null ) { $this->components = $components ?? new ComponentLocator(); + $this->manifest = $manifest ?? new AssetManifest(); } /** @@ -298,7 +307,7 @@ private function with_asset_records( array $args, array $component, array $metad * @return array Scoped asset records. */ private function block_asset_records( array $component, array $metadata, array $args ): array { - $manifest_records = ( new AssetManifest() )->scoped_asset_records( + $manifest_records = $this->manifest->scoped_asset_records( $this->asset_identifiers( $component, $metadata, $args ) ); diff --git a/includes/Blocks/Registry.php b/includes/Blocks/Registry.php index f2821c1..b29afb7 100644 --- a/includes/Blocks/Registry.php +++ b/includes/Blocks/Registry.php @@ -7,11 +7,29 @@ namespace Emulsify\Theme\Blocks; +use Emulsify\Theme\Support\AssetManifest; + /** * Registers optional block integrations. */ final class Registry { + /** + * Asset manifest reader. + * + * @var AssetManifest + */ + private $manifest; + + /** + * Constructor. + * + * @param AssetManifest|null $manifest Asset manifest reader. + */ + public function __construct( ?AssetManifest $manifest = null ) { + $this->manifest = $manifest ?? new AssetManifest(); + } + /** * Registers block integrations. * @@ -22,7 +40,7 @@ public function register(): void { // Share one locator so ACF/Twig and native block registration use the same // request-local child-first component index and duplicate diagnostics. - ( new AcfBlocks( $components ) )->register(); + ( new AcfBlocks( $components, $this->manifest ) )->register(); ( new NativeBlocks( $components ) )->register(); } } diff --git a/includes/Bootstrap.php b/includes/Bootstrap.php index c6bfd12..9024d2d 100644 --- a/includes/Bootstrap.php +++ b/includes/Bootstrap.php @@ -19,6 +19,7 @@ use Emulsify\Theme\Runtime\Setup; use Emulsify\Theme\Runtime\TimberIntegration; use Emulsify\Theme\Runtime\Twig; +use Emulsify\Theme\Support\AssetManifest; /** * Coordinates theme services. @@ -61,17 +62,19 @@ private function register(): void { $this->load_vendor_autoload(); $this->load_classes(); + $asset_manifest = new AssetManifest(); + // These services use WordPress APIs directly and must stay available even // when Timber is missing. The MissingTimber service owns frontend failure // handling later in this method. ( new Setup() )->register(); - ( new Assets() )->register(); + ( new Assets( $asset_manifest ) )->register(); ( new LocalJson() )->register(); ( new CoreBlockTwigRenderer() )->register(); - ( new Enhancements() )->register(); + ( new Enhancements( $asset_manifest ) )->register(); ( new Policy() )->register(); ( new Patterns() )->register(); - ( new Blocks\Registry() )->register(); + ( new Blocks\Registry( $asset_manifest ) )->register(); ( new GenerateChildThemeCommand() )->register(); $timber = new TimberIntegration(); diff --git a/includes/Editor/Enhancements.php b/includes/Editor/Enhancements.php index 38998e0..a244dfe 100644 --- a/includes/Editor/Enhancements.php +++ b/includes/Editor/Enhancements.php @@ -16,6 +16,29 @@ */ final class Enhancements { + /** + * Asset manifest reader. + * + * @var AssetManifest + */ + private $manifest; + + /** + * Memoized editor enhancement configuration. + * + * @var array|null + */ + private $config; + + /** + * Constructor. + * + * @param AssetManifest|null $manifest Asset manifest reader. + */ + public function __construct( ?AssetManifest $manifest = null ) { + $this->manifest = $manifest ?? new AssetManifest(); + } + /** * Registers editor enhancement hooks. * @@ -155,6 +178,10 @@ public function render_block( string $block_content, array $block, $instance = n * @return array Editor enhancement config. */ private function config(): array { + if ( is_array( $this->config ) ) { + return $this->config; + } + $config = array( 'columnsEqualHeight' => array( 'enabled' => false, @@ -193,7 +220,9 @@ private function config(): array { $config = $this->merge_config( $config, $filtered ); } - return $this->normalize_config( $config ); + $this->config = $this->normalize_config( $config ); + + return $this->config; } /** @@ -279,7 +308,7 @@ private function manifest_asset_files( string $directory, array $extensions ): ? return null; } - return ( new AssetManifest() )->asset_records( 'editor', $extensions ); + return $this->manifest->asset_records( 'editor', $extensions ); } /** diff --git a/includes/Runtime/Assets.php b/includes/Runtime/Assets.php index 341ac22..f5280ce 100644 --- a/includes/Runtime/Assets.php +++ b/includes/Runtime/Assets.php @@ -17,6 +17,20 @@ */ final class Assets { + /** + * Asset manifest reader. + * + * @var AssetManifest + */ + private $manifest; + + /** + * Memoized raw asset file records by theme-relative directory. + * + * @var array + */ + private $asset_file_records = array(); + /** * Memoized component metadata asset paths. * @@ -31,6 +45,15 @@ final class Assets { */ private $manifest_component_asset_paths; + /** + * Constructor. + * + * @param AssetManifest|null $manifest Asset manifest reader. + */ + public function __construct( ?AssetManifest $manifest = null ) { + $this->manifest = $manifest ?? new AssetManifest(); + } + /** * Registers asset hooks. * @@ -127,7 +150,7 @@ private function manifest_asset_files( string $directory, array $extensions ): ? return null; } - return ( new AssetManifest() )->asset_records( $scope, $extensions ); + return $this->manifest->asset_records( $scope, $extensions ); } /** @@ -161,7 +184,11 @@ private function scanned_asset_files( string $directory, array $extensions ): ar $assets = array(); $seen = array(); - foreach ( FileDiscovery::file_records( $this->asset_roots( $directory ), $extensions ) as $file ) { + foreach ( $this->raw_asset_file_records( $directory ) as $file ) { + if ( ! $this->extension_allowed( $file, $extensions ) ) { + continue; + } + $relative = $file['relative']; if ( $this->is_reserved_editor_asset( $directory, $relative ) ) { @@ -197,6 +224,65 @@ private function scanned_asset_files( string $directory, array $extensions ): ar return FileDiscovery::sort_by_priority_and_relative( $assets ); } + /** + * Gets raw built asset file records for a theme-relative directory. + * + * @param string $directory Theme-relative asset directory. + * @return array Raw file records. + */ + private function raw_asset_file_records( string $directory ): array { + $key = trim( $directory, '/\\' ); + + if ( ! array_key_exists( $key, $this->asset_file_records ) ) { + $this->asset_file_records[ $key ] = FileDiscovery::file_records( $this->asset_roots( $directory ), array( 'css', 'js' ) ); + } + + return $this->asset_file_records[ $key ]; + } + + /** + * Checks whether a raw asset record matches the requested extension set. + * + * @param array $file Raw file discovery record. + * @param array $extensions Allowed file extensions. + * @return bool TRUE when the file extension is allowed. + */ + private function extension_allowed( array $file, array $extensions ): bool { + $extensions = $this->normalize_extensions( $extensions ); + + if ( empty( $extensions ) ) { + return true; + } + + $extension = strtolower( pathinfo( (string) ( $file['path'] ?? '' ), PATHINFO_EXTENSION ) ); + + return isset( $extensions[ $extension ] ); + } + + /** + * Normalizes extension filters. + * + * @param array $extensions Candidate extensions. + * @return array Normalized extensions keyed by extension. + */ + private function normalize_extensions( array $extensions ): array { + $normalized = array(); + + foreach ( $extensions as $extension ) { + if ( ! is_scalar( $extension ) ) { + continue; + } + + $extension = strtolower( ltrim( trim( (string) $extension ), '.' ) ); + + if ( '' !== $extension ) { + $normalized[ $extension ] = true; + } + } + + return $normalized; + } + /** * Gets child theme asset roots first, then parent theme fallbacks. * @@ -279,7 +365,7 @@ private function manifest_component_asset_paths(): array { return $this->manifest_component_asset_paths; } - $this->manifest_component_asset_paths = ( new AssetManifest() )->scoped_component_asset_paths(); + $this->manifest_component_asset_paths = $this->manifest->scoped_component_asset_paths(); return $this->manifest_component_asset_paths; } diff --git a/includes/Support/AssetManifest.php b/includes/Support/AssetManifest.php index 926cf1a..69cb469 100644 --- a/includes/Support/AssetManifest.php +++ b/includes/Support/AssetManifest.php @@ -19,6 +19,20 @@ final class AssetManifest { */ private const DEFAULT_PATH = 'dist/emulsify-assets.json'; + /** + * Memoized manifest record. + * + * @var array|null + */ + private $manifest; + + /** + * Whether manifest resolution has already run for this instance. + * + * @var bool + */ + private $manifest_resolved = false; + /** * Gets manifest-backed asset records for a runtime scope. * @@ -391,6 +405,22 @@ private function asset_record( $entry, string $extension, array $manifest, strin * @return array|null Active manifest record, or null when unavailable. */ private function manifest(): ?array { + if ( $this->manifest_resolved ) { + return $this->manifest; + } + + $this->manifest_resolved = true; + $this->manifest = $this->resolve_manifest(); + + return $this->manifest; + } + + /** + * Resolves the first readable, valid child-first manifest. + * + * @return array|null Active manifest record, or null when unavailable. + */ + private function resolve_manifest(): ?array { foreach ( $this->manifest_candidates() as $candidate ) { if ( ! is_readable( $candidate['path'] ) ) { continue; From 5c5f3783ff09d0d84af68a9274e43a8af05f3a27 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 20:54:56 -0500 Subject: [PATCH 49/55] perf(blocks): enable discovery caching by default outside development --- .github/scripts/component-locator-smoke.php | 2 +- docs/parent-child-architecture.md | 8 ++++---- docs/post-2x-optimization-roadmap.md | 4 ++-- includes/Blocks/ComponentLocator.php | 13 +++++-------- includes/Blocks/Registry.php | 2 ++ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.github/scripts/component-locator-smoke.php b/.github/scripts/component-locator-smoke.php index 5d57aa6..4318a1b 100644 --- a/.github/scripts/component-locator-smoke.php +++ b/.github/scripts/component-locator-smoke.php @@ -12,7 +12,7 @@ $GLOBALS['emulsify_locator_smoke_hooks'] = array(); $GLOBALS['emulsify_locator_transients'] = array(); -$GLOBALS['emulsify_locator_environment'] = 'production'; +$GLOBALS['emulsify_locator_environment'] = 'development'; if ( ! function_exists( 'add_filter' ) ) { function add_filter( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): bool { diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index 3eafaff..a12c087 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -52,17 +52,17 @@ Discovery is memoized for the current PHP request. ACF/Twig and native block reg ## Optional persistent discovery cache -Persistent component discovery caching is available but disabled by default. Enable it only when a project has stable build/version invalidation: +Persistent component discovery caching is enabled by default in production and staging environments, and disabled by default in local, development, or `WP_DEBUG` environments. Projects can still opt out or force it on with: ```php -add_filter( 'emulsify_theme_component_discovery_cache_enabled', '__return_true' ); +add_filter( 'emulsify_theme_component_discovery_cache_enabled', '__return_false' ); ``` -The transient cache key includes the active stylesheet, parent template, child theme version, parent theme version, WordPress environment type, and `dist/emulsify-assets.json` filemtime when that manifest exists. Local and `WP_DEBUG` environments stay uncached unless the filter above explicitly enables caching. +The transient cache key includes the active stylesheet, parent template, child theme version, parent theme version, WordPress environment type, and `dist/emulsify-assets.json` filemtime when that manifest exists. The runtime also calls `clear_discovery_cache` on WordPress' `switch_theme` hook so changing themes invalidates the active cache key. Projects that customize component roots dynamically can add their own invalidation token with `emulsify_theme_component_discovery_cache_key_parts`. The default cache lifetime can be adjusted with `emulsify_theme_component_discovery_cache_ttl`. -To clear the current cache key from project code or a maintenance command, call: +To clear the current cache key from project code, a maintenance command, or custom deployment logic, call: ```php \Emulsify\Theme\Blocks\ComponentLocator::clear_discovery_cache(); diff --git a/docs/post-2x-optimization-roadmap.md b/docs/post-2x-optimization-roadmap.md index d79e627..f174141 100644 --- a/docs/post-2x-optimization-roadmap.md +++ b/docs/post-2x-optimization-roadmap.md @@ -8,7 +8,7 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child the 2. Build on Composer PSR-4 autoloading with grouped runtime directories in a minor release. 3. Add optional manifest-driven asset loading. 4. Add CLI diagnostics such as `wp emulsify doctor`. -5. Expand diagnostics around optional persistent discovery caching once projects have real-world cache invalidation feedback. +5. Expand diagnostics around optional persistent discovery caching once projects have real-world invalidation guidance feedback. ## Code organization @@ -31,7 +31,7 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child the ## Component/block discovery - Refine child-first discovery diagnostics for duplicate ACF/Twig and native block definitions. -- Keep optional persistent discovery caching filter-controlled; future work should focus on diagnostics and invalidation guidance, not UI. +- Keep optional persistent discovery caching production-enabled but filter-controlled; future work should focus on diagnostics and invalidation guidance, not UI. - Keep discovery safe when ACF or native block APIs are unavailable. ## CLI diagnostics diff --git a/includes/Blocks/ComponentLocator.php b/includes/Blocks/ComponentLocator.php index 367b06f..fa568e7 100644 --- a/includes/Blocks/ComponentLocator.php +++ b/includes/Blocks/ComponentLocator.php @@ -17,7 +17,7 @@ * Discovery is memoized on this object for the lifetime of the current PHP * request. The registry shares one locator between ACF/Twig and native block * registration, so both paths reuse the same deterministic child-first file - * index. Persistent caching is optional and disabled by default. + * index. Persistent caching defaults to on outside active development. */ final class ComponentLocator { @@ -352,17 +352,14 @@ private function delete_component_files_cache(): bool { * @return bool TRUE when persistent caching should be used. */ private function persistent_cache_enabled(): bool { - $enabled = false; - - if ( $this->is_development_environment() ) { - $enabled = false; - } + $enabled = ! $this->is_development_environment(); /** * Filters whether component discovery should use persistent caching. * - * The default is false. Return true to explicitly enable persistent - * caching, including in local or WP_DEBUG environments. + * The default is true in production and staging environments, and false + * in local, development, or WP_DEBUG environments. Return a boolean value + * to override the default either way. * * @param bool $enabled Whether persistent caching is enabled. * @param ComponentLocator $locator Component locator instance. diff --git a/includes/Blocks/Registry.php b/includes/Blocks/Registry.php index b29afb7..611b33a 100644 --- a/includes/Blocks/Registry.php +++ b/includes/Blocks/Registry.php @@ -36,6 +36,8 @@ public function __construct( ?AssetManifest $manifest = null ) { * @return void */ public function register(): void { + add_action( 'switch_theme', array( ComponentLocator::class, 'clear_discovery_cache' ) ); + $components = new ComponentLocator(); // Share one locator so ACF/Twig and native block registration use the same From 1ac5c8a4cfd986cdacaa525fef15e9b98828e4e9 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 21:06:07 -0500 Subject: [PATCH 50/55] refactor(twig): extract project component loader and config reader --- .github/scripts/bootstrap-loader-smoke.php | 1 + .github/scripts/release-check.cjs | 3 +- .github/scripts/theme-filters-smoke.php | 1 + .../scripts/twig-project-namespace-smoke.php | 2 + includes/Runtime/Twig.php | 371 +----------------- includes/Support/ProjectConfig.php | 96 +++++ includes/Twig/ProjectComponentLoader.php | 287 ++++++++++++++ 7 files changed, 395 insertions(+), 366 deletions(-) create mode 100644 includes/Support/ProjectConfig.php create mode 100644 includes/Twig/ProjectComponentLoader.php diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php index d4c03e6..41fa8cb 100644 --- a/.github/scripts/bootstrap-loader-smoke.php +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -78,6 +78,7 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { 'Emulsify\\Theme\\Support\\Diagnostics', 'Emulsify\\Theme\\Support\\AssetManifest', 'Emulsify\\Theme\\Support\\FileDiscovery', + 'Emulsify\\Theme\\Support\\ProjectConfig', ); $legacy_classes = array( 'Emulsify\\Theme\\Setup', diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index aad1376..25505da 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -784,6 +784,7 @@ function runStaticChecks() { runStaticCheck('Template fallback model', () => { const twigIntegration = readFile('includes/Runtime/Twig.php'); + const projectComponentLoader = readFile('includes/Twig/ProjectComponentLoader.php'); const twigNamespaceSmoke = readFile('.github/scripts/twig-project-namespace-smoke.php'); const childFunctions = readFile('whisk/functions.php'); const childPageTemplate = readFile('whisk/templates/page.twig'); @@ -833,7 +834,7 @@ function runStaticChecks() { ensure(twigIntegration.includes('project.emulsify.json'), 'Twig integration should read active child project.emulsify.json metadata.'); ensure(twigIntegration.includes('project_structure_namespaces') && twigIntegration.includes('structureImplementations'), 'Twig integration should honor Emulsify Core structureImplementations for configured namespaces.'); ensure(twigIntegration.includes('emulsify_theme_project_component_roots'), 'Twig integration should expose a focused project component roots filter.'); - ensure(twigIntegration.includes('implements \\Twig\\Loader\\LoaderInterface'), 'Twig integration should wrap the loader for machineName:component references.'); + ensure(twigIntegration.includes('new ProjectComponentLoader') && projectComponentLoader.includes('implements \\Twig\\Loader\\LoaderInterface'), 'Twig integration should wrap the loader for machineName:component references.'); ensure(twigIntegration.includes('machineName:component'), 'Twig integration should document the project component reference intent in code comments.'); ensure(twigNamespaceSmoke.includes('@custom/teaser.twig') && twigNamespaceSmoke.includes('variant.structureImplementations'), 'Twig namespace smoke should verify configured Core structure namespaces.'); ensure(!fs.existsSync(path.join(repoRoot, 'whisk/includes/twig-namespaces.php')), 'Whisk should rely on the parent Twig namespace integration by default.'); diff --git a/.github/scripts/theme-filters-smoke.php b/.github/scripts/theme-filters-smoke.php index 502a885..240f72f 100644 --- a/.github/scripts/theme-filters-smoke.php +++ b/.github/scripts/theme-filters-smoke.php @@ -311,6 +311,7 @@ function emulsify_filter_smoke_remove( string $path ): void { require_once $repo_root . '/includes/Support/AssetEnqueuer.php'; require_once $repo_root . '/includes/Support/Diagnostics.php'; require_once $repo_root . '/includes/Support/AssetManifest.php'; + require_once $repo_root . '/includes/Support/ProjectConfig.php'; require_once $repo_root . '/includes/Runtime/Assets.php'; require_once $repo_root . '/includes/Runtime/Context.php'; require_once $repo_root . '/includes/Runtime/Setup.php'; diff --git a/.github/scripts/twig-project-namespace-smoke.php b/.github/scripts/twig-project-namespace-smoke.php index 1026c92..a9fbb59 100644 --- a/.github/scripts/twig-project-namespace-smoke.php +++ b/.github/scripts/twig-project-namespace-smoke.php @@ -77,6 +77,8 @@ function get_template_directory(): string { require_once $repo_root . '/includes/Support/AttributeBag.php'; require_once $repo_root . '/includes/Support/FileDiscovery.php'; +require_once $repo_root . '/includes/Support/ProjectConfig.php'; +require_once $repo_root . '/includes/Twig/ProjectComponentLoader.php'; require_once $repo_root . '/includes/Runtime/Twig.php'; /** diff --git a/includes/Runtime/Twig.php b/includes/Runtime/Twig.php index 8b9222b..6f3d1d1 100644 --- a/includes/Runtime/Twig.php +++ b/includes/Runtime/Twig.php @@ -9,6 +9,8 @@ use Emulsify\Theme\Support\AttributeBag; use Emulsify\Theme\Support\FileDiscovery; +use Emulsify\Theme\Support\ProjectConfig; +use Emulsify\Theme\Twig\ProjectComponentLoader; /** * Twig integration for Timber. @@ -210,16 +212,10 @@ private function add_path( $loader, string $path, string $namespace ): void { * @return array Twig namespace path records. */ private function project_structure_namespaces(): array { - $config = $this->project_config(); - - if ( empty( $config['variant']['structureImplementations'] ) || ! is_array( $config['variant']['structureImplementations'] ) ) { - return array(); - } - $records = array(); $theme_dir = rtrim( get_stylesheet_directory(), '/\\' ); - foreach ( $config['variant']['structureImplementations'] as $implementation ) { + foreach ( ProjectConfig::structure_implementations() as $implementation ) { if ( ! is_array( $implementation ) || empty( $implementation['name'] ) || empty( $implementation['directory'] ) ) { continue; } @@ -230,7 +226,7 @@ private function project_structure_namespaces(): array { continue; } - $resolved = $this->resolve_project_path( $theme_dir, (string) $implementation['directory'] ); + $resolved = ProjectConfig::resolve_theme_relative_path( $theme_dir, (string) $implementation['directory'] ); if ( '' === $resolved ) { continue; @@ -245,31 +241,6 @@ private function project_structure_namespaces(): array { return $records; } - /** - * Resolves a project config path to a safe child-theme-relative path. - * - * @param string $theme_dir Theme root. - * @param string $path Project-configured path. - * @return string Resolved path, or empty string when invalid. - */ - private function resolve_project_path( string $theme_dir, string $path ): string { - $path = trim( str_replace( '\\', '/', $path ) ); - - if ( - '' === $path - || false !== strpos( $path, "\0" ) - || 0 === strpos( $path, '/' ) - || preg_match( '#(^|/)\.\.(/|$)#', $path ) - ) { - // project.emulsify.json paths are child-theme relative. Reject - // absolute paths and traversal so configuration cannot expose - // arbitrary server files as Twig namespaces. - return ''; - } - - return rtrim( $theme_dir, '/\\' ) . '/' . ltrim( $path, '/' ); - } - /** * Wraps the Timber loader with project machine-name component support. * @@ -281,7 +252,7 @@ private function with_project_component_loader( $loader ) { return $loader; } - $machine_name = $this->project_machine_name(); + $machine_name = ProjectConfig::machine_name(); if ( '' === $machine_name ) { return $loader; @@ -293,7 +264,7 @@ private function with_project_component_loader( $loader ) { return $loader; } - return $this->create_project_component_loader( $machine_name, $roots, $loader ); + return new ProjectComponentLoader( $machine_name, $roots, $loader ); } /** @@ -309,50 +280,6 @@ private function can_wrap_twig_loader( $loader ): bool { && is_a( $loader, '\Twig\Loader\LoaderInterface' ); } - /** - * Reads the active child theme project machine name. - * - * @return string Project machine name, or empty string when unavailable. - */ - private function project_machine_name(): string { - $data = $this->project_config(); - - if ( ! is_array( $data ) || empty( $data['project']['machineName'] ) ) { - return ''; - } - - $machine_name = trim( (string) $data['project']['machineName'] ); - - if ( 1 !== preg_match( '/^[A-Za-z0-9_-]+$/', $machine_name ) ) { - return ''; - } - - return $machine_name; - } - - /** - * Reads active child theme Emulsify project metadata. - * - * @return array Project config, or an empty array when unavailable. - */ - private function project_config(): array { - $path = get_stylesheet_directory() . '/project.emulsify.json'; - - if ( ! is_readable( $path ) ) { - return array(); - } - - $contents = file_get_contents( $path ); - - if ( false === $contents ) { - return array(); - } - - $data = json_decode( $contents, true ); - - return is_array( $data ) ? $data : array(); - } - /** * Gets project component root paths in child-first order. * @@ -409,292 +336,6 @@ private function normalize_project_component_roots( array $roots ): array { return array_column( FileDiscovery::normalize_roots( $roots ), 'path' ); } - /** - * Creates a Twig loader wrapper for project component references. - * - * @param string $machine_name Active project machine name. - * @param array $roots Component root paths. - * @param \Twig\Loader\LoaderInterface $loader Timber loader. - * @return \Twig\Loader\LoaderInterface Twig loader wrapper. - */ - private function create_project_component_loader( string $machine_name, array $roots, $loader ) { - return new class( $machine_name, $roots, $loader ) implements \Twig\Loader\LoaderInterface { - - /** - * Project machine name. - * - * @var string - */ - private $machine_name; - - /** - * Project template prefix. - * - * @var string - */ - private $prefix; - - /** - * Component root paths. - * - * @var array - */ - private $roots; - - /** - * Wrapped Twig loader. - * - * @var \Twig\Loader\LoaderInterface - */ - private $loader; - - /** - * Resolved project template cache. - * - * @var array - */ - private $cache = array(); - - /** - * Constructs the project component loader wrapper. - * - * @param string $machine_name Active project machine name. - * @param array $roots Component root paths. - * @param \Twig\Loader\LoaderInterface $loader Wrapped Twig loader. - */ - public function __construct( string $machine_name, array $roots, \Twig\Loader\LoaderInterface $loader ) { - $this->machine_name = $machine_name; - $this->prefix = $machine_name . ':'; - $this->roots = $roots; - $this->loader = $loader; - } - - /** - * Proxies filesystem namespace registration to the wrapped loader. - * - * @param string $path Filesystem path. - * @param string $namespace Twig namespace. - * @return void - */ - public function addPath( string $path, string $namespace = '__main__' ): void { - if ( method_exists( $this->loader, 'addPath' ) ) { - $this->loader->addPath( $path, $namespace ); - } - } - - /** - * Proxies prepended filesystem namespace registration when available. - * - * @param string $path Filesystem path. - * @param string $namespace Twig namespace. - * @return void - */ - public function prependPath( string $path, string $namespace = '__main__' ): void { - if ( method_exists( $this->loader, 'prependPath' ) ) { - $this->loader->prependPath( $path, $namespace ); - } - } - - /** - * Proxies filesystem namespace paths when available. - * - * @param string $namespace Twig namespace. - * @return array Namespace paths. - */ - public function getPaths( string $namespace = '__main__' ): array { - if ( method_exists( $this->loader, 'getPaths' ) ) { - return $this->loader->getPaths( $namespace ); - } - - return array(); - } - - /** - * Proxies filesystem namespace names when available. - * - * @return array Namespace names. - */ - public function getNamespaces(): array { - if ( method_exists( $this->loader, 'getNamespaces' ) ) { - return $this->loader->getNamespaces(); - } - - return array(); - } - - /** - * Returns source for a template logical name. - * - * @param string $name Template logical name. - * @return \Twig\Source Template source. - */ - public function getSourceContext( string $name ): \Twig\Source { - $path = $this->find_project_template( $name ); - - if ( null !== $path ) { - $contents = file_get_contents( $path ); - - if ( false === $contents ) { - throw new \Twig\Error\LoaderError( sprintf( 'Unable to read project component template "%s".', $name ) ); - } - - return new \Twig\Source( $contents, $name, $path ); - } - - if ( $this->is_project_reference( $name ) ) { - throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); - } - - return $this->loader->getSourceContext( $name ); - } - - /** - * Gets the cache key for a template name. - * - * @param string $name Template logical name. - * @return string Cache key. - */ - public function getCacheKey( string $name ): string { - $path = $this->find_project_template( $name ); - - if ( null !== $path ) { - return $path; - } - - if ( $this->is_project_reference( $name ) ) { - throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); - } - - return $this->loader->getCacheKey( $name ); - } - - /** - * Checks whether a template is fresh. - * - * @param string $name Template logical name. - * @param int $time Cached template timestamp. - * @return bool TRUE when the source is fresh. - */ - public function isFresh( string $name, int $time ): bool { - $path = $this->find_project_template( $name ); - - if ( null !== $path ) { - $modified = filemtime( $path ); - - return false !== $modified && $modified <= $time; - } - - if ( $this->is_project_reference( $name ) ) { - throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); - } - - return $this->loader->isFresh( $name, $time ); - } - - /** - * Checks whether a template exists. - * - * @param string $name Template logical name. - * @return bool TRUE when the template exists. - */ - public function exists( string $name ) { - if ( $this->is_project_reference( $name ) ) { - return null !== $this->find_project_template( $name ); - } - - return $this->loader->exists( $name ); - } - - /** - * Finds a project component template path. - * - * @param string $name Template logical name. - * @return string|null Resolved template path. - */ - private function find_project_template( string $name ): ?string { - if ( array_key_exists( $name, $this->cache ) ) { - return $this->cache[ $name ]; - } - - $component = $this->project_component_name( $name ); - - if ( null === $component ) { - return null; - } - - $parts = explode( '/', $component ); - $filename = end( $parts ) . '.twig'; - - foreach ( $this->roots as $root ) { - // Support the single-directory component convention first, then the - // older flat component.twig form. Roots are already child-first. - foreach ( array( $component . '/' . $filename, $component . '.twig' ) as $relative ) { - $path = $root . '/' . $relative; - - if ( is_file( $path ) && is_readable( $path ) ) { - $realpath = realpath( $path ); - - $this->cache[ $name ] = false !== $realpath ? $realpath : $path; - - return $this->cache[ $name ]; - } - } - } - - return null; - } - - /** - * Gets the safe project component name from a template reference. - * - * @param string $name Template logical name. - * @return string|null Component name. - */ - private function project_component_name( string $name ): ?string { - if ( ! $this->is_project_reference( $name ) ) { - return null; - } - - $component = substr( $name, strlen( $this->prefix ) ); - - if ( 1 !== preg_match( '/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)?$/', $component ) ) { - // Keep project component references intentionally shallow: - // component-name or group/component-name. Legacy deep paths are - // still available through explicit @namespace configuration. - return null; - } - - return $component; - } - - /** - * Checks whether a template name starts with the project prefix. - * - * @param string $name Template logical name. - * @return bool TRUE when the name targets this project namespace. - */ - private function is_project_reference( string $name ): bool { - return 0 === strncmp( $name, $this->prefix, strlen( $this->prefix ) ); - } - - /** - * Gets the loader error for a project reference. - * - * @param string $name Template logical name. - * @return string Error message. - */ - private function project_loader_error( string $name ): string { - $component = substr( $name, strlen( $this->prefix ) ); - - if ( '' === $component || false !== strpos( $component, "\0" ) || null === $this->project_component_name( $name ) ) { - return sprintf( 'Project component template name "%s" is invalid.', $name ); - } - - return sprintf( 'Project component template "%s" is not defined.', $name ); - } - }; - } - /** * Normalizes positional and object-style BEM arguments. * diff --git a/includes/Support/ProjectConfig.php b/includes/Support/ProjectConfig.php new file mode 100644 index 0000000..8a2f7db --- /dev/null +++ b/includes/Support/ProjectConfig.php @@ -0,0 +1,96 @@ +machine_name = $machine_name; + $this->prefix = $machine_name . ':'; + $this->roots = $roots; + $this->loader = $loader; + } + + /** + * Proxies filesystem namespace registration to the wrapped loader. + * + * @param string $path Filesystem path. + * @param string $namespace Twig namespace. + * @return void + */ + public function addPath( string $path, string $namespace = '__main__' ): void { + if ( method_exists( $this->loader, 'addPath' ) ) { + $this->loader->addPath( $path, $namespace ); + } + } + + /** + * Proxies prepended filesystem namespace registration when available. + * + * @param string $path Filesystem path. + * @param string $namespace Twig namespace. + * @return void + */ + public function prependPath( string $path, string $namespace = '__main__' ): void { + if ( method_exists( $this->loader, 'prependPath' ) ) { + $this->loader->prependPath( $path, $namespace ); + } + } + + /** + * Proxies filesystem namespace paths when available. + * + * @param string $namespace Twig namespace. + * @return array Namespace paths. + */ + public function getPaths( string $namespace = '__main__' ): array { + if ( method_exists( $this->loader, 'getPaths' ) ) { + return $this->loader->getPaths( $namespace ); + } + + return array(); + } + + /** + * Proxies filesystem namespace names when available. + * + * @return array Namespace names. + */ + public function getNamespaces(): array { + if ( method_exists( $this->loader, 'getNamespaces' ) ) { + return $this->loader->getNamespaces(); + } + + return array(); + } + + /** + * Returns source for a template logical name. + * + * @param string $name Template logical name. + * @return \Twig\Source Template source. + */ + public function getSourceContext( string $name ): \Twig\Source { + $path = $this->find_project_template( $name ); + + if ( null !== $path ) { + $contents = file_get_contents( $path ); + + if ( false === $contents ) { + throw new \Twig\Error\LoaderError( sprintf( 'Unable to read project component template "%s".', $name ) ); + } + + return new \Twig\Source( $contents, $name, $path ); + } + + if ( $this->is_project_reference( $name ) ) { + throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); + } + + return $this->loader->getSourceContext( $name ); + } + + /** + * Gets the cache key for a template name. + * + * @param string $name Template logical name. + * @return string Cache key. + */ + public function getCacheKey( string $name ): string { + $path = $this->find_project_template( $name ); + + if ( null !== $path ) { + return $path; + } + + if ( $this->is_project_reference( $name ) ) { + throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); + } + + return $this->loader->getCacheKey( $name ); + } + + /** + * Checks whether a template is fresh. + * + * @param string $name Template logical name. + * @param int $time Cached template timestamp. + * @return bool TRUE when the source is fresh. + */ + public function isFresh( string $name, int $time ): bool { + $path = $this->find_project_template( $name ); + + if ( null !== $path ) { + $modified = filemtime( $path ); + + return false !== $modified && $modified <= $time; + } + + if ( $this->is_project_reference( $name ) ) { + throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); + } + + return $this->loader->isFresh( $name, $time ); + } + + /** + * Checks whether a template exists. + * + * @param string $name Template logical name. + * @return bool TRUE when the template exists. + */ + public function exists( string $name ) { + if ( $this->is_project_reference( $name ) ) { + return null !== $this->find_project_template( $name ); + } + + return $this->loader->exists( $name ); + } + + /** + * Finds a project component template path. + * + * @param string $name Template logical name. + * @return string|null Resolved template path. + */ + private function find_project_template( string $name ): ?string { + if ( array_key_exists( $name, $this->cache ) ) { + return $this->cache[ $name ]; + } + + $component = $this->project_component_name( $name ); + + if ( null === $component ) { + return null; + } + + $parts = explode( '/', $component ); + $filename = end( $parts ) . '.twig'; + + foreach ( $this->roots as $root ) { + // Support the single-directory component convention first, then the + // older flat component.twig form. Roots are already child-first. + foreach ( array( $component . '/' . $filename, $component . '.twig' ) as $relative ) { + $path = $root . '/' . $relative; + + if ( is_file( $path ) && is_readable( $path ) ) { + $realpath = realpath( $path ); + + $this->cache[ $name ] = false !== $realpath ? $realpath : $path; + + return $this->cache[ $name ]; + } + } + } + + return null; + } + + /** + * Gets the safe project component name from a template reference. + * + * @param string $name Template logical name. + * @return string|null Component name. + */ + private function project_component_name( string $name ): ?string { + if ( ! $this->is_project_reference( $name ) ) { + return null; + } + + $component = substr( $name, strlen( $this->prefix ) ); + + if ( 1 !== preg_match( '/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)?$/', $component ) ) { + // Keep project component references intentionally shallow: + // component-name or group/component-name. Legacy deep paths are + // still available through explicit @namespace configuration. + return null; + } + + return $component; + } + + /** + * Checks whether a template name starts with the project prefix. + * + * @param string $name Template logical name. + * @return bool TRUE when the name targets this project namespace. + */ + private function is_project_reference( string $name ): bool { + return 0 === strncmp( $name, $this->prefix, strlen( $this->prefix ) ); + } + + /** + * Gets the loader error for a project reference. + * + * @param string $name Template logical name. + * @return string Error message. + */ + private function project_loader_error( string $name ): string { + $component = substr( $name, strlen( $this->prefix ) ); + + if ( '' === $component || false !== strpos( $component, "\0" ) || null === $this->project_component_name( $name ) ) { + return sprintf( 'Project component template name "%s" is invalid.', $name ); + } + + return sprintf( 'Project component template "%s" is not defined.', $name ); + } +} From 9cc26ea83202344d2030d71f22d56ba0eb0048d0 Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 21:14:55 -0500 Subject: [PATCH 51/55] chore: remove dead compatibility shims and unused release tooling --- .github/scripts/bootstrap-loader-smoke.php | 34 - .github/scripts/release-check.cjs | 19 +- LICENSE.txt | 339 --- composer.json | 6 +- includes/Bootstrap.php | 6 - includes/Compatibility.php | 33 - package-lock.json | 2289 +++++++++++++++++--- package.json | 8 - release.config.js | 1 - 9 files changed, 1997 insertions(+), 738 deletions(-) delete mode 100644 LICENSE.txt delete mode 100644 includes/Compatibility.php diff --git a/.github/scripts/bootstrap-loader-smoke.php b/.github/scripts/bootstrap-loader-smoke.php index 41fa8cb..48492ac 100644 --- a/.github/scripts/bootstrap-loader-smoke.php +++ b/.github/scripts/bootstrap-loader-smoke.php @@ -80,37 +80,16 @@ function emulsify_bootstrap_loader_run_php( string $code ): void { 'Emulsify\\Theme\\Support\\FileDiscovery', 'Emulsify\\Theme\\Support\\ProjectConfig', ); -$legacy_classes = array( - 'Emulsify\\Theme\\Setup', - 'Emulsify\\Theme\\Assets', - 'Emulsify\\Theme\\Context', - 'Emulsify\\Theme\\Twig', - 'Emulsify\\Theme\\Timber_Integration', - 'Emulsify\\Theme\\Missing_Timber', - 'Emulsify\\Theme\\Blocks\\Registry', - 'Emulsify\\Theme\\Blocks\\Component_Locator', - 'Emulsify\\Theme\\Blocks\\Acf_Blocks', - 'Emulsify\\Theme\\Blocks\\Native_Blocks', - 'Emulsify\\Theme\\Core_Block_Twig_Renderer', - 'Emulsify\\Theme\\Patterns', - 'Emulsify\\Theme\\Editor_Enhancements', - 'Emulsify\\Theme\\Editor_Policy', - 'Emulsify\\Theme\\Acf_Local_JSON', - 'Emulsify\\Theme\\Cli', - 'Emulsify\\Theme\\AttributeBag', -); emulsify_bootstrap_loader_assert( is_readable( $autoload ), 'Run composer install or composer dump-autoload before the Bootstrap loader smoke test.' ); $classes_export = var_export( $classes, true ); -$legacy_export = var_export( $legacy_classes, true ); $repo_export = var_export( $repo_root, true ); emulsify_bootstrap_loader_run_php( <<=8.3'), 'composer.json should enforce the PHP 8.3 runtime floor.'); ensure(composer.require && composer.require['timber/timber'] === '^2.3', 'composer.json should keep the Timber 2 dependency constraint.'); ensure(composer.autoload && composer.autoload['psr-4'] && composer.autoload['psr-4']['Emulsify\\Theme\\'] === 'includes/', 'composer.json should expose the runtime namespace through PSR-4 autoloading.'); ensure(!Object.hasOwn(composer.autoload, 'classmap'), 'composer.json should rely on PSR-4 runtime paths instead of classmap loading.'); - ensure(Array.isArray(composer.autoload.files) && composer.autoload.files.includes('includes/Compatibility.php'), 'composer.json should load runtime compatibility aliases for Composer installs.'); + ensure(!Object.hasOwn(composer.autoload, 'files'), 'composer.json should not load removed compatibility files.'); ensureParentThemeLanguage('composer.json description', composer.description); return `Validated root package ${rootPackage.version} and composer metadata.`; }); @@ -465,7 +467,6 @@ function runStaticChecks() { ensure(bootstrap.indexOf('load_vendor_autoload();') < bootstrap.indexOf('load_classes();'), 'Bootstrap should try Composer autoloading before fallback runtime loading.'); ensure(bootstrap.includes('spl_autoload_register'), 'Bootstrap should register a fallback runtime autoloader.'); ensure(bootstrap.includes('runtime_class_file'), 'Bootstrap should resolve runtime classes through a fallback file mapper.'); - ensure(bootstrap.includes('Compatibility.php'), 'Bootstrap should load compatibility aliases after registering fallback runtime loading.'); ensure(attributeBag.includes('implements \\Stringable'), 'AttributeBag should serialize safely in Twig string contexts.'); ensure(attributeBag.includes('function addClass'), 'AttributeBag should support Core-style class merging.'); ensure(attributeBag.includes('function toString'), 'AttributeBag should expose explicit serialization.'); @@ -478,18 +479,12 @@ function runStaticChecks() { runStaticCheck('Bootstrap runtime autoloading', () => { const bootstrap = readFile('includes/Bootstrap.php'); - const compatibility = readFile('includes/Compatibility.php'); const smoke = readFile('.github/scripts/bootstrap-loader-smoke.php'); ensure(bootstrap.includes("str_replace( '\\\\', '/', $relative_class )"), 'Bootstrap fallback loader should resolve PSR-4-shaped runtime paths.'); - ensure(compatibility.includes('class_alias'), 'Compatibility should preserve old runtime class names with aliases.'); - ensure(compatibility.includes('Runtime\\Twig::class') && compatibility.includes("'\\\\Twig'"), 'Compatibility should alias old root Twig class names.'); - ensure(compatibility.includes('Blocks\\ComponentLocator::class') && compatibility.includes("'\\\\Blocks\\\\Component_Locator'"), 'Compatibility should alias old block service class names.'); ensure(smoke.includes("require_once \\$repo_root . '/vendor/autoload.php'"), 'Bootstrap loader smoke should verify Composer autoloading.'); - ensure(smoke.includes('Composer compatibility alias did not load'), 'Bootstrap loader smoke should verify Composer-loaded compatibility aliases.'); ensure(smoke.includes("require_once \\$repo_root . '/includes/Bootstrap.php'"), 'Bootstrap loader smoke should verify fallback loading without Composer.'); ensure(smoke.includes('Emulsify\\\\Theme\\\\Blocks\\\\Registry'), 'Bootstrap loader smoke should cover nested block runtime classes.'); - ensure(smoke.includes('Emulsify\\\\Theme\\\\Blocks\\\\Component_Locator'), 'Bootstrap loader smoke should cover legacy compatibility aliases.'); ensure(smoke.includes('Fallback loader did not load'), 'Bootstrap loader smoke should fail clearly when fallback loading breaks.'); return 'Composer autoloading and Bootstrap fallback loading are covered.'; }); @@ -1136,7 +1131,7 @@ function runStaticChecks() { runStaticCheck('License metadata', () => { ensureGpl2LicenseText('LICENSE', license); - ensureGpl2LicenseText('LICENSE.txt', licenseTxt); + ensure(!fs.existsSync(path.join(repoRoot, 'LICENSE.txt')), 'LICENSE.txt should not duplicate the canonical LICENSE file.'); ensure(!/MIT License/i.test(readme), 'README.md should not document an MIT license.'); return 'License files and project metadata align on GPL-2.0-only.'; }); diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index d159169..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/composer.json b/composer.json index b81f508..844c2f8 100644 --- a/composer.json +++ b/composer.json @@ -15,15 +15,13 @@ } ], "require": { + "php": ">=8.3", "timber/timber": "^2.3" }, "autoload": { "psr-4": { "Emulsify\\Theme\\": "includes/" - }, - "files": [ - "includes/Compatibility.php" - ] + } }, "config": { "optimize-autoloader": true, diff --git a/includes/Bootstrap.php b/includes/Bootstrap.php index 9024d2d..4b609fd 100644 --- a/includes/Bootstrap.php +++ b/includes/Bootstrap.php @@ -115,12 +115,6 @@ function ( string $class ): void { $this->autoload_runtime_class( $class ); } ); - - $compatibility = $this->theme_dir . '/includes/Compatibility.php'; - - if ( is_readable( $compatibility ) ) { - require_once $compatibility; - } } /** diff --git a/includes/Compatibility.php b/includes/Compatibility.php deleted file mode 100644 index d832989..0000000 --- a/includes/Compatibility.php +++ /dev/null @@ -1,33 +0,0 @@ - __NAMESPACE__ . '\\Setup', - Runtime\Assets::class => __NAMESPACE__ . '\\Assets', - Runtime\Context::class => __NAMESPACE__ . '\\Context', - Runtime\Twig::class => __NAMESPACE__ . '\\Twig', - Runtime\TimberIntegration::class => __NAMESPACE__ . '\\Timber_Integration', - Runtime\MissingTimber::class => __NAMESPACE__ . '\\Missing_Timber', - Blocks\ComponentLocator::class => __NAMESPACE__ . '\\Blocks\\Component_Locator', - Blocks\AcfBlocks::class => __NAMESPACE__ . '\\Blocks\\Acf_Blocks', - Blocks\NativeBlocks::class => __NAMESPACE__ . '\\Blocks\\Native_Blocks', - Blocks\CoreBlockTwigRenderer::class => __NAMESPACE__ . '\\Core_Block_Twig_Renderer', - Blocks\Patterns::class => __NAMESPACE__ . '\\Patterns', - Editor\Enhancements::class => __NAMESPACE__ . '\\Editor_Enhancements', - Editor\Policy::class => __NAMESPACE__ . '\\Editor_Policy', - Acf\LocalJson::class => __NAMESPACE__ . '\\Acf_Local_JSON', - Cli\GenerateChildThemeCommand::class => __NAMESPACE__ . '\\Cli', - Support\AttributeBag::class => __NAMESPACE__ . '\\AttributeBag', -); - -foreach ( $aliases as $class => $alias ) { - if ( ! class_exists( $alias, false ) && class_exists( $class ) ) { - class_alias( $class, $alias ); - } -} diff --git a/package-lock.json b/package-lock.json index e56737c..a2faa80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,8 @@ "devDependencies": { "@commitlint/cli": "^19.4.1", "@commitlint/config-conventional": "^19.4.1", - "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^13.0.1", - "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^12.0.8", - "@semantic-release/npm": "^13.1.5", "@semantic-release/release-notes-generator": "^14.1.1", "husky": "^9.1.5", "lint-staged": "^15.2.9", @@ -576,25 +573,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@semantic-release/changelog": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", - "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "fs-extra": "^11.0.0", - "lodash": "^4.17.4" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" - } - }, "node_modules/@semantic-release/commit-analyzer": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", @@ -648,39 +626,6 @@ "node": ">=18" } }, - "node_modules/@semantic-release/error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", - "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@semantic-release/git": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", - "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@semantic-release/error": "^3.0.0", - "aggregate-error": "^3.0.0", - "debug": "^4.0.0", - "dir-glob": "^3.0.0", - "execa": "^5.0.0", - "lodash": "^4.17.4", - "micromatch": "^4.0.0", - "p-reduce": "^2.0.0" - }, - "engines": { - "node": ">=14.17" - }, - "peerDependencies": { - "semantic-release": ">=18.0.0" - } - }, "node_modules/@semantic-release/github": { "version": "12.0.8", "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.8.tgz", @@ -809,134 +754,2041 @@ "node": ">=18" } }, - "node_modules/@semantic-release/npm/node_modules/aggregate-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", - "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "node_modules/@semantic-release/npm/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm": { + "version": "11.18.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.18.0.tgz", + "integrity": "sha512-T67M4L5wNm0cZ7EBLErcEkY1SmzEW/WJ+SADBzsFUY1UdAPfFHXFQtZ6SEXiK0+vzXysCvAsepbMaBTwnrAD+w==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/metavuln-calculator", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^9.9.0", + "@npmcli/config": "^10.12.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/map-workspaces": "^5.0.3", + "@npmcli/metavuln-calculator": "^9.0.3", + "@npmcli/package-json": "^7.0.5", + "@npmcli/promise-spawn": "^9.0.1", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.4", + "@sigstore/tuf": "^4.0.2", + "abbrev": "^4.0.0", + "archy": "~1.0.0", + "cacache": "^20.0.4", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^9.0.3", + "ini": "^6.0.0", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.4", + "json-parse-even-better-errors": "^5.0.0", + "libnpmaccess": "^10.0.3", + "libnpmdiff": "^8.1.11", + "libnpmexec": "^10.3.1", + "libnpmfund": "^7.0.25", + "libnpmorg": "^8.0.1", + "libnpmpack": "^9.1.11", + "libnpmpublish": "^11.2.0", + "libnpmsearch": "^9.0.1", + "libnpmteam": "^8.0.2", + "libnpmversion": "^8.0.4", + "make-fetch-happen": "^15.0.6", + "minimatch": "^10.2.5", + "minipass": "^7.1.3", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^12.4.0", + "nopt": "^9.0.0", + "npm-audit-report": "^7.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.2", + "npm-pick-manifest": "^11.0.3", + "npm-profile": "^12.0.2", + "npm-registry-fetch": "^19.1.1", + "npm-user-validate": "^4.0.0", + "p-map": "^7.0.4", + "pacote": "^21.5.1", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.1.0", + "qrcode-terminal": "^0.12.0", + "read": "^5.0.1", + "semver": "^7.8.5", + "spdx-expression-parse": "^4.0.0", + "ssri": "^13.0.1", + "supports-color": "^10.2.2", + "tar": "^7.5.19", + "text-table": "~0.2.0", + "tiny-relative-date": "^2.0.2", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/agent": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.9.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^5.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/metavuln-calculator": "^9.0.2", + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^4.0.0", + "@npmcli/run-script": "^10.0.0", + "bin-links": "^6.0.0", + "cacache": "^20.0.1", + "common-ancestor-path": "^2.0.0", + "hosted-git-info": "^9.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^9.0.0", + "npm-install-checks": "^8.0.0", + "npm-package-arg": "^13.0.0", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "pacote": "^21.0.2", + "parse-conflict-json": "^5.0.1", + "proc-log": "^6.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^13.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/config": { + "version": "10.12.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^6.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/fs": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/git": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/package-json": { + "version": "7.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/query": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/redact": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@npmcli/run-script": { + "version": "10.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/bundle": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/core": { + "version": "3.2.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.5.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/sign": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.4", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/tuf": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@sigstore/verify": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/@tufjs/models": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/abbrev": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/aproba": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/bin-links": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "proc-log": "^6.0.0", + "read-cmd-shim": "^6.0.0", + "write-file-atomic": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/binary-extensions": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/brace-expansion": { + "version": "5.0.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cacache": { + "version": "20.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/chalk": { + "version": "5.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ci-info": { + "version": "4.4.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cidr-regex": { + "version": "5.0.5", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cmd-shim": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/common-ancestor-path": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/diff": { + "version": "8.0.4", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/glob": { + "version": "13.0.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/hosted-git-info": { + "version": "9.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/iconv-lite": { + "version": "0.7.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ignore-walk": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ini": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/init-package-json": { + "version": "8.2.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "npm-package-arg": "^13.0.0", + "promzard": "^3.0.1", + "read": "^5.0.1", + "semver": "^7.7.2", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ip-address": { + "version": "10.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/is-cidr": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^5.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/isexe": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmaccess": { + "version": "10.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmdiff": { + "version": "8.1.11", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.9.0", + "@npmcli/installed-package-contents": "^4.0.0", + "binary-extensions": "^3.0.0", + "diff": "^8.0.2", + "minimatch": "^10.0.3", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "tar": "^7.5.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmexec": { + "version": "10.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.9.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2", + "proc-log": "^6.0.0", + "read": "^5.0.1", + "semver": "^7.3.7", + "signal-exit": "^4.1.0", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmfund": { + "version": "7.0.25", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.9.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmorg": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmpack": { + "version": "9.1.11", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.9.0", + "@npmcli/run-script": "^10.0.0", + "npm-package-arg": "^13.0.0", + "pacote": "^21.0.2" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmpublish": { + "version": "11.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^13.0.0", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7", + "sigstore": "^4.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmsearch": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmteam": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^19.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/libnpmversion": { + "version": "8.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "@npmcli/run-script": "^10.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/lru-cache": { + "version": "11.5.1", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/make-fetch-happen": { + "version": "15.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minimatch": { + "version": "10.2.5", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-fetch": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.6", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^7.1.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/mute-stream": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/node-gyp": { + "version": "12.4.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/nopt": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-audit-report": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-bundled": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-install-checks": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-package-arg": { + "version": "13.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-pick-manifest": { + "version": "11.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-profile": { + "version": "12.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-registry-fetch": { + "version": "19.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/npm-user-validate": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/p-map": { + "version": "7.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/pacote": { + "version": "21.5.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^6.0.0", + "sigstore": "^4.0.0", + "ssri": "^13.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/parse-conflict-json": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^5.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/proc-log": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/proggy": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/promzard": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^3.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/read-cmd-shim": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/semver": { + "version": "7.8.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/sigstore": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.2.1", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.1.1", + "@sigstore/tuf": "^4.0.2", + "@sigstore/verify": "^3.1.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/socks": { + "version": "2.8.9", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.23", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/ssri": { + "version": "13.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/supports-color": { + "version": "10.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tar": { + "version": "7.5.19", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tiny-relative-date": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.17", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "clean-stack": "^5.2.0", - "indent-string": "^5.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=18" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/@semantic-release/npm/node_modules/clean-stack": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", - "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "escape-string-regexp": "5.0.0" - }, "engines": { - "node": ">=14.16" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/@semantic-release/npm/node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, "engines": { - "node": "^18.19.0 || >=20.5.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@semantic-release/npm/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/tuf-js": { + "version": "4.1.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@semantic-release/npm/node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/undici": { + "version": "6.27.0", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "MIT", "engines": { - "node": ">=18.18.0" + "node": ">=18.17" } }, - "node_modules/@semantic-release/npm/node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/validate-npm-package-name": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@semantic-release/npm/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=18" + "node": "20 || >=22" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/which": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@semantic-release/npm/node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/write-file-atomic": { + "version": "7.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" + "signal-exit": "^4.0.1" }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/npm/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@semantic-release/npm/node_modules/path-key": { @@ -1120,20 +2972,6 @@ "node": ">= 20" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -1281,16 +3119,6 @@ "node": ">=10" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -2257,30 +4085,6 @@ "dev": true, "license": "MIT" }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2624,16 +4428,6 @@ "node": ">= 20" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -2702,16 +4496,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/index-to-position": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", @@ -2795,19 +4579,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-text-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", @@ -3239,13 +5010,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash-es": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", @@ -3510,16 +5274,6 @@ "node": ">=16" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -3620,30 +5374,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm": { - "name": "cmd-shim", - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", - "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3654,22 +5384,6 @@ "node": ">=0.10.0" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -3760,16 +5474,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-reduce": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", - "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/p-timeout": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", @@ -4747,13 +6451,6 @@ "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, "node_modules/signale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", @@ -5028,16 +6725,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", diff --git a/package.json b/package.json index b2843df..4755bc0 100644 --- a/package.json +++ b/package.json @@ -55,19 +55,11 @@ "devDependencies": { "@commitlint/cli": "^19.4.1", "@commitlint/config-conventional": "^19.4.1", - "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^13.0.1", - "@semantic-release/git": "^10.0.1", "@semantic-release/github": "^12.0.8", - "@semantic-release/npm": "^13.1.5", "@semantic-release/release-notes-generator": "^14.1.1", "husky": "^9.1.5", "lint-staged": "^15.2.9", "semantic-release": "^25.0.5" - }, - "overrides": { - "@semantic-release/npm": { - "npm": "npm:cmd-shim@8.0.0" - } } } diff --git a/release.config.js b/release.config.js index 8b3f296..3959e16 100644 --- a/release.config.js +++ b/release.config.js @@ -67,7 +67,6 @@ module.exports = { ['@semantic-release/commit-analyzer', { parserOpts }], ['@semantic-release/release-notes-generator', { parserOpts }], expectedStableReleaseGuard, - ['@semantic-release/npm', { npmPublish: false }], '@semantic-release/github', ], }; From 44aa22625539e8180f57b3fca40a10a233f29f5b Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 21:22:44 -0500 Subject: [PATCH 52/55] docs: refresh architecture docs and add index --- .github/scripts/release-check.cjs | 36 ++++++++++++++- README.md | 1 + docs/README.md | 30 ++++++++++++ docs/acf-twig-blocks.md | 4 +- docs/asset-loading.md | 10 +++- docs/block-patterns.md | 2 + docs/core-block-twig-rendering.md | 2 + docs/native-gutenberg-blocks.md | 4 +- docs/parent-child-architecture.md | 8 ++++ docs/post-2x-optimization-roadmap.md | 68 ++++++++++++++++++---------- 10 files changed, 134 insertions(+), 31 deletions(-) create mode 100644 docs/README.md diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 5056757..8bb8805 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -253,6 +253,7 @@ function runStaticChecks() { const wordpressFixtureSmoke = readFile('.github/scripts/wordpress-fixture-smoke.cjs'); const readme = readFile('README.md'); const docs = { + index: readFile('docs/README.md'), upgrading: readFile('docs/upgrading-1x-to-2x.md'), parity: readFile('docs/sister-project-parity.md'), architecture: readFile('docs/parent-child-architecture.md'), @@ -300,6 +301,7 @@ function runStaticChecks() { '.gitignore', '.nvmrc', 'README.md', + 'docs/README.md', 'docs/acf-local-json.md', 'docs/acf-twig-blocks.md', 'docs/asset-loading.md', @@ -964,6 +966,7 @@ function runStaticChecks() { runStaticCheck('Release documentation', () => { const expectedDocLinks = [ + 'docs/README.md', 'docs/upgrading-1x-to-2x.md', 'docs/sister-project-parity.md', 'docs/parent-child-architecture.md', @@ -1086,6 +1089,7 @@ function runStaticChecks() { ensure(docs.assets.includes('emulsify_theme_asset_directories'), 'Asset loading doc should document asset directory filtering.'); ensure(docs.assets.includes('dist/emulsify-assets.json') && docs.assets.includes('emulsify_theme_asset_manifest_path'), 'Asset loading doc should document optional manifest loading.'); ensure(docs.assets.includes('block-scoped assets') && docs.assets.includes('emulsify_theme_acf_block_asset_records'), 'Asset loading doc should document block-scoped asset behavior.'); + ensure(docs.assets.includes('Manifest data is memoized') && docs.assets.includes('Scanner fallback records are also memoized'), 'Asset loading doc should document per-request manifest and scanner memoization.'); ensure(docs.coreBlockTwig.includes('[Component recipes](component-recipes.md)'), 'Core block Twig rendering doc should link to component recipes.'); ensure(docs.cli.includes('--dry-run') && docs.cli.includes('--force') && docs.cli.includes('--activate'), 'WP-CLI doc should document generator safety options.'); ensure(docs.cli.includes('Force replacement safety') && docs.cli.includes('Emulsify-generated child theme markers'), 'WP-CLI doc should document force replacement safety.'); @@ -1103,11 +1107,17 @@ function runStaticChecks() { ensure(docs.release.includes('Manual dispatch can also run the Whisk Storybook build and accessibility audit'), 'Release process doc should document optional extended checks.'); ensure(docs.post2xRoadmap.includes('follow-up opportunities for focused minor releases, not 2.0 blockers'), 'Post-2.x roadmap should frame items as follow-up opportunities.'); ensure(docs.post2xRoadmap.includes('Ship 2.0 without adding new runtime features'), 'Post-2.x roadmap should keep 2.0 focused.'); - ensure(docs.post2xRoadmap.includes('Build on Composer PSR-4 autoloading with grouped runtime directories'), 'Post-2.x roadmap should include the code organization milestone.'); - ensure(docs.post2xRoadmap.includes('optional manifest-driven asset loading'), 'Post-2.x roadmap should include the asset manifest milestone.'); + ensure(docs.post2xRoadmap.includes('## Done in 2.0'), 'Post-2.x roadmap should separate completed 2.0 work from follow-up work.'); + ensure(docs.post2xRoadmap.includes('Composer PSR-4 autoloading and grouped runtime directories'), 'Post-2.x roadmap should identify runtime class grouping as completed.'); + ensure(docs.post2xRoadmap.includes('Optional manifest-driven asset loading') || docs.post2xRoadmap.includes('optional manifest-driven asset loading'), 'Post-2.x roadmap should identify asset manifest support as completed.'); + ensure(docs.post2xRoadmap.includes('AssetRecord') && docs.post2xRoadmap.includes('AssetEnqueuer') && docs.post2xRoadmap.includes('Diagnostics'), 'Post-2.x roadmap should identify shared Support helpers as completed.'); + ensure(docs.post2xRoadmap.includes('ProjectComponentLoader'), 'Post-2.x roadmap should identify the named Twig project component loader as completed.'); ensure(docs.post2xRoadmap.includes('wp emulsify doctor'), 'Post-2.x roadmap should include CLI diagnostics.'); + ensure(docs.post2xRoadmap.includes('PHPUnit'), 'Post-2.x roadmap should include migration of smoke coverage into PHPUnit or an equivalent WordPress-aware layer.'); + ensure(docs.post2xRoadmap.includes('block.json-based ACF registration'), 'Post-2.x roadmap should include block.json-based ACF registration as a follow-up.'); ensure(docs.post2xRoadmap.includes('optional persistent discovery caching') && docs.post2xRoadmap.includes('invalidation guidance'), 'Post-2.x roadmap should keep cache follow-up work focused on diagnostics and guidance.'); for (const heading of [ + '## Done in 2.0', '## Code organization', '## Runtime architecture', '## Asset loading and performance', @@ -1118,6 +1128,28 @@ function runStaticChecks() { ]) { ensure(docs.post2xRoadmap.includes(heading), `Post-2.x roadmap should include ${heading}.`); } + for (const docLink of [ + 'upgrading-1x-to-2x.md', + 'parent-child-architecture.md', + 'wp-cli-child-theme-generation.md', + 'timber-and-twig-authoring.md', + 'component-recipes.md', + 'core-4-vite-workflow.md', + 'acf-twig-blocks.md', + 'native-gutenberg-blocks.md', + 'core-block-twig-rendering.md', + 'block-patterns.md', + 'editor-policy.md', + 'editor-enhancements.md', + 'acf-local-json.md', + 'asset-loading.md', + 'release-process.md', + 'sister-project-parity.md', + 'post-2x-optimization-roadmap.md', + ]) { + const matches = docs.index.split(`(${docLink})`).length - 1; + ensure(matches === 1, `Docs index should link to ${docLink} exactly once.`); + } ensure(pullRequestTemplate.includes('2.0 release branch merge') && pullRequestTemplate.includes('wordpress_fixture'), 'PR template should include the 2.0 manual fixture checklist item.'); ensure(/duplicate[\w\s/`.-]*skipped instead of being registered twice/i.test(docsText), 'Docs should document duplicate block handling.'); ensure(docsText.includes('normal frontend visitors') || docsText.includes('Normal frontend visitors'), 'Docs should document that duplicate diagnostics avoid frontend noise.'); diff --git a/README.md b/README.md index db8393b..605392d 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ For Emulsify CLI integration, the standalone starter repository is `https://gith ## Documentation +- [Documentation index](docs/README.md) - [Upgrading from 1.x to 2.x](docs/upgrading-1x-to-2x.md) - [Sister-project parity contract](docs/sister-project-parity.md) - [Parent and child theme architecture](docs/parent-child-architecture.md) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b152719 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +# Emulsify WordPress docs + +## Getting started + +- [Upgrading from 1.x to 2.x](upgrading-1x-to-2x.md) +- [Parent and child theme architecture](parent-child-architecture.md) +- [WP-CLI child theme generation](wp-cli-child-theme-generation.md) + +## Authoring + +- [Timber and Twig authoring](timber-and-twig-authoring.md) +- [Component recipes](component-recipes.md) +- [Emulsify Core 4 and Vite workflow](core-4-vite-workflow.md) + +## Blocks and editor + +- [ACF/Twig blocks](acf-twig-blocks.md) +- [Native Gutenberg blocks](native-gutenberg-blocks.md) +- [Core block Twig rendering](core-block-twig-rendering.md) +- [Block patterns](block-patterns.md) +- [Editor policy](editor-policy.md) +- [Editor enhancements](editor-enhancements.md) +- [ACF Local JSON](acf-local-json.md) + +## Operations + +- [Asset loading](asset-loading.md) +- [Release process](release-process.md) +- [Sister-project parity contract](sister-project-parity.md) +- [Post-2.x optimization roadmap](post-2x-optimization-roadmap.md) diff --git a/docs/acf-twig-blocks.md b/docs/acf-twig-blocks.md index 34b3a9b..db4ef67 100644 --- a/docs/acf-twig-blocks.md +++ b/docs/acf-twig-blocks.md @@ -1,5 +1,7 @@ # ACF/Twig blocks +Related: [Native Gutenberg blocks](native-gutenberg-blocks.md), [Core block Twig rendering](core-block-twig-rendering.md), [Block patterns](block-patterns.md). + ACF/Twig blocks are optional. If ACF is not active, this registration path is skipped without affecting the base theme. ## How discovery works @@ -11,7 +13,7 @@ The parent theme scans built component output under `dist/components`. A compone The Twig template can match the metadata filename or the component directory name. -Discovery uses request-only memoization by default. See [Parent and child theme architecture](parent-child-architecture.md#optional-persistent-discovery-cache) for the optional persistent discovery cache. +Discovery is memoized during each request. Persistent discovery caching is enabled by default outside local, development, or `WP_DEBUG` environments; see [Parent and child theme architecture](parent-child-architecture.md#optional-persistent-discovery-cache). ## Example diff --git a/docs/asset-loading.md b/docs/asset-loading.md index 5cb8607..617588d 100644 --- a/docs/asset-loading.md +++ b/docs/asset-loading.md @@ -2,6 +2,12 @@ The parent theme loads built assets from the active child theme first, then parent fallback assets. This keeps project builds in the child theme while allowing the parent to provide reusable runtime behavior. +## Resolution order + +Asset loading is manifest-first and scanner-backed. For each supported scope, the parent checks a valid child `dist/emulsify-assets.json` before a parent manifest. When no manifest exists, the manifest is invalid, or the manifest does not declare the requested scope, the recursive scanner remains the fallback. + +Manifest data is memoized for the life of the service instance during a PHP request. Scanner fallback records are also memoized per theme-relative directory, so the CSS and JavaScript enqueue passes do not walk the same directory tree twice. Filters still receive the same extension-specific asset records they received before memoization. + ## Scanner behavior When no usable manifest is present, Emulsify WordPress scans these built asset directories: @@ -19,7 +25,7 @@ For ACF/Twig components, files declared in `*.component.json` `assets` metadata ## Manifest behavior -Projects can optionally ship `dist/emulsify-assets.json` to avoid recursive discovery on every request. The manifest is child-first: a valid child manifest is used before a parent manifest. If no manifest exists, if the manifest is invalid JSON, or if a scope is not declared, the current scanner path remains the fallback for that scope. +Projects can optionally ship `dist/emulsify-assets.json` to avoid recursive discovery during normal requests. The manifest is child-first: a valid child manifest is used before a parent manifest. If no manifest exists, if the manifest is invalid JSON, or if a scope is not declared, the current scanner path remains the fallback for that scope. Manifest entry paths are relative to the manifest directory. For `dist/emulsify-assets.json`, use paths such as `global/app.css` or `components/card.js`. @@ -75,7 +81,7 @@ Block-specific entries are accepted under `blocks` and are used by ACF/Twig bloc ## Performance -The scanner is simple and requires no build integration. A manifest is useful for larger projects because build tooling can write the exact asset list, hashes, and dependencies once, and PHP can avoid walking `dist/global` and `dist/components` on every request. +The scanner is simple and requires no build integration. A manifest is useful for larger projects because build tooling can write the exact asset list, hashes, and dependencies once, and PHP can usually avoid walking `dist/global` and `dist/components` during normal requests. ## Asset filters diff --git a/docs/block-patterns.md b/docs/block-patterns.md index 2b3151c..3bebf1f 100644 --- a/docs/block-patterns.md +++ b/docs/block-patterns.md @@ -1,5 +1,7 @@ # Block patterns +Related: [ACF/Twig blocks](acf-twig-blocks.md), [Native Gutenberg blocks](native-gutenberg-blocks.md), [Core block Twig rendering](core-block-twig-rendering.md). + The parent theme registers JSON block patterns from active child and parent theme `patterns` directories. Discovery is child-first and scans direct `patterns/*.json` files only, so a child theme can override a parent pattern file by using the same JSON filename. `patterns/categories.json` and `patterns/_categories.json` are reserved for optional category metadata and are not registered as block patterns. If no pattern directory exists, or if WordPress pattern registration functions are unavailable, the service exits without changing editor behavior. diff --git a/docs/core-block-twig-rendering.md b/docs/core-block-twig-rendering.md index afe09eb..284bf27 100644 --- a/docs/core-block-twig-rendering.md +++ b/docs/core-block-twig-rendering.md @@ -1,5 +1,7 @@ # Core block Twig rendering +Related: [ACF/Twig blocks](acf-twig-blocks.md), [Native Gutenberg blocks](native-gutenberg-blocks.md), [Block patterns](block-patterns.md). + The parent theme includes an experimental `CoreBlockTwigRenderer` service that can render explicitly mapped WordPress blocks through Twig templates. It is disabled by default and does not change frontend output unless a child theme or project plugin opts in. Use this carefully. Replacing core block output can affect block validation, style supports, accessibility attributes, plugin integrations, and future WordPress markup changes. Keep Twig output compatible with the saved block attributes and test editing, saving, reloading, and frontend rendering for every mapped block. diff --git a/docs/native-gutenberg-blocks.md b/docs/native-gutenberg-blocks.md index f121b35..404bee8 100644 --- a/docs/native-gutenberg-blocks.md +++ b/docs/native-gutenberg-blocks.md @@ -1,5 +1,7 @@ # Native Gutenberg blocks +Related: [ACF/Twig blocks](acf-twig-blocks.md), [Core block Twig rendering](core-block-twig-rendering.md), [Block patterns](block-patterns.md). + Native Gutenberg blocks use the WordPress Block API. They are separate from Twig components and ACF/Twig blocks. ## Gutenberg and block paths @@ -10,7 +12,7 @@ The starter does not include an active native block example. Native blocks usual See [Component recipes](component-recipes.md) for a short `block.json` example and guidance on when to choose native blocks. -Discovery uses request-only memoization by default. See [Parent and child theme architecture](parent-child-architecture.md#optional-persistent-discovery-cache) for the optional persistent discovery cache. +Discovery is memoized during each request. Persistent discovery caching is enabled by default outside local, development, or `WP_DEBUG` environments; see [Parent and child theme architecture](parent-child-architecture.md#optional-persistent-discovery-cache). ## Block assets diff --git a/docs/parent-child-architecture.md b/docs/parent-child-architecture.md index a12c087..6ba8355 100644 --- a/docs/parent-child-architecture.md +++ b/docs/parent-child-architecture.md @@ -20,6 +20,8 @@ The parent theme owns reusable runtime behavior: Most parent runtime code lives in `includes/`. The root `functions.php` should stay thin. +Runtime classes are grouped by domain: `Runtime`, `Blocks`, `Editor`, `Acf`, `Cli`, `Support`, and `Twig`. Composer PSR-4 autoloading loads these classes when Composer is available, and Bootstrap keeps a small fallback loader for manual theme installs without Composer. + ## Child theme responsibilities Generated child themes own: @@ -50,6 +52,12 @@ Built child theme components are discovered before built parent theme components Discovery is memoized for the current PHP request. ACF/Twig and native block registration share one filesystem scan by default. +## Asset resolution priority + +Built assets are resolved child-first. When `dist/emulsify-assets.json` exists and is valid, the manifest is used before recursive scanner fallback for each asset scope. If a child manifest is valid, it takes priority over the parent manifest; if a scope is missing or invalid, that scope falls back to scanning built child and parent directories. + +Manifest reads are memoized per `AssetManifest` instance during a request. Scanner fallback also memoizes raw file records per theme-relative directory, then filters CSS and JavaScript in memory for each enqueue pass while preserving the existing asset filters. + ## Optional persistent discovery cache Persistent component discovery caching is enabled by default in production and staging environments, and disabled by default in local, development, or `WP_DEBUG` environments. Projects can still opt out or force it on with: diff --git a/docs/post-2x-optimization-roadmap.md b/docs/post-2x-optimization-roadmap.md index f174141..ed719b3 100644 --- a/docs/post-2x-optimization-roadmap.md +++ b/docs/post-2x-optimization-roadmap.md @@ -2,52 +2,70 @@ Emulsify WordPress 2.0 should stay focused on the parent and generated child theme release contract. The items below are follow-up opportunities for focused minor releases, not 2.0 blockers. +## Release posture + +- Ship 2.0 without adding new runtime features. +- Keep 2.0 changes limited to release hardening, documentation cleanup, and bug fixes. +- Group future improvements into small minor releases with focused smoke or PHPUnit coverage. + +## Done in 2.0 + +- Composer PSR-4 autoloading and grouped runtime directories under `includes/`. +- Shared `Support` helpers including `AssetRecord`, `AssetEnqueuer`, `Diagnostics`, `FileDiscovery`, and `ProjectConfig`. +- A named `ProjectComponentLoader` for `project_machine_name:component_name` Twig references. +- Optional manifest-driven asset loading through `dist/emulsify-assets.json`, with scanner fallback. +- Per-request memoization for manifest reads, asset discovery, editor config, and component discovery. +- Block-scoped asset support for ACF/Twig blocks while native blocks keep WordPress `block.json` asset handling. +- Optional persistent discovery caching that is enabled by default outside local, development, or `WP_DEBUG` environments. +- `switch_theme` cache invalidation and cache-key parts for theme versions, environment, and manifest mtime. +- Safer WP-CLI `--force` replacement checks using generated child theme metadata. + ## Recommended implementation order -1. Ship 2.0 without adding new runtime features. -2. Build on Composer PSR-4 autoloading with grouped runtime directories in a minor release. -3. Add optional manifest-driven asset loading. -4. Add CLI diagnostics such as `wp emulsify doctor`. -5. Expand diagnostics around optional persistent discovery caching once projects have real-world invalidation guidance feedback. +1. Keep the 2.0 release branch focused on release readiness only. +2. Add read-only CLI diagnostics such as `wp emulsify doctor`. +3. Migrate the highest-value smoke harness checks into PHPUnit or another WordPress-aware test layer. +4. Explore block.json-based ACF registration only after the current `*.component.json` path is stable in real projects. +5. Expand cache and manifest diagnostics after maintainers have real-world invalidation guidance feedback. ## Code organization -- Move runtime PHP classes into grouped directories such as setup, assets, blocks, editor, and CLI while preserving public hooks and filters. -- Move current legacy class filenames toward clean PSR-4 paths in a minor release. -- Keep compatibility shims or clear upgrade notes for any class-loading paths that existing child themes may reference. +- Keep the current domain folders stable through the 2.0 release. +- Add tests around service boundaries before further class movement. +- Avoid reintroducing compatibility shims for class names that never shipped in a stable release. ## Runtime architecture -- Split bootstrap responsibilities into small provider-style classes so setup, Twig, assets, block registration, and editor policy can be reasoned about independently. -- Keep the parent theme focused on reusable WordPress runtime behavior; project behavior should remain in child themes or site plugins. -- Document service boundaries before adding new runtime services. +- Consider a small service-provider layer if Bootstrap grows again. +- Keep parent services focused on reusable WordPress runtime behavior. +- Keep project-specific behavior in child themes or site plugins. ## Asset loading and performance -- Add optional manifest-driven asset loading for `dist/global` and component assets. -- Keep current directory scanning as the fallback while manifests are optional. -- Avoid requiring build output in fresh Whisk child themes until a project installs a component system. +- Add build-time manifest validation so malformed `dist/emulsify-assets.json` files fail earlier. +- Document CDN or asset-host filters if real projects need them. +- Add diagnostics that explain whether a page used manifest records, scanner fallback, or block-scoped assets. ## Component/block discovery -- Refine child-first discovery diagnostics for duplicate ACF/Twig and native block definitions. -- Keep optional persistent discovery caching production-enabled but filter-controlled; future work should focus on diagnostics and invalidation guidance, not UI. -- Keep discovery safe when ACF or native block APIs are unavailable. +- Add `wp emulsify doctor` checks for active child theme metadata, generated lineage, Timber availability, build output, and component discovery state. +- Improve duplicate and cache diagnostics for optional persistent discovery caching without adding an admin UI. +- Keep invalidation guidance close to the filters that alter roots or cache-key parts. ## CLI diagnostics -- Add a read-only `wp emulsify doctor` command that reports parent install state, active child theme, `Template` header, Timber availability, generated metadata, and missing build output. -- Use diagnostics before adding repair commands. -- Keep generation behavior stable so the 2.0 WP-CLI contract remains predictable. +- Start with read-only reporting before adding repair commands. +- Report parent install state, active child theme, `Template` header, `project.platform`, `generatedFrom`, `generatedFromVersion`, Timber status, and missing build output. +- Include cache status and manifest path checks once those messages are stable. ## Editor and block feature modules -- Expand editor enhancements as opt-in modules, not broad parent-theme defaults. -- Treat ACF/Twig blocks, native Gutenberg blocks, core block Twig rendering, and pattern policies as independently enableable areas. -- Add documentation and smoke coverage with each module. +- Keep editor enhancements opt-in and independently testable. +- Evaluate block.json-based ACF registration as a follow-up to the current ACF/Twig metadata path. +- Add feature-specific docs and smoke/PHPUnit coverage with each new editor or block module. ## Documentation and support tooling -- Maintain upgrade checklists, troubleshooting notes, and component recipes as follow-up support material. -- Surface generated child theme lineage metadata in diagnostics and support docs. +- Migrate smoke harness behavior into PHPUnit where WordPress APIs are easier to model. +- Maintain upgrade checklists, troubleshooting notes, component recipes, and release checklists as support material. - Keep examples as recipes instead of active starter files in `whisk/src/components`. From 9bd0c513b44fc04e08e21e724cd05d017b010dbe Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 21:39:01 -0500 Subject: [PATCH 53/55] chore(lint): adopt phpcs wpcs and phpstan for runtime php --- .github/scripts/pr-validation.cjs | 3 +- .github/scripts/release-check.cjs | 8 +++ .github/workflows/semantic-release.yml | 3 + .github/workflows/theme-readiness.yml | 3 + composer.json | 13 +++++ includes/Acf/LocalJson.php | 3 +- includes/Blocks/AcfBlocks.php | 12 ++-- includes/Blocks/ComponentLocator.php | 10 ++-- includes/Blocks/CoreBlockTwigRenderer.php | 26 +++++---- includes/Blocks/Patterns.php | 13 +++-- includes/Blocks/Registry.php | 7 ++- includes/Cli/GenerateChildThemeCommand.php | 3 + includes/Editor/Enhancements.php | 8 +-- includes/Editor/PatternGovernance.php | 3 +- includes/Editor/Policy.php | 10 ++-- includes/Editor/PolicyOptions.php | 18 +++--- includes/Runtime/Context.php | 4 +- includes/Runtime/MissingTimber.php | 4 +- includes/Runtime/Twig.php | 4 +- includes/Support/AttributeBag.php | 8 +-- includes/Support/Diagnostics.php | 2 + includes/Support/ProjectConfig.php | 2 +- includes/Twig/ProjectComponentLoader.php | 25 ++++----- package.json | 3 +- phpcs.xml.dist | 65 ++++++++++++++++++++++ phpstan.neon.dist | 17 ++++++ single.php | 12 ++-- 27 files changed, 209 insertions(+), 80 deletions(-) create mode 100644 phpcs.xml.dist create mode 100644 phpstan.neon.dist diff --git a/.github/scripts/pr-validation.cjs b/.github/scripts/pr-validation.cjs index 9a7d697..d551b63 100644 --- a/.github/scripts/pr-validation.cjs +++ b/.github/scripts/pr-validation.cjs @@ -23,9 +23,8 @@ function run(label, command, args) { } run('Validate Composer metadata', 'composer', ['validate', '--no-check-publish', '--strict']); -run('Install Composer dependencies for Twig smoke coverage', 'composer', [ +run('Install Composer dependencies for PHP lint and Twig smoke coverage', 'composer', [ 'install', - '--no-dev', '--no-interaction', '--no-progress', '--prefer-dist', diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 8bb8805..1b1f3d2 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -320,6 +320,8 @@ function runStaticChecks() { 'docs/upgrading-1x-to-2x.md', 'docs/wp-cli-child-theme-generation.md', 'composer.json', + 'phpcs.xml.dist', + 'phpstan.neon.dist', 'functions.php', 'includes/Bootstrap.php', 'includes/Acf/LocalJson.php', @@ -403,6 +405,9 @@ function runStaticChecks() { ensure(rootPackage.bugs.url === 'https://github.com/emulsify-ds/emulsify-wordpress/issues', 'package.json bugs.url should target emulsify-wordpress.'); ensure(rootPackage.scripts['pr:check'] === 'node .github/scripts/pr-validation.cjs', 'package.json should expose npm run pr:check.'); ensure(rootPackage.scripts['release:check'] === 'node .github/scripts/release-check.cjs', 'package.json should expose npm run release:check.'); + ensure(rootPackage.scripts['lint:php'].includes('vendor/bin/phpcs -q'), 'package.json lint:php should run PHPCS.'); + ensure(rootPackage.scripts['lint:php'].includes('vendor/bin/phpstan analyse --no-progress'), 'package.json lint:php should run PHPStan.'); + ensure(rootPackage.scripts['lint:php:fix'] === 'vendor/bin/phpcbf', 'package.json should expose npm run lint:php:fix.'); ensure(rootPackage.scripts['smoke:acf-json'] === 'php .github/scripts/acf-local-json-smoke.php', 'package.json should expose npm run smoke:acf-json.'); ensure(rootPackage.scripts['smoke:asset-manifest'] === 'php .github/scripts/asset-manifest-smoke.php', 'package.json should expose npm run smoke:asset-manifest.'); ensure(rootPackage.scripts['smoke:attributes'] === 'php .github/scripts/attribute-helper-smoke.php', 'package.json should expose npm run smoke:attributes.'); @@ -430,6 +435,9 @@ function runStaticChecks() { ensure(!Object.hasOwn(composer, 'prefer-stable'), 'composer.json should not keep prefer-stable when stable-only constraints are sufficient.'); ensure(composer.require && typeof composer.require.php === 'string' && composer.require.php.startsWith('>=8.3'), 'composer.json should enforce the PHP 8.3 runtime floor.'); ensure(composer.require && composer.require['timber/timber'] === '^2.3', 'composer.json should keep the Timber 2 dependency constraint.'); + ensure(composer['require-dev'] && composer['require-dev']['wp-coding-standards/wpcs'], 'composer.json should provide WordPress Coding Standards for PHP linting.'); + ensure(composer['require-dev'] && composer['require-dev']['phpstan/phpstan'], 'composer.json should provide PHPStan for static analysis.'); + ensure(composer.config && composer.config['allow-plugins'] && composer.config['allow-plugins']['dealerdirect/phpcodesniffer-composer-installer'], 'composer.json should allow the PHPCS standards installer plugin.'); ensure(composer.autoload && composer.autoload['psr-4'] && composer.autoload['psr-4']['Emulsify\\Theme\\'] === 'includes/', 'composer.json should expose the runtime namespace through PSR-4 autoloading.'); ensure(!Object.hasOwn(composer.autoload, 'classmap'), 'composer.json should rely on PSR-4 runtime paths instead of classmap loading.'); ensure(!Object.hasOwn(composer.autoload, 'files'), 'composer.json should not load removed compatibility files.'); diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 03e4f14..8675e56 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -64,6 +64,9 @@ jobs: - name: Validate Composer metadata run: composer validate --no-check-publish --strict + - name: Install Composer dependencies for PHP lint + run: composer install --no-interaction --no-progress --prefer-dist + - name: Run PHP lint run: npm run lint:php diff --git a/.github/workflows/theme-readiness.yml b/.github/workflows/theme-readiness.yml index 9b9f8d1..508522c 100644 --- a/.github/workflows/theme-readiness.yml +++ b/.github/workflows/theme-readiness.yml @@ -60,6 +60,9 @@ jobs: - name: Validate Composer metadata run: composer validate --no-check-publish --strict + - name: Install Composer dependencies for PHP lint + run: composer install --no-interaction --no-progress --prefer-dist + - name: Run runtime npm audit run: npm audit --omit=dev diff --git a/composer.json b/composer.json index 844c2f8..31173ad 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,20 @@ "optimize-autoloader": true, "sort-packages": true, "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, "composer/installers": true } + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.2", + "php-stubs/acf-pro-stubs": "^6.8", + "php-stubs/wordpress-stubs": "^6.9", + "php-stubs/wp-cli-stubs": "^2.12", + "phpcsstandards/phpcsextra": "^1.5", + "phpcsstandards/phpcsutils": "^1.2", + "phpstan/phpstan": "^2.2", + "squizlabs/php_codesniffer": "^3.13", + "szepeviktor/phpstan-wordpress": "^2.0", + "wp-coding-standards/wpcs": "^3.3" } } diff --git a/includes/Acf/LocalJson.php b/includes/Acf/LocalJson.php index 4e441bd..c26a733 100644 --- a/includes/Acf/LocalJson.php +++ b/includes/Acf/LocalJson.php @@ -177,7 +177,8 @@ private function normalize_paths( array $paths ): array { continue; } - $key = realpath( $path ) ?: $path; + $realpath = realpath( $path ); + $key = false !== $realpath ? $realpath : $path; if ( isset( $seen[ $key ] ) ) { continue; diff --git a/includes/Blocks/AcfBlocks.php b/includes/Blocks/AcfBlocks.php index 96d222e..ac4d94b 100644 --- a/includes/Blocks/AcfBlocks.php +++ b/includes/Blocks/AcfBlocks.php @@ -250,11 +250,11 @@ private function block_args( array $component, array $metadata ): array { 'mode' => 'preview', ); - $args = array_merge( $defaults, $metadata ); - $args['name'] = $this->normalize_block_name( $args['name'] ?? '', $component['slug'] ); - $args['render_callback'] = array( $this, 'render_block' ); - $args['twig_template'] = $component['template']; - $args['data'] = isset( $args['data'] ) && is_array( $args['data'] ) ? $args['data'] : array(); + $args = array_merge( $defaults, $metadata ); + $args['name'] = $this->normalize_block_name( $args['name'] ?? '', $component['slug'] ); + $args['render_callback'] = array( $this, 'render_block' ); + $args['twig_template'] = $component['template']; + $args['data'] = isset( $args['data'] ) && is_array( $args['data'] ) ? $args['data'] : array(); $args['data']['twig_template'] = $component['template']; return $args; @@ -292,7 +292,7 @@ private function with_asset_records( array $args, array $component, array $metad return $args; } - $existing_callback = isset( $args['enqueue_assets'] ) && is_callable( $args['enqueue_assets'] ) ? $args['enqueue_assets'] : null; + $existing_callback = isset( $args['enqueue_assets'] ) && is_callable( $args['enqueue_assets'] ) ? $args['enqueue_assets'] : null; $args['enqueue_assets'] = $this->enqueue_assets_callback( $asset_records, $existing_callback, $this->block_name( $args ) ); return $args; diff --git a/includes/Blocks/ComponentLocator.php b/includes/Blocks/ComponentLocator.php index fa568e7..b14e608 100644 --- a/includes/Blocks/ComponentLocator.php +++ b/includes/Blocks/ComponentLocator.php @@ -376,11 +376,11 @@ private function persistent_cache_enabled(): bool { * @return string Transient key. */ private function component_files_cache_key(): string { - $stylesheet = $this->stylesheet(); - $template = $this->template(); - $stylesheet_directory = function_exists( 'get_stylesheet_directory' ) ? get_stylesheet_directory() : ''; - $template_directory = function_exists( 'get_template_directory' ) ? get_template_directory() : ''; - $key_parts = array( + $stylesheet = $this->stylesheet(); + $template = $this->template(); + $stylesheet_directory = function_exists( 'get_stylesheet_directory' ) ? get_stylesheet_directory() : ''; + $template_directory = function_exists( 'get_template_directory' ) ? get_template_directory() : ''; + $key_parts = array( 'stylesheet' => $stylesheet, 'template' => $template, 'stylesheet_version' => $this->theme_version( $stylesheet, $stylesheet_directory ), diff --git a/includes/Blocks/CoreBlockTwigRenderer.php b/includes/Blocks/CoreBlockTwigRenderer.php index c852f0b..711fb96 100644 --- a/includes/Blocks/CoreBlockTwigRenderer.php +++ b/includes/Blocks/CoreBlockTwigRenderer.php @@ -48,7 +48,7 @@ public function render_block( string $block_content, array $block, $instance = n return $block_content; } - if ( ! class_exists( '\Timber\Timber' ) || ! method_exists( '\Timber\Timber', 'compile' ) ) { + if ( ! class_exists( '\Timber\Timber' ) ) { return $this->render_error( $block_content, $this->translate( 'Timber is not available for mapped block rendering.', 'emulsify' ), $block, $template ); } @@ -99,7 +99,7 @@ private function enabled(): bool { private function context( array $block, string $block_content, $instance, string $template ): array { $context = array(); - if ( class_exists( '\Timber\Timber' ) && method_exists( '\Timber\Timber', 'context' ) ) { + if ( class_exists( '\Timber\Timber' ) ) { $timber_context = \Timber\Timber::context(); $context = is_array( $timber_context ) ? $timber_context : array(); } @@ -211,7 +211,7 @@ private function template_map(): array { $map = array(); foreach ( $filtered as $block_name => $template ) { - if ( ! is_scalar( $block_name ) || ! is_scalar( $template ) ) { + if ( ! is_scalar( $template ) ) { continue; } @@ -331,13 +331,17 @@ private function cleanup_classes( string $html, array $block ): string { private function cleanup_class_names( array $block ): array { $block_name = $this->block_name( $block ); $slug = false === strpos( $block_name, '/' ) ? $block_name : substr( $block_name, strpos( $block_name, '/' ) + 1 ); - $classes = array_filter( - array( - 'wp-block', - 'wp-block-' . $this->sanitize_html_class( $slug ), - 'wp-block-' . $this->sanitize_html_class( str_replace( '/', '-', $block_name ) ), - ) - ); + $classes = array( 'wp-block' ); + $slug_class = $this->sanitize_html_class( $slug ); + $name_class = $this->sanitize_html_class( str_replace( '/', '-', $block_name ) ); + + if ( '' !== $slug_class ) { + $classes[] = 'wp-block-' . $slug_class; + } + + if ( '' !== $name_class ) { + $classes[] = 'wp-block-' . $name_class; + } /** * Filters class names removed from mapped block Twig output. @@ -444,6 +448,7 @@ private function sanitize_html_class( string $class_name ): string { * @return string Escaped translated text. */ private function esc_html__( string $text, string $domain ): string { + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralDomain -- Internal translation proxy receives literal strings from this class. return function_exists( 'esc_html__' ) ? esc_html__( $text, $domain ) : $this->esc_html( $text ); } @@ -455,6 +460,7 @@ private function esc_html__( string $text, string $domain ): string { * @return string Translated text. */ private function translate( string $text, string $domain ): string { + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralDomain -- Internal translation proxy receives literal strings from this class. return function_exists( '__' ) ? __( $text, $domain ) : $text; } diff --git a/includes/Blocks/Patterns.php b/includes/Blocks/Patterns.php index 8d351a5..3181a3e 100644 --- a/includes/Blocks/Patterns.php +++ b/includes/Blocks/Patterns.php @@ -90,7 +90,7 @@ private function pattern_records(): array { } $seen_names[ $name ] = true; - $patterns[] = array( + $patterns[] = array( 'name' => $name, 'args' => $args, 'data' => $data, @@ -114,7 +114,8 @@ private function pattern_files(): array { foreach ( FileDiscovery::file_records( $this->pattern_directories(), array( 'json' ), false ) as $file ) { $path = $file['path']; $relative = basename( $path ); - $real = realpath( $path ) ?: $path; + $realpath = realpath( $path ); + $real = false !== $realpath ? $realpath : $path; if ( $this->is_category_metadata_file( $relative ) ) { continue; @@ -137,9 +138,9 @@ private function pattern_files(): array { continue; } - $seen_file_path[ $real ] = true; + $seen_file_path[ $real ] = true; $seen_relative[ $relative ] = true; - $files[] = array( + $files[] = array( 'path' => $path, 'relative' => $relative, 'source' => $file['root_source'], @@ -499,7 +500,7 @@ static function ( string $value ): bool { * @return bool TRUE when the pattern is already registered. */ private function pattern_registered( string $name ): bool { - if ( ! class_exists( '\WP_Block_Patterns_Registry' ) || ! method_exists( '\WP_Block_Patterns_Registry', 'get_instance' ) ) { + if ( ! class_exists( '\WP_Block_Patterns_Registry' ) ) { return false; } @@ -516,7 +517,7 @@ private function pattern_registered( string $name ): bool { */ private function category_registered( string $name ): bool { foreach ( array( '\WP_Block_Pattern_Categories_Registry', '\WP_Block_Pattern_Category_Registry' ) as $class ) { - if ( ! class_exists( $class ) || ! method_exists( $class, 'get_instance' ) ) { + if ( ! class_exists( $class ) ) { continue; } diff --git a/includes/Blocks/Registry.php b/includes/Blocks/Registry.php index 611b33a..c61cea0 100644 --- a/includes/Blocks/Registry.php +++ b/includes/Blocks/Registry.php @@ -36,7 +36,12 @@ public function __construct( ?AssetManifest $manifest = null ) { * @return void */ public function register(): void { - add_action( 'switch_theme', array( ComponentLocator::class, 'clear_discovery_cache' ) ); + add_action( + 'switch_theme', + static function (): void { + ComponentLocator::clear_discovery_cache(); + } + ); $components = new ComponentLocator(); diff --git a/includes/Cli/GenerateChildThemeCommand.php b/includes/Cli/GenerateChildThemeCommand.php index 693ea57..7421743 100644 --- a/includes/Cli/GenerateChildThemeCommand.php +++ b/includes/Cli/GenerateChildThemeCommand.php @@ -277,6 +277,7 @@ function ( array $data ) use ( $theme_label, $machine_name, $version ): array { $data['project'] = array(); } + // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- project.platform is a machine-readable adapter key. $data['project']['platform'] = 'wordpress'; $data['project']['name'] = $theme_label; $data['project']['machineName'] = $machine_name; @@ -614,7 +615,9 @@ private function get_destination_replacement_error( string $destination, string return 'project.emulsify.json is missing project metadata'; } + // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- project.platform is a machine-readable adapter key. if ( 'wordpress' !== ( $project['project']['platform'] ?? null ) ) { + // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Diagnostic quotes the machine-readable project.platform value. return 'project.emulsify.json is missing project.platform: wordpress'; } diff --git a/includes/Editor/Enhancements.php b/includes/Editor/Enhancements.php index a244dfe..665876e 100644 --- a/includes/Editor/Enhancements.php +++ b/includes/Editor/Enhancements.php @@ -111,8 +111,8 @@ public function render_block_data( array $parsed_block, array $source_block = ar return $parsed_block; } - $class = $this->string_option( $settings['blockClassName'] ?? '', 'has-media-caption' ); - $parsed_block['attrs'] = isset( $parsed_block['attrs'] ) && is_array( $parsed_block['attrs'] ) ? $parsed_block['attrs'] : array(); + $class = $this->string_option( $settings['blockClassName'] ?? '', 'has-media-caption' ); + $parsed_block['attrs'] = isset( $parsed_block['attrs'] ) && is_array( $parsed_block['attrs'] ) ? $parsed_block['attrs'] : array(); $parsed_block['attrs']['className'] = $this->append_class( $parsed_block['attrs']['className'] ?? '', $class ); return $parsed_block; @@ -449,8 +449,8 @@ private function attachment_id( array $attributes ): int { * @return string Class string. */ private function append_class( $current, string $class ): string { - $classes = is_scalar( $current ) ? preg_split( '/\s+/', (string) $current ) : array(); - $classes = is_array( $classes ) ? $classes : array(); + $classes = is_scalar( $current ) ? preg_split( '/\s+/', (string) $current ) : array(); + $classes = is_array( $classes ) ? $classes : array(); $classes[] = $class; return trim( implode( ' ', array_unique( array_filter( $classes ) ) ) ); diff --git a/includes/Editor/PatternGovernance.php b/includes/Editor/PatternGovernance.php index 97a3630..235f061 100644 --- a/includes/Editor/PatternGovernance.php +++ b/includes/Editor/PatternGovernance.php @@ -187,7 +187,8 @@ private function pattern_json_files( array $options ): array { } $file_path = $file->getPathname(); - $key = realpath( $file_path ) ?: $file_path; + $realpath = realpath( $file_path ); + $key = false !== $realpath ? $realpath : $file_path; if ( isset( $seen[ $key ] ) ) { continue; diff --git a/includes/Editor/Policy.php b/includes/Editor/Policy.php index 72e1ca2..1bcfd6b 100644 --- a/includes/Editor/Policy.php +++ b/includes/Editor/Policy.php @@ -50,11 +50,11 @@ final class Policy { /** * Constructor. * - * @param PolicyOptions|null $options Policy option resolver. - * @param AllowedBlockTypes|null $allowed_blocks Allowed block policy. - * @param PatternGovernance|null $patterns Pattern visibility policy. - * @param UserPatternPermissions|null $user_patterns User pattern policy. - * @param BlockSupportOverrides|null $support_overrides Block support override policy. + * @param PolicyOptions|null $options Policy option resolver. + * @param AllowedBlockTypes|null $allowed_blocks Allowed block policy. + * @param PatternGovernance|null $patterns Pattern visibility policy. + * @param UserPatternPermissions|null $user_patterns User pattern policy. + * @param BlockSupportOverrides|null $support_overrides Block support override policy. */ public function __construct( ?PolicyOptions $options = null, diff --git a/includes/Editor/PolicyOptions.php b/includes/Editor/PolicyOptions.php index 9e9898d..5977840 100644 --- a/includes/Editor/PolicyOptions.php +++ b/includes/Editor/PolicyOptions.php @@ -20,16 +20,16 @@ final class PolicyOptions { */ public function get( $context = null ): array { $options = array( - 'allowed_block_types' => null, - 'merge_allowed_block_types' => true, - 'auto_allow_pattern_blocks' => false, - 'pattern_directories' => null, - 'pattern_namespaces' => array(), + 'allowed_block_types' => null, + 'merge_allowed_block_types' => true, + 'auto_allow_pattern_blocks' => false, + 'pattern_directories' => null, + 'pattern_namespaces' => array(), 'disable_user_patterns_for_non_admins' => false, - 'admin_capability' => 'manage_options', - 'restrict_wp_block_creation' => false, - 'wp_block_create_capability' => 'manage_options', - 'block_support_overrides' => array(), + 'admin_capability' => 'manage_options', + 'restrict_wp_block_creation' => false, + 'wp_block_create_capability' => 'manage_options', + 'block_support_overrides' => array(), ); /** diff --git a/includes/Runtime/Context.php b/includes/Runtime/Context.php index 6acfdce..e90d59a 100644 --- a/includes/Runtime/Context.php +++ b/includes/Runtime/Context.php @@ -81,7 +81,7 @@ private function theme(): array { * @return mixed Timber menu or null. */ private function menu() { - if ( ! class_exists( '\Timber\Timber' ) || ! method_exists( '\Timber\Timber', 'get_menu' ) ) { + if ( ! class_exists( '\Timber\Timber' ) ) { return null; } @@ -104,7 +104,7 @@ private function menu() { * @return mixed Timber post or null. */ private function post() { - if ( ! is_singular() || ! class_exists( '\Timber\Timber' ) || ! method_exists( '\Timber\Timber', 'get_post' ) ) { + if ( ! is_singular() || ! class_exists( '\Timber\Timber' ) ) { return null; } diff --git a/includes/Runtime/MissingTimber.php b/includes/Runtime/MissingTimber.php index 8cb615a..cac818f 100644 --- a/includes/Runtime/MissingTimber.php +++ b/includes/Runtime/MissingTimber.php @@ -80,7 +80,9 @@ private function is_ajax_request(): bool { return wp_doing_ajax(); } - return defined( 'DOING_AJAX' ) && DOING_AJAX; + $doing_ajax = defined( 'DOING_AJAX' ) ? constant( 'DOING_AJAX' ) : false; + + return true === $doing_ajax; } /** diff --git a/includes/Runtime/Twig.php b/includes/Runtime/Twig.php index 6f3d1d1..4c7c30c 100644 --- a/includes/Runtime/Twig.php +++ b/includes/Runtime/Twig.php @@ -132,8 +132,8 @@ public function functions( array $functions ): array { * @return AttributeBag HTML attributes. */ public function bem( ...$arguments ): AttributeBag { - $context = $this->shift_twig_context( $arguments ); - $options = $this->normalize_bem_options( + $context = $this->shift_twig_context( $arguments ); + $options = $this->normalize_bem_options( $arguments[0] ?? '', $arguments[1] ?? array(), $arguments[2] ?? '', diff --git a/includes/Support/AttributeBag.php b/includes/Support/AttributeBag.php index 56ec4c4..68b0ead 100644 --- a/includes/Support/AttributeBag.php +++ b/includes/Support/AttributeBag.php @@ -382,10 +382,10 @@ private static function escapeAttributeValue( string $value ): string { return strtr( $value, array( - '&' => '&', - '"' => '"', - '<' => '<', - '>' => '>', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', ) ); } diff --git a/includes/Support/Diagnostics.php b/includes/Support/Diagnostics.php index de3c86e..3c070ec 100644 --- a/includes/Support/Diagnostics.php +++ b/includes/Support/Diagnostics.php @@ -77,9 +77,11 @@ static function () use ( $messages, $heading ): void { return; } + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Heading is escaped by self::esc_html(). echo '

    ' . self::esc_html( $heading ) . '

      '; foreach ( $messages as $message ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Message is escaped by self::esc_html(). echo '
    • ' . self::esc_html( $message ) . '
    • '; } diff --git a/includes/Support/ProjectConfig.php b/includes/Support/ProjectConfig.php index 8a2f7db..83b8c8c 100644 --- a/includes/Support/ProjectConfig.php +++ b/includes/Support/ProjectConfig.php @@ -43,7 +43,7 @@ public static function read(): array { public static function machine_name(): string { $data = self::read(); - if ( ! is_array( $data ) || empty( $data['project']['machineName'] ) ) { + if ( empty( $data['project']['machineName'] ) ) { return ''; } diff --git a/includes/Twig/ProjectComponentLoader.php b/includes/Twig/ProjectComponentLoader.php index 67beef5..8b89d40 100644 --- a/includes/Twig/ProjectComponentLoader.php +++ b/includes/Twig/ProjectComponentLoader.php @@ -12,13 +12,6 @@ */ final class ProjectComponentLoader implements \Twig\Loader\LoaderInterface { - /** - * Project machine name. - * - * @var string - */ - private $machine_name; - /** * Project template prefix. * @@ -50,15 +43,14 @@ final class ProjectComponentLoader implements \Twig\Loader\LoaderInterface { /** * Constructs the project component loader wrapper. * - * @param string $machine_name Active project machine name. - * @param array $roots Component root paths. + * @param string $machine_name Active project machine name. + * @param array $roots Component root paths. * @param \Twig\Loader\LoaderInterface $loader Wrapped Twig loader. */ public function __construct( string $machine_name, array $roots, \Twig\Loader\LoaderInterface $loader ) { - $this->machine_name = $machine_name; - $this->prefix = $machine_name . ':'; - $this->roots = $roots; - $this->loader = $loader; + $this->prefix = $machine_name . ':'; + $this->roots = $roots; + $this->loader = $loader; } /** @@ -119,6 +111,7 @@ public function getNamespaces(): array { * * @param string $name Template logical name. * @return \Twig\Source Template source. + * @throws \Twig\Error\LoaderError When project template resolution fails. */ public function getSourceContext( string $name ): \Twig\Source { $path = $this->find_project_template( $name ); @@ -127,6 +120,7 @@ public function getSourceContext( string $name ): \Twig\Source { $contents = file_get_contents( $path ); if ( false === $contents ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not rendered HTML. throw new \Twig\Error\LoaderError( sprintf( 'Unable to read project component template "%s".', $name ) ); } @@ -134,6 +128,7 @@ public function getSourceContext( string $name ): \Twig\Source { } if ( $this->is_project_reference( $name ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not rendered HTML. throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); } @@ -145,6 +140,7 @@ public function getSourceContext( string $name ): \Twig\Source { * * @param string $name Template logical name. * @return string Cache key. + * @throws \Twig\Error\LoaderError When project template resolution fails. */ public function getCacheKey( string $name ): string { $path = $this->find_project_template( $name ); @@ -154,6 +150,7 @@ public function getCacheKey( string $name ): string { } if ( $this->is_project_reference( $name ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not rendered HTML. throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); } @@ -166,6 +163,7 @@ public function getCacheKey( string $name ): string { * @param string $name Template logical name. * @param int $time Cached template timestamp. * @return bool TRUE when the source is fresh. + * @throws \Twig\Error\LoaderError When project template resolution fails. */ public function isFresh( string $name, int $time ): bool { $path = $this->find_project_template( $name ); @@ -177,6 +175,7 @@ public function isFresh( string $name, int $time ): bool { } if ( $this->is_project_reference( $name ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are not rendered HTML. throw new \Twig\Error\LoaderError( $this->project_loader_error( $name ) ); } diff --git a/package.json b/package.json index 4755bc0..4f72b67 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "husky:commit-msg": "commitlint --edit $1", "husky:pre-commit": "npm run lint", "lint": "npm run lint:php", - "lint:php": "find . \\( -path './.git' -o -path './node_modules' -o -path './vendor' -o -path './whisk/node_modules' \\) -prune -o -type f -name '*.php' -exec php -l {} \\;", + "lint:php": "vendor/bin/phpcs -q && vendor/bin/phpstan analyse --no-progress --memory-limit=1G", + "lint:php:fix": "vendor/bin/phpcbf", "lint-staged": "lint-staged", "prepare": "[ -d '.git' ] && (husky install) || true", "pr:check": "node .github/scripts/pr-validation.cjs", diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..98e78d0 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,65 @@ + + + PHPCS rules for the Emulsify WordPress parent theme runtime. + + includes + 404.php + archive.php + author.php + functions.php + index.php + page.php + search.php + single.php + + vendor/* + node_modules/* + whisk/* + .github/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..aa8dada --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,17 @@ +includes: + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + level: 5 + treatPhpDocTypesAsCertain: false + paths: + - includes/ + scanFiles: + - vendor/php-stubs/acf-pro-stubs/acf-pro-stubs.php + - vendor/php-stubs/wp-cli-stubs/wp-cli-stubs.php + parallel: + maximumNumberOfProcesses: 1 + excludePaths: + - vendor/ + - node_modules/ + - whisk/ diff --git a/single.php b/single.php index b2d0529..a9fbc3f 100644 --- a/single.php +++ b/single.php @@ -11,15 +11,15 @@ exit; } -$context = Timber::context(); -$wp_post = $context['post'] ?? null; -$post_type = $wp_post->post_type ?? get_post_type(); -$templates = array( '@templates/single.twig' ); +$context = Timber::context(); +$wp_post = $context['post'] ?? null; +$post_type_slug = $wp_post->post_type ?? get_post_type(); +$templates = array( '@templates/single.twig' ); -if ( $post_type ) { +if ( $post_type_slug ) { // Child themes can add single-{post_type}.twig to override one post type // without duplicating the generic single.twig fallback. - array_unshift( $templates, '@templates/single-' . $post_type . '.twig' ); + array_unshift( $templates, '@templates/single-' . $post_type_slug . '.twig' ); } if ( $wp_post && post_password_required( $wp_post->ID ) ) { From fe44d106af0311228e5d9b558a875b80afbe733a Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Tue, 7 Jul 2026 22:03:54 -0500 Subject: [PATCH 54/55] ci(release): force wordpress 2.0 stable release --- .github/scripts/release-check.cjs | 12 +++++++++++- docs/release-process.md | 2 +- release.config.js | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 1b1f3d2..5bc774b 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -291,6 +291,10 @@ function runStaticChecks() { lastRelease: { version: '2.0.0' }, nextRelease: { version: '2.0.1' }, }; + const releaseAnalyzerAlphaContext = { + branch: { name: 'main' }, + lastRelease: { version: '1.0.0-alpha.5' }, + }; runStaticCheck('Required files', () => { const requiredFiles = [ @@ -895,12 +899,14 @@ function runStaticChecks() { runStaticCheck('Semantic release configuration', () => { const analyzerOptions = getReleasePluginOptions(releaseConfig, '@semantic-release/commit-analyzer'); const notesOptions = getReleasePluginOptions(releaseConfig, '@semantic-release/release-notes-generator'); + const releaseAnalyzer = releaseConfig.plugins.find((plugin) => plugin && typeof plugin.analyzeCommits === 'function'); const releaseGuard = releaseConfig.plugins.find((plugin) => plugin && typeof plugin.verifyRelease === 'function'); ensure(releaseConfig.expectedStableRelease === '2.0.0', 'release.config.js should declare 2.0.0 as the expected stable release.'); ensure(releaseConfig.tagFormat === '${version}', 'release.config.js should emit non-prefixed semver tags.'); ensure(releaseConfig.repositoryUrl === 'git@github.com:emulsify-ds/emulsify-wordpress.git', 'release.config.js should publish against emulsify-wordpress.'); ensure(Array.isArray(releaseConfig.branches), 'release.config.js branches must be an array.'); ensure(releaseConfig.branches.length === 1 && releaseConfig.branches[0] === 'main', 'release.config.js should publish only from main.'); + ensure(releaseAnalyzer, 'release.config.js should force the first stable release to a major release type.'); ensure(releaseGuard, 'release.config.js should guard the first stable release version.'); ensureBreakingParser('@semantic-release/commit-analyzer', analyzerOptions.parserOpts); ensureBreakingParser('@semantic-release/release-notes-generator', notesOptions.parserOpts); @@ -922,7 +928,11 @@ function runStaticChecks() { } releaseGuard.verifyRelease({}, releaseGuardAcceptContext); releaseGuard.verifyRelease({}, releaseGuardFutureContext); - return 'Semantic release is configured for non-prefixed tags, main-only publishing, and a guarded 2.0.0 stable release.'; + ensure(releaseAnalyzer.analyzeCommits({}, releaseAnalyzerAlphaContext) === 'major', 'release.config.js should force a major release from the latest alpha tag.'); + ensure(releaseAnalyzerAlphaContext.lastRelease.version === '1.0.0', 'release.config.js should normalize the latest alpha tag before semantic-release computes 2.0.0.'); + ensure(releaseAnalyzer.analyzeCommits({}, releaseGuardRejectContext) === 'major', 'release.config.js should force a major release before 2.0.0 exists.'); + ensure(releaseAnalyzer.analyzeCommits({}, releaseGuardFutureContext) === null, 'release.config.js should use normal commit analysis after 2.0.0 exists.'); + return 'Semantic release is configured for non-prefixed tags, main-only publishing, and a forced 2.0.0 stable release.'; }); runStaticCheck('Theme readiness workflow', () => { diff --git a/docs/release-process.md b/docs/release-process.md index 85710bc..6af2b6c 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -4,7 +4,7 @@ This repository uses semantic-release with Conventional Commits. Releases are pr ## Release branch target -The `release-2.x` branch prepares the next stable release as `2.0.0`. The release dry run must compute `2.0.0` before the publish job runs. If it computes another version, the release guard fails so stale 1.x metadata is not published. +The `release-2.x` branch prepares the next stable release as `2.0.0`. Until that stable tag exists, the semantic-release config forces the first `main` publish to use a major release type and then verifies the computed release is exactly `2.0.0`. After the `2.0.0` tag exists, normal Conventional Commit analysis drives later releases. ## Commit messages diff --git a/release.config.js b/release.config.js index 3959e16..ffc4192 100644 --- a/release.config.js +++ b/release.config.js @@ -43,6 +43,10 @@ function isAtLeastVersion(version, minimumVersion) { return true; } +function stableVersionBase(version) { + return String(version || '').split('-')[0]; +} + const expectedStableReleaseGuard = { verifyRelease(pluginConfig, { branch, lastRelease = {}, nextRelease }) { // The 2.x release branch prepares the stable 2.0.0 baseline. After that @@ -58,12 +62,27 @@ const expectedStableReleaseGuard = { }, }; +const expectedStableReleaseAnalyzer = { + analyzeCommits(pluginConfig, { branch, lastRelease = {} }) { + // The release-2.x branch is the first stable release line for the rebuilt + // parent theme. The latest existing tag is an alpha, so normalize it to its + // stable base before semantic-release increments it to 2.0.0. + if (branch.name === 'main' && !isAtLeastVersion(lastRelease.version, expectedStableRelease)) { + lastRelease.version = stableVersionBase(lastRelease.version); + return 'major'; + } + + return null; + }, +}; + module.exports = { expectedStableRelease, tagFormat: '${version}', branches: ['main'], repositoryUrl: 'git@github.com:emulsify-ds/emulsify-wordpress.git', plugins: [ + expectedStableReleaseAnalyzer, ['@semantic-release/commit-analyzer', { parserOpts }], ['@semantic-release/release-notes-generator', { parserOpts }], expectedStableReleaseGuard, From d1368045fa33969b76eeb0c818f8d27cc263ebff Mon Sep 17 00:00:00 2001 From: Callin Mullaney <57088-callinmullaney@users.noreply.drupalcode.org> Date: Fri, 10 Jul 2026 18:55:53 -0500 Subject: [PATCH 55/55] feat(twig): add switch case tags --- .github/scripts/pr-validation.cjs | 1 + .github/scripts/release-check.cjs | 25 ++++ .github/scripts/twig-switch-smoke.php | 79 ++++++++++++ docs/release-process.md | 1 + docs/sister-project-parity.md | 2 +- docs/timber-and-twig-authoring.md | 15 +++ includes/Runtime/Twig.php | 14 +++ includes/Twig/SwitchExtension.php | 27 +++++ includes/Twig/SwitchNode.php | 68 +++++++++++ includes/Twig/SwitchTokenParser.php | 168 ++++++++++++++++++++++++++ package.json | 1 + 11 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/twig-switch-smoke.php create mode 100644 includes/Twig/SwitchExtension.php create mode 100644 includes/Twig/SwitchNode.php create mode 100644 includes/Twig/SwitchTokenParser.php diff --git a/.github/scripts/pr-validation.cjs b/.github/scripts/pr-validation.cjs index d551b63..49ff02b 100644 --- a/.github/scripts/pr-validation.cjs +++ b/.github/scripts/pr-validation.cjs @@ -42,6 +42,7 @@ run('Run editor enhancements smoke test', 'npm', ['run', 'smoke:editor-enhanceme run('Run editor policy smoke test', 'npm', ['run', 'smoke:editor-policy']); run('Run pattern registry smoke test', 'npm', ['run', 'smoke:patterns']); run('Run parent theme filter smoke test', 'npm', ['run', 'smoke:theme-filters']); +run('Run Twig switch smoke test', 'npm', ['run', 'smoke:twig-switch']); run('Run Twig project namespace smoke test', 'npm', ['run', 'smoke:twig-project-namespace']); run('Install Whisk dependencies', 'npm', ['run', 'whisk:install']); run('Run WordPress starter init smoke test', 'npm', ['run', 'smoke:starter-init']); diff --git a/.github/scripts/release-check.cjs b/.github/scripts/release-check.cjs index 5bc774b..ed1ab08 100644 --- a/.github/scripts/release-check.cjs +++ b/.github/scripts/release-check.cjs @@ -353,6 +353,9 @@ function runStaticChecks() { 'includes/Support/AssetManifest.php', 'includes/Support/AttributeBag.php', 'includes/Support/FileDiscovery.php', + 'includes/Twig/SwitchExtension.php', + 'includes/Twig/SwitchNode.php', + 'includes/Twig/SwitchTokenParser.php', 'package.json', 'release.config.js', 'style.css', @@ -377,6 +380,7 @@ function runStaticChecks() { '.github/scripts/wordpress-starter-init-smoke.cjs', '.github/scripts/theme-filters-smoke.php', '.github/scripts/twig-project-namespace-smoke.php', + '.github/scripts/twig-switch-smoke.php', '.github/scripts/wordpress-fixture-smoke.cjs', 'whisk/.cli/init.js', 'whisk/.gitignore', @@ -424,6 +428,7 @@ function runStaticChecks() { ensure(rootPackage.scripts['smoke:patterns'] === 'php .github/scripts/pattern-registry-smoke.php', 'package.json should expose npm run smoke:patterns.'); ensure(rootPackage.scripts['smoke:starter-init'] === 'node .github/scripts/wordpress-starter-init-smoke.cjs', 'package.json should expose npm run smoke:starter-init.'); ensure(rootPackage.scripts['smoke:theme-filters'] === 'php .github/scripts/theme-filters-smoke.php', 'package.json should expose npm run smoke:theme-filters.'); + ensure(rootPackage.scripts['smoke:twig-switch'] === 'php .github/scripts/twig-switch-smoke.php', 'package.json should expose npm run smoke:twig-switch.'); ensure(rootPackage.scripts['smoke:twig-project-namespace'] === 'php .github/scripts/twig-project-namespace-smoke.php', 'package.json should expose npm run smoke:twig-project-namespace.'); ensure(rootPackage.scripts['whisk:install'], 'package.json should expose npm run whisk:install.'); ensure(rootPackage.scripts['whisk:build'] === 'npm --prefix whisk run build', 'package.json should expose npm run whisk:build.'); @@ -491,6 +496,23 @@ function runStaticChecks() { return 'Attribute helper runtime and smoke fixture are wired.'; }); + runStaticCheck('Twig switch tags', () => { + const twig = readFile('includes/Runtime/Twig.php'); + const extension = readFile('includes/Twig/SwitchExtension.php'); + const parser = readFile('includes/Twig/SwitchTokenParser.php'); + const node = readFile('includes/Twig/SwitchNode.php'); + const smoke = readFile('.github/scripts/twig-switch-smoke.php'); + + ensure(twig.includes("add_filter( 'timber/twig', array( $this, 'extensions' ) )"), 'Timber integration should register custom Twig extensions.'); + ensure(twig.includes('new SwitchExtension()'), 'Timber integration should add the switch extension.'); + ensure(extension.includes('new SwitchTokenParser()'), 'Switch extension should expose the switch token parser.'); + ensure(parser.includes("return 'switch';") && parser.includes("case 'case':") && parser.includes("case 'default':") && parser.includes("case 'endswitch':"), 'Switch parser should support switch, case, default, and endswitch tags.'); + ensure(node.includes("->write( 'switch (' )") && node.includes('->write( "break;\\n" )'), 'Switch node should compile native switch branches with automatic breaks.'); + ensure(smoke.includes("{% case 'alpha' or 'beta' %}") && smoke.includes('automatic case break'), 'Twig switch smoke should cover multiple values and no fall-through.'); + ensure(docs.twig.includes('## Switch statements') && docs.twig.includes('{% switch variant %}'), 'Twig authoring docs should document switch statements.'); + return 'Twig switch tags are registered, documented, and covered by a runtime smoke test.'; + }); + runStaticCheck('Bootstrap runtime autoloading', () => { const bootstrap = readFile('includes/Bootstrap.php'); const smoke = readFile('.github/scripts/bootstrap-loader-smoke.php'); @@ -975,6 +997,7 @@ function runStaticChecks() { ensure(prValidationScript.includes('smoke:patterns'), 'PR validation should run the pattern registry smoke test.'); ensure(prValidationScript.includes('smoke:starter-init'), 'PR validation should run the WordPress starter init smoke test.'); ensure(prValidationScript.includes('smoke:theme-filters'), 'PR validation should run the parent theme filter smoke test.'); + ensure(prValidationScript.includes('smoke:twig-switch'), 'PR validation should run the Twig switch smoke test.'); ensure(prValidationScript.includes('smoke:twig-project-namespace'), 'PR validation should run the Twig project namespace smoke test.'); ensure(prValidationScript.includes('whisk:install'), 'PR validation should install Whisk dependencies.'); ensure(prValidationScript.indexOf('whisk:install') < prValidationScript.indexOf('smoke:starter-init'), 'PR validation should install Whisk dependencies before checking the starter npm test script.'); @@ -1043,6 +1066,8 @@ function runStaticChecks() { ensure(docs.parity.includes('Built global assets are emitted under `dist/global`'), 'Sister-project parity doc should document global build output.'); ensure(docs.parity.includes('Built component assets and block metadata are emitted under `dist/components`'), 'Sister-project parity doc should document component build output.'); ensure(docs.parity.includes('Frontend rendering uses Timber'), 'Sister-project parity doc should document Timber as a WordPress difference.'); + ensure(docs.twig.includes('## Switch statements') && docs.twig.includes("{% case 'primary' or 'secondary' %}"), 'Twig authoring docs should document switch tags and multiple case values.'); + ensure(docs.release.includes('Twig switch tag smoke test'), 'Release documentation should list Twig switch smoke coverage.'); ensure(docs.parity.includes('WordPress theme identity lives in `style.css` headers'), 'Sister-project parity doc should document WordPress theme headers.'); ensure(docs.parity.includes('`theme.json` is the WordPress site and editor configuration surface'), 'Sister-project parity doc should document theme.json.'); ensure(docs.parity.includes('Whisk does not include a child `theme.json` by default'), 'Sister-project parity doc should explain why Whisk does not ship an empty child theme.json.'); diff --git a/.github/scripts/twig-switch-smoke.php b/.github/scripts/twig-switch-smoke.php new file mode 100644 index 0000000..ecd09b3 --- /dev/null +++ b/.github/scripts/twig-switch-smoke.php @@ -0,0 +1,79 @@ + <<<'TWIG' +{% switch value %}{% case 'alpha' or 'beta' %}matched{% default %}default{% endswitch %} +TWIG, + 'default' => <<<'TWIG' +{% switch value %}{% case 'alpha' %}matched{% default %}default{% endswitch %} +TWIG, + 'break' => <<<'TWIG' +{% switch value %}{% case 'beta' %}first{% case 'beta' %}second{% default %}default{% endswitch %} +TWIG, + 'expression' => <<<'TWIG' +{% switch value %}{% case ('alpha' ~ suffix) or secondary %}expression{% default %}default{% endswitch %} +TWIG, + 'loose' => <<<'TWIG' +{% switch value %}{% case '2' %}numeric{% default %}default{% endswitch %} +TWIG, + ) + ), + array( + 'autoescape' => false, + 'cache' => false, + 'use_yield' => true, + ) +); + +$environment = ( new TwigIntegration() )->extensions( $environment ); + +emulsify_twig_switch_assert_same( 'multiple case values', 'matched', trim( $environment->render( 'multiple', array( 'value' => 'beta' ) ) ) ); +emulsify_twig_switch_assert_same( 'default branch', 'default', trim( $environment->render( 'default', array( 'value' => 'gamma' ) ) ) ); +emulsify_twig_switch_assert_same( 'automatic case break', 'first', trim( $environment->render( 'break', array( 'value' => 'beta' ) ) ) ); +emulsify_twig_switch_assert_same( + 'case expressions', + 'expression', + trim( + $environment->render( + 'expression', + array( + 'secondary' => 'beta', + 'suffix' => '-card', + 'value' => 'alpha-card', + ) + ) + ) +); +emulsify_twig_switch_assert_same( 'PHP-style scalar matching', 'numeric', trim( $environment->render( 'loose', array( 'value' => 2 ) ) ) ); + +echo "Twig switch smoke checks passed.\n"; diff --git a/docs/release-process.md b/docs/release-process.md index 6af2b6c..754689d 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -31,6 +31,7 @@ npm run publish-test -- --no-ci - PHP linting. - ACF Local JSON smoke test. - Twig attribute helper smoke test. +- Twig switch tag smoke test. - Child theme generator smoke test. - Component locator smoke test. - Parent theme filter smoke test. diff --git a/docs/sister-project-parity.md b/docs/sister-project-parity.md index 2934c82..846dfd0 100644 --- a/docs/sister-project-parity.md +++ b/docs/sister-project-parity.md @@ -37,7 +37,7 @@ Emulsify WordPress is not a Drupal runtime port. It keeps parity at the Emulsify ### Timber runtime -Frontend rendering uses Timber. The parent theme owns Timber bootstrapping, global context, Twig namespace registration, route fallbacks, and Twig helper functions. Child themes supply project Twig templates and component source. +Frontend rendering uses Timber. The parent theme owns Timber bootstrapping, global context, Twig namespace registration, route fallbacks, helper functions, and Emulsify-compatible switch tags. Child themes supply project Twig templates and component source. For new project component includes, prefer the generated child theme machine name from `project.emulsify.json`, using the general form `{% include "project_machine_name:component_name" %}`. The legacy `@components/component-name/component-name.twig` namespace remains supported for compatible component libraries, existing projects, shared templates, and migration work. diff --git a/docs/timber-and-twig-authoring.md b/docs/timber-and-twig-authoring.md index 3cee1f8..c89d89b 100644 --- a/docs/timber-and-twig-authoring.md +++ b/docs/timber-and-twig-authoring.md @@ -115,6 +115,21 @@ The parent theme registers Core-style helpers for class and attribute handling: The helpers return an attribute bag that serializes safely in Twig string contexts. +## Switch statements + +The parent theme registers the Emulsify-compatible `switch`, `case`, `default`, and `endswitch` tags used by Emulsify Core in Storybook and Emulsify Tools in Drupal. Use `or` to match more than one value in a case: + +```twig +{% switch variant %} + {% case 'primary' or 'secondary' %} + {{ label }} + {% default %} + {{ label }} +{% endswitch %} +``` + +The `default` branch is optional and, when present, follows the case branches. Each matching case stops automatically, so switch blocks do not fall through to later cases. Matching follows PHP switch semantics in Timber and the equivalent scalar behavior in Emulsify Core's Twig.js runtime. + ## Global context The parent theme adds common values such as `site`, `theme`, `menu`, `post`, `wp`, and `body_class` to the Timber context. Child themes and project plugins can add values with: diff --git a/includes/Runtime/Twig.php b/includes/Runtime/Twig.php index 4c7c30c..8fe3e40 100644 --- a/includes/Runtime/Twig.php +++ b/includes/Runtime/Twig.php @@ -11,6 +11,7 @@ use Emulsify\Theme\Support\FileDiscovery; use Emulsify\Theme\Support\ProjectConfig; use Emulsify\Theme\Twig\ProjectComponentLoader; +use Emulsify\Theme\Twig\SwitchExtension; /** * Twig integration for Timber. @@ -25,6 +26,7 @@ final class Twig { public function register(): void { add_filter( 'timber/loader/loader', array( $this, 'loader_paths' ) ); add_filter( 'timber/twig/functions', array( $this, 'functions' ) ); + add_filter( 'timber/twig', array( $this, 'extensions' ) ); } /** @@ -125,6 +127,18 @@ public function functions( array $functions ): array { return $functions; } + /** + * Registers custom Twig extensions. + * + * @param \Twig\Environment $environment Timber Twig environment. + * @return \Twig\Environment Updated Twig environment. + */ + public function extensions( \Twig\Environment $environment ): \Twig\Environment { + $environment->addExtension( new SwitchExtension() ); + + return $environment; + } + /** * Builds a BEM class attribute. * diff --git a/includes/Twig/SwitchExtension.php b/includes/Twig/SwitchExtension.php new file mode 100644 index 0000000..be64825 --- /dev/null +++ b/includes/Twig/SwitchExtension.php @@ -0,0 +1,27 @@ +addDebugInfo( $this ) + ->write( 'switch (' ) + ->subcompile( $this->getNode( 'value' ) ) + ->raw( ") {\n" ) + ->indent(); + + foreach ( $this->getNode( 'cases' ) as $case ) { + if ( ! $case->hasNode( 'body' ) ) { + continue; + } + + foreach ( $case->getNode( 'values' ) as $value ) { + $compiler + ->write( 'case ' ) + ->subcompile( $value ) + ->raw( ":\n" ); + } + + $compiler + ->write( "{\n" ) + ->indent() + ->subcompile( $case->getNode( 'body' ) ) + ->write( "break;\n" ) + ->outdent() + ->write( "}\n" ); + } + + if ( $this->hasNode( 'default' ) ) { + $compiler + ->write( "default:\n" ) + ->write( "{\n" ) + ->indent() + ->subcompile( $this->getNode( 'default' ) ) + ->outdent() + ->write( "}\n" ); + } + + $compiler + ->outdent() + ->write( "}\n" ); + } +} diff --git a/includes/Twig/SwitchTokenParser.php b/includes/Twig/SwitchTokenParser.php new file mode 100644 index 0000000..6e20ce3 --- /dev/null +++ b/includes/Twig/SwitchTokenParser.php @@ -0,0 +1,168 @@ +getLine(); + $parser = $this->parser; + $stream = $parser->getStream(); + $nodes = array( + 'value' => $this->parse_expression( $parser ), + ); + + $stream->expect( Token::BLOCK_END_TYPE ); + + while ( $stream->getCurrent()->test( Token::TEXT_TYPE ) && '' === trim( $stream->getCurrent()->getValue() ) ) { + $stream->next(); + } + + $stream->expect( Token::BLOCK_START_TYPE ); + + $cases = array(); + $end = false; + + while ( ! $end ) { + $next = $stream->next(); + + switch ( $next->getValue() ) { + case 'case': + $values = array(); + + while ( true ) { + $values[] = $this->parse_expression( $parser, self::CASE_VALUE_PRECEDENCE ); + + if ( $stream->test( Token::OPERATOR_TYPE, 'or' ) ) { + $stream->next(); + continue; + } + + break; + } + + $stream->expect( Token::BLOCK_END_TYPE ); + $cases[] = $this->node_collection( + array( + 'values' => $this->node_collection( $values ), + 'body' => $parser->subparse( array( $this, 'decide_if_fork' ) ), + ) + ); + break; + + case 'default': + $stream->expect( Token::BLOCK_END_TYPE ); + $nodes['default'] = $parser->subparse( array( $this, 'decide_if_end' ) ); + break; + + case 'endswitch': + $end = true; + break; + + default: + $message = sprintf( + 'Unexpected tag "%s". Twig was looking for "case", "default", or "endswitch" to close the "switch" block started on line %d.', + $next->getValue(), + $lineno + ); + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Twig syntax errors are not rendered HTML. + throw new SyntaxError( $message, $lineno ); + } + } + + $nodes['cases'] = $this->node_collection( $cases ); + $stream->expect( Token::BLOCK_END_TYPE ); + + return new SwitchNode( $nodes, array(), $lineno ); + } + + /** + * Checks whether the parser reached another switch branch. + * + * @param Token $token Current Twig token. + * @return bool TRUE at another switch branch. + */ + public function decide_if_fork( Token $token ): bool { + return $token->test( array( 'case', 'default', 'endswitch' ) ); + } + + /** + * Checks whether the parser reached the end of a switch block. + * + * @param Token $token Current Twig token. + * @return bool TRUE at the closing switch tag. + */ + public function decide_if_end( Token $token ): bool { + return $token->test( array( 'endswitch' ) ); + } + + /** + * Parses an expression across the Twig versions allowed by Timber 2.x. + * + * @param Parser $parser Active Twig parser. + * @param int $precedence Expression precedence floor. + * @return Node Parsed expression node. + */ + private function parse_expression( Parser $parser, int $precedence = 0 ): Node { + if ( version_compare( Environment::VERSION, '3.21.0', '>=' ) ) { + return $parser->parseExpression( $precedence ); + } + + $expression_parser = $parser->getExpressionParser(); + + return $expression_parser->parseExpression( $precedence ); + } + + /** + * Builds a node collection across the Twig versions allowed by Timber 2.x. + * + * @param array $nodes Child nodes. + * @return Node Twig node collection. + */ + private function node_collection( array $nodes ): Node { + if ( class_exists( Nodes::class ) ) { + return new Nodes( $nodes ); + } + + return new Node( $nodes ); + } +} diff --git a/package.json b/package.json index 4f72b67..ba3a1c0 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "smoke:patterns": "php .github/scripts/pattern-registry-smoke.php", "smoke:starter-init": "node .github/scripts/wordpress-starter-init-smoke.cjs", "smoke:theme-filters": "php .github/scripts/theme-filters-smoke.php", + "smoke:twig-switch": "php .github/scripts/twig-switch-smoke.php", "smoke:twig-project-namespace": "php .github/scripts/twig-project-namespace-smoke.php", "whisk:build": "npm --prefix whisk run build", "whisk:install": "npm --prefix whisk install --package-lock=false --ignore-scripts",