diff --git a/.eslintignore b/.eslintignore index 7df5c928c1..77fb2df91b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,5 @@ packages/volto packages/volto-guillotina !.* dist +packages/registry/lib +packages/registry/docs diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index a52a7502a3..82480849c7 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -521,6 +521,9 @@ jobs: - name: Install yalc run: npm -g install yalc + - name: Build dependencies + run: make build-deps + - name: Install a yalc'ed version of the current Volto in the project - publish run: | yalc publish packages/types diff --git a/.github/workflows/docs-rtd-pr-preview.yml b/.github/workflows/docs-rtd-pr-preview.yml index 83d7e72494..a123d8ef75 100644 --- a/.github/workflows/docs-rtd-pr-preview.yml +++ b/.github/workflows/docs-rtd-pr-preview.yml @@ -10,6 +10,7 @@ on: - "docs/source/**" - .readthedocs.yaml - requirements-docs.txt + - "packages/registry/docs/**" permissions: pull-requests: write diff --git a/.github/workflows/readme-link-check.yml b/.github/workflows/readme-link-check.yml index 7f53b65824..42c96e1455 100644 --- a/.github/workflows/readme-link-check.yml +++ b/.github/workflows/readme-link-check.yml @@ -20,4 +20,4 @@ jobs: - name: Check links in README.md with awesome_bot run: | gem install awesome_bot - awesome_bot --request-delay 1 --allow-dupe --white-list http://localhost:8080/Plone,http://localhost:3000,https://github.com/kitconcept/volto-blocks-grid.git,https://my-server-DNS-name.tld/api --files PACKAGES.md,README.md,packages/blocks/README.md,packages/client/README.md,packages/components/README.md,packages/generator-volto/README.md,packages/registry/README.md,packages/scripts/README.md,packages/tsconfig/README.md,packages/types/README.md,packages/volto-slate/README.md,apps/nextjs/README.md,apps/remix/README.md,apps/vite-ssr/README.md + awesome_bot --request-delay 1 --allow-dupe --white-list http://localhost:8080/Plone,http://localhost:8080,http://localhost:3000,https://github.com/kitconcept/volto-blocks-grid.git,https://my-server-DNS-name.tld/api --files PACKAGES.md,README.md,packages/blocks/README.md,packages/client/README.md,packages/components/README.md,packages/generator-volto/README.md,packages/registry/README.md,packages/scripts/README.md,packages/tsconfig/README.md,packages/types/README.md,packages/volto-slate/README.md,apps/nextjs/README.md,apps/remix/README.md,apps/vite-ssr/README.md diff --git a/.prettierignore b/.prettierignore index cf39cfcd94..1749ca9b36 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,6 +5,7 @@ styles/rules/* node_modules packages/volto/types/* +packages/registry/docs/* storybook-static apps/vite-ssr/src/routeTree.gen.ts apps/vite/src/routeTree.gen.ts diff --git a/apps/remix/tsconfig.json b/apps/remix/tsconfig.json index 9d87dd378f..42f049fa4a 100644 --- a/apps/remix/tsconfig.json +++ b/apps/remix/tsconfig.json @@ -25,8 +25,6 @@ "paths": { "~/*": ["./app/*"] }, - - // Vite takes care of building everything, not tsc. "noEmit": true } } diff --git a/apps/remix/vite.config.ts b/apps/remix/vite.config.ts index 1a43ce37db..03bfa30a99 100644 --- a/apps/remix/vite.config.ts +++ b/apps/remix/vite.config.ts @@ -1,6 +1,7 @@ import { vitePlugin as remix } from '@remix-run/dev'; import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; +import { PloneRegistryVitePlugin } from '@plone/registry/vite-plugin'; export default defineConfig({ plugins: [ @@ -12,6 +13,7 @@ export default defineConfig({ }, }), tsconfigPaths(), + PloneRegistryVitePlugin(), ], server: { port: 3000, diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 60de994f58..b9b67fcf4d 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -522,6 +522,63 @@ The `react/jsx-key` rule has been enabled in ESlint for catching missing `key` i You might catch some violations in your project or add-on code after running ESlint. Adding the missing `key` property whenever the violation is reported will fix it. +### `@plone/registry` moved to ESM + +The `@plone/registry` package has been moved to ESM. +The add-on registry scripts have also been refactored to TypeScript. +For maximum compatibility with CommonJS builds, the default exports have been moved to named exports. +The modules affected are now built, and the import paths have changed, too. +These changes force some import path changes that you should patch in your Plone project or add-on boilerplates. + +```{note} +As always, when something changes in the boilerplate, you may regenerate one from Cookieplone and move your code into it, instead of fiddling with it. +``` + +For example, in your project's {file}`.eslintrc.js`: + +```diff + const fs = require('fs'); + const projectRootPath = __dirname; +-const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); ++const { AddonRegistry } = require('@plone/registry/addon-registry'); + + let voltoPath = './node_modules/@plone/volto'; + +@@ -17,15 +17,15 @@ if (configFile) { + voltoPath = `./${jsConfig.baseUrl}/${pathsConfig['@plone/volto'][0]}`; + } + +-const reg = new AddonConfigurationRegistry(__dirname); ++const { registry } = AddonRegistry.init(__dirname); + + // Extends ESlint configuration for adding the aliases to `src` directories in Volto addons +-const addonAliases = Object.keys(reg.packages).map((o) => [ ++const addonAliases = Object.keys(registry.packages).map((o) => [ + o, +- reg.packages[o].modulePath, ++ registry.packages[o].modulePath, + ]); + +-const addonExtenders = reg.getEslintExtenders().map((m) => require(m)); ++const addonExtenders = registry.getEslintExtenders().map((m) => require(m)); +``` + +Also in the Storybook configuration {file}`.storybook/main.js`. + +```diff +- const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); ++ const { AddonRegistry } = require('@plone/registry/addon-registry'); + +- const registry = new AddonConfigurationRegistry(projectRootPath); ++ const { registry } = AddonRegistry.init(projectRootPath); +``` + +```{versionadded} Volto 18.0.0-alpha.47 +``` + +```{versionadded} @plone/registry 3.0.0-alpha.0 +``` + ### Deprecation notices for Volto 18 #### `@plone/generator-volto` diff --git a/package.json b/package.json index 0b7d97e4de..603b737d9a 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,9 @@ "overrides": { "@pmmmwh/react-refresh-webpack-plugin": "0.5.11", "react-refresh": "0.14.0" + }, + "patchedDependencies": { + "jest-resolve@26.6.2": "patches/jest-resolve@26.6.2.patch" } } } diff --git a/packages/generator-volto/generators/app/templates/.eslintrc.js b/packages/generator-volto/generators/app/templates/.eslintrc.js index 6148e2e62f..a5aaf045b1 100644 --- a/packages/generator-volto/generators/app/templates/.eslintrc.js +++ b/packages/generator-volto/generators/app/templates/.eslintrc.js @@ -1,6 +1,6 @@ const fs = require('fs'); const projectRootPath = __dirname; -const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); +const { AddonRegistry } = require('@plone/registry/addon-registry'); let voltoPath = './node_modules/@plone/volto'; @@ -17,15 +17,15 @@ if (configFile) { voltoPath = `./${jsConfig.baseUrl}/${pathsConfig['@plone/volto'][0]}`; } -const reg = new AddonConfigurationRegistry(__dirname); +const { registry } = AddonRegistry.init(__dirname); // Extends ESlint configuration for adding the aliases to `src` directories in Volto addons -const addonAliases = Object.keys(reg.packages).map((o) => [ +const addonAliases = Object.keys(registry.packages).map((o) => [ o, - reg.packages[o].modulePath, + registry.packages[o].modulePath, ]); -const addonExtenders = reg.getEslintExtenders().map((m) => require(m)); +const addonExtenders = registry.getEslintExtenders().map((m) => require(m)); const defaultConfig = { extends: `${voltoPath}/.eslintrc`, diff --git a/packages/generator-volto/generators/app/templates/.storybook/main.js b/packages/generator-volto/generators/app/templates/.storybook/main.js index aa094cb6d7..da6c799e56 100644 --- a/packages/generator-volto/generators/app/templates/.storybook/main.js +++ b/packages/generator-volto/generators/app/templates/.storybook/main.js @@ -104,9 +104,9 @@ module.exports = { [], defaultRazzleOptions, ); - const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); + const { AddonRegistry } = require('@plone/registry/addon-registry'); - const registry = new AddonConfigurationRegistry(projectRootPath); + const { registry } = AddonRegistry.init(projectRootPath); config = lessPlugin({ registry }).modifyWebpackConfig({ env: { target: 'web', dev: 'dev' }, diff --git a/packages/generator-volto/news/6399.breaking b/packages/generator-volto/news/6399.breaking new file mode 100644 index 0000000000..2b0695d18f --- /dev/null +++ b/packages/generator-volto/news/6399.breaking @@ -0,0 +1,2 @@ +Breaking changes in `.eslintrc.js` and `.storybook/main.js` because of #6399. @sneridagh +Please see the [Upgrade Guide](https://6.docs.plone.org/volto/upgrade-guide/index.html). diff --git a/packages/registry/.eslintrc.js b/packages/registry/.eslintrc.cjs similarity index 88% rename from packages/registry/.eslintrc.js rename to packages/registry/.eslintrc.cjs index b60637a973..8614b4cd25 100644 --- a/packages/registry/.eslintrc.js +++ b/packages/registry/.eslintrc.cjs @@ -16,6 +16,8 @@ module.exports = { // Base config extends: ['eslint:recommended'], + ignorePatterns: ['docs/_static/searchtools.js'], + overrides: [ // React { @@ -61,7 +63,11 @@ module.exports = { // Node { - files: ['.eslintrc.js', 'src/*.js'], + files: [ + '.eslintrc.cjs', + 'src/addon-registry/**/*.{js,ts}', + '__tests__/**/*.{js,ts}', + ], env: { node: true, es6: true, diff --git a/packages/registry/.gitignore b/packages/registry/.gitignore index d32327c9cd..2c29928dd0 100644 --- a/packages/registry/.gitignore +++ b/packages/registry/.gitignore @@ -1,6 +1,7 @@ .parcel-cache/ dist +/bin +/lib +/include -# yarn 3 -.pnp.* -.yarn/* +docs/_build/ diff --git a/packages/registry/.readthedocs.yaml b/packages/registry/.readthedocs.yaml new file mode 100644 index 0000000000..b1f7970727 --- /dev/null +++ b/packages/registry/.readthedocs.yaml @@ -0,0 +1,34 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + # You can also specify other tool versions: + # nodejs: "19" + # rust: "1.64" + # golang: "1.19" + commands: + - cd ./packages/registry && make docs-rtd-registry + +# Build documentation in the "docs/" directory with Sphinx +#sphinx: +# configuration: packages/registry/docs/conf.py + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +#python: +# install: +# - requirements: requirements-docs.txt diff --git a/packages/registry/Makefile b/packages/registry/Makefile new file mode 100644 index 0000000000..d0e9c89f6d --- /dev/null +++ b/packages/registry/Makefile @@ -0,0 +1,90 @@ +### Defensive settings for make: +# https://tech.davis-hansson.com/p/make/ +SHELL:=bash +.ONESHELL: +.SHELLFLAGS:=-eu -o pipefail -c +.SILENT: +.DELETE_ON_ERROR: +MAKEFLAGS+=--warn-undefined-variables +MAKEFLAGS+=--no-builtin-rules + +# Sphinx variables +# You can set these variables from the command line. +SPHINXOPTS ?= +VALEOPTS ?= +# Internal variables. +SPHINXBUILD = "$(realpath bin/sphinx-build)" +SPHINXAUTOBUILD = "$(realpath bin/sphinx-autobuild)" +DOCS_DIR = ./docs/ +BUILDDIR = ./_build/ +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . +VALEFILES := $(shell find $(DOCS_DIR) -type f -name "*.md" -print) + +# We like colors +# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects +RED=`tput setaf 1` +GREEN=`tput setaf 2` +RESET=`tput sgr0` +YELLOW=`tput setaf 3` + +## Docs + +bin/python: ## Create a Python virtual environment with the latest pip, and install documentation requirements + python3 -m venv . || virtualenv --clear --python=python3 . + bin/python -m pip install --upgrade pip + @echo "Python environment created." + bin/pip install -r ../../requirements-docs.txt + @echo "Requirements installed." + +.PHONY: docs-clean +docs-clean: ## Clean current and legacy docs build directories, and Python virtual environment + rm -rf bin include lib + rm -rf docs/_build + cd $(DOCS_DIR) && rm -rf $(BUILDDIR)/ + +.PHONY: docs-html +docs-html: bin/python ## Build html + cd $(DOCS_DIR) && $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: docs-livehtml +docs-livehtml: bin/python ## Rebuild Sphinx documentation on changes, with live-reload in the browser + cd "$(DOCS_DIR)" && ${SPHINXAUTOBUILD} \ + --ignore "*.swp" \ + -b html . "$(BUILDDIR)/html" $(SPHINXOPTS) + +.PHONY: docs-linkcheck +docs-linkcheck: bin/python ## Run linkcheck + cd $(DOCS_DIR) && $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/ ." + +.PHONY: docs-linkcheckbroken +docs-linkcheckbroken: bin/python ## Run linkcheck and show only broken links + cd $(DOCS_DIR) && $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | GREP_COLORS='0;31' grep -wi "broken\|redirect" --color=always | GREP_COLORS='0;31' grep -vi "https://github.com/plone/volto/issues/" --color=always && if test $$? -eq 0; then exit 1; fi || test $$? -ne 0 + +.PHONY: docs-vale +docs-vale: bin/python ## Install (once) and run Vale style, grammar, and spell checks + bin/vale sync + bin/vale --no-wrap $(VALEOPTS) $(VALEFILES) + @echo + @echo "Vale is finished; look for any errors in the above output." + +.PHONY: docs-rtd-pr-preview +docs-rtd-pr-preview: ## Build previews of pull requests that have documentation changes on Read the Docs via CI + pip install -r requirements-docs.txt + cd $(DOCS_DIR) && sphinx-build -b html $(ALLSPHINXOPTS) ${READTHEDOCS_OUTPUT}/html/ + +.PHONY: docs-rtd-registry +docs-rtd-registry: ## Build Plone Registry docs on RTD + pip install -r ../../requirements-docs.txt && cd $(DOCS_DIR) && sphinx-build -b html $(ALLSPHINXOPTS) ${READTHEDOCS_OUTPUT}/html/ + +## Build + +.PHONY: rename-to-cjs +rename-to-cjs: ## Rename the built files js -> cjs + mv dist/cjs/addon-registry.js dist/cjs/addon-registry.cjs + mv dist/cjs/create-addons-loader.js dist/cjs/create-addons-loader.cjs + mv dist/cjs/create-theme-loader.js dist/cjs/create-theme-loader.cjs diff --git a/packages/registry/README.md b/packages/registry/README.md index 552133573c..0c4ae8865b 100644 --- a/packages/registry/README.md +++ b/packages/registry/README.md @@ -10,198 +10,10 @@ That means you have to build something that has very specific requirements, beha Sometimes you need to build something generic that is pluggable and extensible. In the JavaScript and TypeScript ecosystem, this is often quite complex, and the existing frameworks do not provide the means to do this. -## Add-on registry +`@plone/registry` provides tools to facilitate pluggability for your app. -An add-on registry is a facility that allows an app, which was built on an existing framework, to itself be extensible and pluggable. +## Documentation -The add-on registry is a store where you can register a number of add-ons that your app consumes. +You can find the detailed documentation of this package by visiting the following link. -Add-on packages are just CommonJS packages. -The only requirement is that they point the `main` key of their `package.json` to a module that exports as a default function, which acts as a configuration loader. - -An add-on can be published in an npm registry, just as any other package. -However, add-ons are meant to not be transpiled. -They should be released as source packages. - -## Register an add-on - -You should declare your add-on in your project. -This is done in your app's `package.json`'s `addons` key: - -```json -{ - "name": "my-app-project", - "addons": [ - "acme-volto-foo-addon", - "@plone/some-addon", - "collective-another-volto-addon" - ] -} -``` - -The `addons` key ensures the add-on's main default export function is executed, being passed the configuration registry. -In that function, the add-on can customize the registry. -The function needs to return the `config` object (the configuration registry), so that it's passed further along to the other add-ons. - -The add-ons are registered in the order they are found in the `addons` key. -The last add-on takes precedence over the others. -This means that if you configure something in `acme-volto-foo-addon`, then the same thing later in `collective-another-volto-addon`, the latter configured thing will win and its configuration will be applied. - -The default export of any add-on main `index.js` file should be a function with the signature `config => config`. -That is, it should take the configuration registry object and return it, possibly mutated or changed. - -## Configuration registry - -The configuration registry supplements the add-on registry. - -It is a facility that stores app configuration to be shared in the app. - -Let's say that your app is the user interface of a content management system (CMS). -This CMS uses blocks as its main fundamental unit of content. -The pages that the CMS builds are made up of these blocks. -The CMS has some basic available blocks, but it's a requirement that integrators are able to register more blocks in a pluggable way. - -This app will use the add-on registry to extend the basic CMS capabilities, so an external add-on can supplement their own add-ons to the basic CMS ones. - -Let's assume we've defined a key in the registry `config.blocks.blocksConfig`, and defined a way to register the available blocks in the CMS as the keys in that object in the configuration registry: - -```js - config.blocks.blocksConfig.faq_viewer = { - id: 'faq_viewer', - title: 'FAQ Viewer', - edit: FAQBlockEdit, - view: FAQBlockView, - icon: chartIcon, - group: 'common', - restricted: false, - mostUsed: true, - sidebarTab: 1, - }; -``` - -The configuration registry will have other keys already set by default, which will compose the initial set of basic blocks used by the CMS. -Then the CMS will properly populate the available blocks in the user interface. - -The add-on is meant to extend the initial configuration. -From the default export function of our add-on, we should provide the configuration of the new block: - -```ts -export default function applyConfig(config: ConfigData) { - config.blocks.blocksConfig.faq_viewer = { - id: 'faq_viewer', - title: 'FAQ Viewer', - edit: FAQBlockEdit, - view: FAQBlockView, - icon: chartIcon, - group: 'common', - restricted: false, - mostUsed: true, - sidebarTab: 1, - }; - - return config; -} -``` - -Once the app starts, the add-on registry will execute, in order, all the registered add-ons' default export functions, configuring the new block. -The add-on will then become available to the CMS when it asks the configuration registry for it. - -## Accessing the configuration registry - -The configuration registry can be accessed by: - -```ts -import config from '@plone/registry' - -const blocksConfig = config.blocks.blocksConfig -``` - -## Component registry - -The configuration registry also stores a components registry in itself. -The components registry is a mapping of name to component. -You can look up a name, and receive a component that you can reference in your code. -This provides an alternative, and more convenient, way to customize components. -You can override programmatically such registrations from your add-on or projects because it's stored in the configuration registry. -You can customize a component without using shadowing at all, if the code that uses the component retrieves from the component registry, rather then import it directly. -You can even have modifiers to the component registrations through dependencies. -Thus you can adapt the call, given an array of such dependencies. - -## Register components by name using `config.registerComponent` - -You can register components by name, typically from an add-on or project configuration: - -```js -import MyToolbarComponent from './MyToolbarComponent' - -config.registerComponent({ - name: 'Toolbar', - component: MyToolbarComponent, -}); -``` - -## Retrieve a component from the component registry - -You can programmatically retrieve a component from the registry using `config.getComponent`: - -```js -const Toolbar = config.getComponent('Toolbar').component -``` - -Or you can retrieve a component by using the convenience component `Component`, if you want to use it in JSX code directly. - -```jsx -import Component from '@plone/volto/components/theme/Component/Component'; - - -``` - -Note that you can pass `props` down to the retrieved component. - -## Adapt the component using the `dependencies` array - -You can register components, then retrieve them, given a list of modifiers using `dependencies`. - -```js -import MyTeaserNewsItemComponent from './MyTeaserNewsItemComponent' - -config.registerComponent({ - name: 'Teaser', - component: MyTeaserNewsItemComponent, - dependencies: 'News Item', - }); -``` - -And then retrieve the component: - -```js -config.getComponent({ - name: 'Teaser', - dependencies: ['News Item'], - }).component -``` - -You can have both, either with or without dependencies: - -```js -import MyTeaserDefaultComponent from './MyTeaserDefaultComponent' -import MyTeaserNewsItemComponent from './MyTeaserNewsItemComponent' - -config.registerComponent({ - name: 'Teaser', - component: MyTeaserDefaultComponent, - }); - -config.registerComponent({ - name: 'Teaser', - component: MyTeaserNewsItemComponent, - dependencies: 'News Item', - }); -``` - -Then retrieve them both, depending on the use case. -In the example, given a content type value coming from the `content` prop, you would retrieve them as shown: - -```jsx - -``` +https://plone-registry.readthedocs.io diff --git a/packages/registry/__tests__/addon-registry-app.test.js b/packages/registry/__tests__/addon-registry-app.test.js new file mode 100644 index 0000000000..7c3d7d7e04 --- /dev/null +++ b/packages/registry/__tests__/addon-registry-app.test.js @@ -0,0 +1,18 @@ +import path from 'path'; +import { AddonRegistry } from '../src/addon-registry/addon-registry'; +import { describe, it, expect } from 'vitest'; + +describe('AddonRegistry - get()', () => { + it('Basic information', () => { + const base = path.join(import.meta.dirname, 'fixtures', 'test-app'); + const { addons, shadowAliases, theme } = AddonRegistry.init(base); + expect(addons).toStrictEqual(['@plone/slots', 'my-addon']); + expect(shadowAliases).toStrictEqual([ + { + find: '@plone/slots/components/Logo/Logo.svg', + replacement: `${base}/node_modules/my-addon/customizations/@plone/slots/components/Logo/Logo.svg`, + }, + ]); + expect(theme).toStrictEqual(undefined); + }); +}); diff --git a/packages/registry/__tests__/addon-registry.test.js b/packages/registry/__tests__/addon-registry.test.js new file mode 100644 index 0000000000..cc21df88fa --- /dev/null +++ b/packages/registry/__tests__/addon-registry.test.js @@ -0,0 +1,366 @@ +import path from 'path'; +import { + AddonRegistry, + buildDependencyGraph, + getAddonsLoaderChain, +} from '../src/addon-registry/addon-registry'; +import { vi, describe, it, expect, test, beforeEach, afterEach } from 'vitest'; + +vi.mock( + 'fixtures/test-volto-project/node_modules/@plone/volto/package.json', + () => ({ + // TODO: mock the packages folder inside the mocked @plone/volto to work with resolves + coreAddons: {}, + }), + { virtual: true }, +); + +describe('AddonRegistry - get()', () => { + it('Basic information', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { addons, shadowAliases, theme } = AddonRegistry.init(base); + expect(addons).toStrictEqual([ + 'test-released-unmentioned:extra1,extra2', + 'test-released-dummy', + 'test-addon', + 'test-released-addon:extra', + 'test-released-source-addon', + 'my-volto-config-addon', + ]); + expect(shadowAliases).toStrictEqual([ + { + find: 'test-released-source-addon/index', + replacement: `${base}/addons/test-addon/src/custom-addons/test-released-source-addon/index.js`, + }, + { + find: '@plone/volto/server', + replacement: `${base}/addons/test-addon/src/custom-addons/volto/server.jsx`, + }, + { + find: '@root/marker', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/@root/marker.js`, + }, + { + find: '@plone/volto/routes', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/routes.tsx`, + }, + { + find: '@plone/volto/client', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/client.js`, + }, + { + find: '@plone/volto/TSComponent', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx`, + }, + { + find: '@plone/volto/LanguageSwitcher', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js`, + }, + ]); + expect(theme).toStrictEqual(undefined); + }); +}); + +describe('AddonRegistry - Project', () => { + it('works in a mock project directory', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + + const voltoPath = `${base}/node_modules/@plone/volto`; + + expect(registry.projectRootPath).toStrictEqual(base); + expect(registry.voltoPath).toStrictEqual(voltoPath); + + expect(registry.addonNames).toStrictEqual([ + 'test-addon', + 'test-released-addon', + 'test-released-source-addon', + 'my-volto-config-addon', + 'test-released-dummy', + 'test-released-unmentioned', + ]); + + expect(registry.packages).toEqual({ + 'test-addon': { + isPublishedPackage: false, + modulePath: `${base}/addons/test-addon/src`, + name: 'test-addon', + packageJson: `${base}/addons/test-addon/package.json`, + addons: ['test-released-dummy'], + isRegisteredAddon: true, + version: '0.0.0', + }, + 'test-released-addon': { + basePath: `${base}/node_modules/test-released-addon`, + isPublishedPackage: true, + modulePath: `${base}/node_modules/test-released-addon`, + name: 'test-released-addon', + packageJson: `${base}/node_modules/test-released-addon/package.json`, + addons: ['test-released-unmentioned:extra1,extra2'], + isRegisteredAddon: true, + tsConfigPaths: null, + version: '0.0.0', + }, + 'test-released-source-addon': { + basePath: `${base}/node_modules/test-released-source-addon`, + isPublishedPackage: true, + modulePath: `${base}/node_modules/test-released-source-addon/src`, + name: 'test-released-source-addon', + packageJson: `${base}/node_modules/test-released-source-addon/package.json`, + razzleExtender: `${base}/node_modules/test-released-source-addon/razzle.extend.js`, + addons: [], + isRegisteredAddon: true, + tsConfigPaths: null, + version: '0.0.0', + }, + 'test-released-unmentioned': { + addons: [], + basePath: `${base}/node_modules/test-released-unmentioned`, + isPublishedPackage: true, + modulePath: `${base}/node_modules/test-released-unmentioned`, + name: 'test-released-unmentioned', + packageJson: `${base}/node_modules/test-released-unmentioned/package.json`, + isRegisteredAddon: true, + tsConfigPaths: null, + version: '0.0.0', + }, + 'my-volto-config-addon': { + addons: ['test-released-dummy'], + isPublishedPackage: false, + modulePath: `${base}/addons/my-volto-config-addon/src`, + name: 'my-volto-config-addon', + packageJson: `${base}/addons/my-volto-config-addon/package.json`, + isRegisteredAddon: true, + version: '0.0.0', + }, + 'test-released-dummy': { + addons: ['test-released-unmentioned'], + isPublishedPackage: false, + modulePath: `${base}/addons/test-released-dummy`, + name: 'test-released-dummy', + packageJson: `${base}/addons/test-released-dummy/package.json`, + isRegisteredAddon: true, + version: '0.0.0', + }, + }); + }); + + it('provides aliases for addons', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + expect(registry.getResolveAliases()).toStrictEqual({ + 'my-volto-config-addon': `${base}/addons/my-volto-config-addon/src`, + 'test-addon': `${base}/addons/test-addon/src`, + 'test-released-addon': `${base}/node_modules/test-released-addon`, + 'test-released-dummy': `${base}/addons/test-released-dummy`, + 'test-released-source-addon': `${base}/node_modules/test-released-source-addon/src`, + 'test-released-unmentioned': `${base}/node_modules/test-released-unmentioned`, + }); + }); + + it('provides addon extenders', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + expect(registry.getAddonExtenders().length).toBe(1); + }); + + it('provides a list of addon records ordered by initial package declaration', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + const addons = registry.getAddons(); + expect(addons.map((a) => a.name)).toStrictEqual([ + 'test-released-unmentioned', + 'test-released-dummy', + 'test-addon', + 'test-released-addon', + 'test-released-source-addon', + 'my-volto-config-addon', + ]); + }); + + it('provides customization paths declared in a Volto project', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + expect(registry.getProjectCustomizationPaths()).toStrictEqual({ + '@plone/volto/LanguageSwitcher': `${base}/src/customizations/LanguageSwitcher.js`, + '@plone/volto/TSComponent': `${base}/src/customizations/TSComponent.jsx`, + '@plone/volto/client': `${base}/src/customizations/client.js`, + '@plone/volto/routes': `${base}/src/customizations/routes.tsx`, + 'test-addon/testaddon': `${base}/src/custom-addons/test-addon/testaddon.js`, + '@plone/volto/server': `${base}/src/customizations/server.jsx`, + }); + }); + + it('provides customization paths declared in addons', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + expect(registry.getAddonCustomizationPaths()).toStrictEqual({ + '@plone/volto/LanguageSwitcher': `${base}/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js`, + '@plone/volto/TSComponent': `${base}/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx`, + '@plone/volto/client': `${base}/node_modules/test-released-source-addon/src/customizations/client.js`, + '@plone/volto/routes': `${base}/node_modules/test-released-source-addon/src/customizations/routes.tsx`, + '@plone/volto/server': `${base}/addons/test-addon/src/custom-addons/volto/server.jsx`, + '@root/marker': `${base}/node_modules/test-released-source-addon/src/customizations/@root/marker.js`, + 'test-released-source-addon/index': `${base}/addons/test-addon/src/custom-addons/test-released-source-addon/index.js`, + }); + }); +}); + +describe('Addon chain loading dependencies', () => { + const depTree = { + add0: ['add1'], + add1: ['add2:e0', 'add4'], + add2: ['add3:e6', 'add5', 'add6'], + add3: ['add0'], + add4: ['add2:e1,e3'], + add5: ['add6'], + }; + const extractor = (name) => depTree[name] || []; + + test('no addons', () => { + const graph = buildDependencyGraph([], extractor); + const deps = getAddonsLoaderChain(graph); + expect(deps).toEqual([]); + }); + + test('one addon', () => { + const graph = buildDependencyGraph(['volto-addon1'], extractor); + const deps = getAddonsLoaderChain(graph); + expect(deps).toEqual(['volto-addon1']); + }); + + test('two addons', () => { + const graph = buildDependencyGraph( + ['volto-addon1', 'volto-addon2'], + extractor, + ); + const deps = getAddonsLoaderChain(graph); + expect(deps).toEqual(['volto-addon1', 'volto-addon2']); + }); + + test('one addon with dependency', () => { + const graph = buildDependencyGraph(['add5'], extractor); + const deps = getAddonsLoaderChain(graph); + expect(deps).toEqual(['add6', 'add5']); + }); + + test('one addon with circular dependencies', () => { + const graph = buildDependencyGraph(['add0'], extractor); + const deps = getAddonsLoaderChain(graph); + expect(deps).toEqual([ + 'add3:e6', + 'add6', + 'add5', + 'add2:e0,e1,e3', + 'add4', + 'add1', + 'add0', + ]); + }); +}); + +describe('Addon via env var - Released addon (same as dev add-on, when resolved via workspaces)', () => { + const originalEnv = process.env; + + beforeEach(() => { + vi.resetModules(); + process.env = { + ...originalEnv, + ADDONS: 'test-released-via-addons-env-var', + }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('addons can be specified on the fly using ADDONS env var - Released addon', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const { registry } = AddonRegistry.init(base); + expect( + Object.keys(registry.packages).includes( + 'test-released-via-addons-env-var', + ), + ).toBe(true); + }); +}); + +describe('Add-on via config file provided using an env var', () => { + const originalEnv = process.env; + const base = path.join(import.meta.dirname, 'fixtures', 'test-volto-project'); + + beforeEach(() => { + vi.resetModules(); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('VOLTOCONFIG - provides a list of addon records ordered using an env var for providing the configuration file', () => { + process.env = { + ...originalEnv, + VOLTOCONFIG: `${base}/volto.config.envvar.js`, + }; + const { registry } = AddonRegistry.init(base); + const addons = registry.getAddons(); + expect(addons.map((a) => a.name)).toStrictEqual([ + 'test-released-unmentioned', + 'test-released-dummy', + 'test-addon', + 'test-released-addon', + 'test-released-source-addon', + 'my-volto-config-addon-via-env-var', + ]); + }); + + it('[REGISTRYCONFIG - provides a list of addon records ordered using an env var for providing the configuration file', () => { + process.env = { + ...originalEnv, + REGISTRYCONFIG: `${base}/volto.config.envvar.js`, + }; + + const { registry } = AddonRegistry.init(base); + const addons = registry.getAddons(); + expect(addons.map((a) => a.name)).toStrictEqual([ + 'test-released-unmentioned', + 'test-released-dummy', + 'test-addon', + 'test-released-addon', + 'test-released-source-addon', + 'my-volto-config-addon-via-env-var', + ]); + }); +}); diff --git a/packages/registry/__tests__/create-addons-loader.test.js b/packages/registry/__tests__/create-addons-loader.test.js new file mode 100644 index 0000000000..60b611ad67 --- /dev/null +++ b/packages/registry/__tests__/create-addons-loader.test.js @@ -0,0 +1,164 @@ +import { + getAddonsLoaderCode, + nameFromPackage, +} from '../src/addon-registry/create-addons-loader'; +import { describe, expect, test } from 'vitest'; + +describe('create-addons-loader code generation', () => { + test('no addon creates simple loader, default = no loadProjectConfig', () => { + const code = getAddonsLoaderCode([]); + expect(code).toBe(`/* +This file is autogenerated. Don't change it directly. +Instead, change the "addons" setting in your package.json file. +*/ + + +const addonsInfo = {}; +export { addonsInfo }; + +const safeWrapper = (func) => (config) => { + const res = func(config); + if (typeof res === 'undefined') { + throw new Error("Configuration function doesn't return config"); + } + return res; +} + +const projectConfigLoader = false; +const projectConfig = (config) => { + return projectConfigLoader && typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; +} + +const load = (config) => { + const addonLoaders = []; + if(!addonLoaders.every((el) => typeof el === "function")) { + throw new TypeError( + 'Each addon has to provide a function applying its configuration to the projects configuration.', + ); + } + return projectConfig(addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config)); +}; +export default load; +`); + }); + + test('no addon creates simple loader, loadProjectConfig set to true', () => { + const code = getAddonsLoaderCode([], {}, true); + expect(code).toBe(`/* +This file is autogenerated. Don't change it directly. +Instead, change the "addons" setting in your package.json file. +*/ + +const projectConfigLoader = require('@root/config'); + +const addonsInfo = {}; +export { addonsInfo }; + +const safeWrapper = (func) => (config) => { + const res = func(config); + if (typeof res === 'undefined') { + throw new Error("Configuration function doesn't return config"); + } + return res; +} + + +const projectConfig = (config) => { + return projectConfigLoader && typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; +} + +const load = (config) => { + const addonLoaders = []; + if(!addonLoaders.every((el) => typeof el === "function")) { + throw new TypeError( + 'Each addon has to provide a function applying its configuration to the projects configuration.', + ); + } + return projectConfig(addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config)); +}; +export default load; +`); + }); + + test('one addon creates loader', () => { + const code = getAddonsLoaderCode(['volto-addon1']); + expect(code.indexOf("import voltoAddon1 from 'volto-addon1';") > 0).toBe( + true, + ); + }); + + test('two addons create loaders', () => { + const code = getAddonsLoaderCode(['volto-addon1', 'volto-addon2']); + expect( + code.indexOf(` +import voltoAddon1 from 'volto-addon1'; +import voltoAddon2 from 'volto-addon2';`) > 0, + ).toBe(true); + }); + + test('one addons plus one extra creates loader', () => { + const code = getAddonsLoaderCode(['volto-addon1:loadExtra1']); + expect( + code.indexOf(` +import voltoAddon1, { loadExtra1 as loadExtra10 } from 'volto-addon1'; +`) > 0, + ).toBe(true); + }); + + test('one addons plus two extras creates loader', () => { + const code = getAddonsLoaderCode(['volto-addon1:loadExtra1,loadExtra2']); + expect( + code.indexOf(` +import voltoAddon1, { loadExtra1 as loadExtra10, loadExtra2 as loadExtra21 } from 'volto-addon1'; +`) > 0, + ).toBe(true); + }); + + test('two addons plus extras creates loader', () => { + const code = getAddonsLoaderCode([ + 'volto-addon1:loadExtra1,loadExtra2', + 'volto-addon2:loadExtra3,loadExtra4', + ]); + expect( + code.indexOf(` +import voltoAddon1, { loadExtra1 as loadExtra10, loadExtra2 as loadExtra21 } from 'volto-addon1'; +import voltoAddon2, { loadExtra3 as loadExtra32, loadExtra4 as loadExtra43 } from 'volto-addon2'; +`) > 0, + ).toBe(true); + }); +}); + +describe('create-addons-loader default name generation', () => { + const getName = nameFromPackage; + + test('passing a simple word returns a word', () => { + expect(getName('something')).toBe('something'); + }); + + test('passing a kebab-name returns a word', () => { + expect(getName('volto-something-else')).toBe('voltoSomethingElse'); + }); + + test('passing a simple relative path returns random string', () => { + const rand = getName('../../'); + expect(rand.length).toBe(10); + expect(new RegExp(/[abcdefghjk]+/).exec(rand)[0].length > 0).toBe(true); + }); + test('passing a tilda relative path with addon strips tilda', () => { + const name = getName('~/addons/volto-addon1'); + expect(name).toBe('addonsvoltoAddon1'); + }); + test('passing a namespace package strips @', () => { + const name = getName('@plone/volto-addon1'); + expect(name).toBe('plonevoltoAddon1'); + }); + test('passing a tilda relative path strips tilda', () => { + const name = getName('~/../'); + expect(name.length).toBe(10); + expect(new RegExp(/[abcdefghjk]+/).exec(name)[0].length > 0).toBe(true); + }); + test('passing a backspaced path strips backspace', () => { + const name = getName('c:\\nodeprojects'); + expect(name).toBe('cnodeprojects'); + }); +}); diff --git a/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/components/Logo/Logo.svg b/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/components/Logo/Logo.svg new file mode 100644 index 0000000000..1aa6adfdf3 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/components/Logo/Logo.svg @@ -0,0 +1 @@ +asdasd diff --git a/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/index.js b/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/package.json b/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/package.json new file mode 100644 index 0000000000..68a86c4d37 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/node_modules/@plone/slots/package.json @@ -0,0 +1,4 @@ +{ + "name": "@plone/slots", + "main": "index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/customizations/@plone/slots/components/Logo/Logo.svg b/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/customizations/@plone/slots/components/Logo/Logo.svg new file mode 100644 index 0000000000..3e8fcacea6 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/customizations/@plone/slots/components/Logo/Logo.svg @@ -0,0 +1 @@ + diff --git a/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/index.js b/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/package.json b/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/package.json new file mode 100644 index 0000000000..ccf0c501c1 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/node_modules/my-addon/package.json @@ -0,0 +1,4 @@ +{ + "name": "my-addon", + "main": "index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-app/package.json b/packages/registry/__tests__/fixtures/test-app/package.json new file mode 100644 index 0000000000..24de726e14 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/package.json @@ -0,0 +1,3 @@ +{ + "name": "test-app" +} diff --git a/packages/registry/__tests__/fixtures/test-app/registry.config.js b/packages/registry/__tests__/fixtures/test-app/registry.config.js new file mode 100644 index 0000000000..f520684acb --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-app/registry.config.js @@ -0,0 +1,3 @@ +module.exports = { + addons: ['@plone/slots', 'my-addon'], +}; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json b/packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json new file mode 100644 index 0000000000..87e8d2ad96 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json @@ -0,0 +1,10 @@ +{ + "name": "test-addon", + "version": "0.0.0", + "customizationPaths": [ + "src/custom-addons" + ], + "addons": [ + "test-released-dummy" + ] +} diff --git a/packages/registry/pnpm-lock.yaml b/packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js similarity index 100% rename from packages/registry/pnpm-lock.yaml rename to packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/index.js b/packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/package.json b/packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/package.json new file mode 100644 index 0000000000..c81be50ab7 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/package.json @@ -0,0 +1,5 @@ +{ + "name": "non-volto-addon-lib", + "version": "0.0.0", + "main": "index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/package.json b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/package.json new file mode 100644 index 0000000000..87e8d2ad96 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/package.json @@ -0,0 +1,10 @@ +{ + "name": "test-addon", + "version": "0.0.0", + "customizationPaths": [ + "src/custom-addons" + ], + "addons": [ + "test-released-dummy" + ] +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/test-released-source-addon/index.js b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/test-released-source-addon/index.js new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/test-released-source-addon/index.js @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/volto/server.jsx b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/volto/server.jsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/volto/server.jsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/testaddon.js b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/testaddon.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/index.js b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/package.json b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/package.json new file mode 100644 index 0000000000..18c940d721 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-released-dummy", + "version": "0.0.0", + "main": "index.js", + "addons": [ + "test-released-unmentioned" + ] +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/jsconfig.json b/packages/registry/__tests__/fixtures/test-volto-project/jsconfig.json new file mode 100644 index 0000000000..4c3beb25ec --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "paths": { + "test-addon": ["test-addon/src"], + "test-released-dummy": ["test-released-dummy"], + "my-volto-config-addon": ["my-volto-config-addon/src"], + "non-volto-addon-lib": ["non-volto-addon-lib/src"] + }, + "baseUrl": "addons" + } +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/LanguageSwitcher.jsx b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/LanguageSwitcher.jsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/LanguageSwitcher.jsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/TSComponent.tsx b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/TSComponent.tsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/TSComponent.tsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/client.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/client.js new file mode 100644 index 0000000000..0dae97301d --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/client.js @@ -0,0 +1 @@ +//dummy file diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/routes.jsx b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/routes.jsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/routes.jsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/server.jsx b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/server.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/my-volto-config-addon-via-env-var/index.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/my-volto-config-addon-via-env-var/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/my-volto-config-addon-via-env-var/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/my-volto-config-addon-via-env-var/package.json b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/my-volto-config-addon-via-env-var/package.json new file mode 100644 index 0000000000..ac0652a9b0 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/my-volto-config-addon-via-env-var/package.json @@ -0,0 +1,11 @@ +{ + "name": "my-volto-config-addon-via-env-var", + "version": "0.0.0", + "main": "index.js", + "customizationPaths": [ + "src/custom-addons" + ], + "addons": [ + "test-released-dummy" + ] +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/index.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/package.json b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/package.json new file mode 100644 index 0000000000..4f1f61b465 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-released-addon", + "version": "0.0.0", + "main": "index.js", + "addons": [ + "test-released-unmentioned:extra1,extra2" + ] +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/index.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/index.js new file mode 100644 index 0000000000..53e61140f2 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/index.js @@ -0,0 +1,2 @@ +export default (config) => config; + diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/package.json b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/package.json new file mode 100644 index 0000000000..4640873bbf --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/package.json @@ -0,0 +1,5 @@ +{ + "name": "test-released-dummy", + "main": "index.js", + "addons": [] +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/package.json b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/package.json new file mode 100644 index 0000000000..3220bd17cc --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/package.json @@ -0,0 +1,5 @@ +{ + "name": "test-released-source-addon", + "version": "0.0.0", + "main": "src/index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/razzle.extend.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/razzle.extend.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/@root/marker.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/@root/marker.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/client.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/client.js new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/client.js @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/routes.tsx b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/routes.tsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/routes.tsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/index.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/index.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/index.js new file mode 100644 index 0000000000..53e61140f2 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/index.js @@ -0,0 +1,2 @@ +export default (config) => config; + diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/package.json b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/package.json new file mode 100644 index 0000000000..b3b3c399f1 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/package.json @@ -0,0 +1,5 @@ +{ + "name": "test-released-unmentioned", + "version": "0.0.0", + "main": "index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/index.js b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/index.js new file mode 100644 index 0000000000..53e61140f2 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/index.js @@ -0,0 +1,2 @@ +export default (config) => config; + diff --git a/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/package.json b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/package.json new file mode 100644 index 0000000000..c6a4424310 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-released-via-addons-env-var", + "main": "index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/package.json b/packages/registry/__tests__/fixtures/test-volto-project/package.json new file mode 100644 index 0000000000..bb67637bb2 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/package.json @@ -0,0 +1,12 @@ +{ + "name": "test-volto-project", + "addons": [ + "test-addon", + "test-released-addon:extra", + "test-released-source-addon" + ], + "customizationPaths": [ + "src/custom-addons", + "src/customizations/" + ] +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/package.json b/packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/package.json new file mode 100644 index 0000000000..7e6c4a11fb --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/package.json @@ -0,0 +1,4 @@ +{ + "name": "test-local-packages-via-addons-env-var", + "main": "src/index.js" +} diff --git a/packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/src/index.js b/packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/src/index.js new file mode 100644 index 0000000000..8cb61d300a --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/src/index.js @@ -0,0 +1 @@ +export default (config) => config; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/test-addon/testaddon.js b/packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/test-addon/testaddon.js new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/test-addon/testaddon.js @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/volto/client.js b/packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/volto/client.js new file mode 100644 index 0000000000..4d389677f0 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/volto/client.js @@ -0,0 +1 @@ +// another customization diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/LanguageSwitcher.js b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/LanguageSwitcher.js new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/LanguageSwitcher.js @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/TSComponent.jsx b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/TSComponent.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/client.js b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/client.js new file mode 100644 index 0000000000..b262fa10a3 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/client.js @@ -0,0 +1 @@ +// dummy file diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/routes.tsx b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/routes.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/server.jsx b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/server.jsx new file mode 100644 index 0000000000..8337712ea5 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/src/customizations/server.jsx @@ -0,0 +1 @@ +// diff --git a/packages/registry/__tests__/fixtures/test-volto-project/src/marker.js b/packages/registry/__tests__/fixtures/test-volto-project/src/marker.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/registry/__tests__/fixtures/test-volto-project/volto.config.envvar.js b/packages/registry/__tests__/fixtures/test-volto-project/volto.config.envvar.js new file mode 100644 index 0000000000..2e37cd37c2 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/volto.config.envvar.js @@ -0,0 +1,3 @@ +module.exports = { + addons: ['my-volto-config-addon-via-env-var'], +}; diff --git a/packages/registry/__tests__/fixtures/test-volto-project/volto.config.js b/packages/registry/__tests__/fixtures/test-volto-project/volto.config.js new file mode 100644 index 0000000000..ec05e8e085 --- /dev/null +++ b/packages/registry/__tests__/fixtures/test-volto-project/volto.config.js @@ -0,0 +1,3 @@ +module.exports = { + addons: ['my-volto-config-addon'], +}; diff --git a/packages/registry/__tests__/fixtures/volto-addon1.js b/packages/registry/__tests__/fixtures/volto-addon1.js new file mode 100644 index 0000000000..b406bacbd8 --- /dev/null +++ b/packages/registry/__tests__/fixtures/volto-addon1.js @@ -0,0 +1,14 @@ +export default (config) => { + const settings = { + nonContentRoutes: [], + supportedLanguages: ['en'], + navDepth: 1, + }; + + config.settings = { + ...config.settings, + ...settings, + }; + + return config; +}; diff --git a/packages/registry/__tests__/fixtures/volto-addon2.js b/packages/registry/__tests__/fixtures/volto-addon2.js new file mode 100644 index 0000000000..62ec3d2cf0 --- /dev/null +++ b/packages/registry/__tests__/fixtures/volto-addon2.js @@ -0,0 +1,29 @@ +export default (config) => { + const settings = { + nonContentRoutes: [], + supportedLanguages: ['en', 'de'], + navDepth: 3, + }; + + config.settings = { + ...config.settings, + ...settings, + }; + + return config; +}; + +const additionalConfig = (config) => { + const settings = { + navDepth: 6, + }; + + config.settings = { + ...config.settings, + ...settings, + }; + + return config; +}; + +export { additionalConfig }; diff --git a/packages/registry/__tests__/fixtures/volto-addon3.js b/packages/registry/__tests__/fixtures/volto-addon3.js new file mode 100644 index 0000000000..2231f8a8b1 --- /dev/null +++ b/packages/registry/__tests__/fixtures/volto-addon3.js @@ -0,0 +1,41 @@ +export default (config) => { + const settings = { + nonContentRoutes: [], + supportedLanguages: ['en', 'de'], + navDepth: 3, + }; + + config.settings = { + ...config.settings, + ...settings, + }; + + return config; +}; + +const additionalConfig = (config) => { + const settings = { + navDepth: 6, + }; + + config.settings = { + ...config.settings, + ...settings, + }; + + return config; +}; + +const alternateAdditionalConfig = (config) => { + const settings = { + navDepth: 10, + }; + + config.settings = { + ...config.settings, + ...settings, + }; + + return config; +}; +export { additionalConfig, alternateAdditionalConfig }; diff --git a/packages/registry/docs/_static/Plone_logo_square.png b/packages/registry/docs/_static/Plone_logo_square.png new file mode 100644 index 0000000000..f42a1220d7 Binary files /dev/null and b/packages/registry/docs/_static/Plone_logo_square.png differ diff --git a/packages/registry/docs/_static/favicon.ico b/packages/registry/docs/_static/favicon.ico new file mode 100644 index 0000000000..ae7bb0cd8b Binary files /dev/null and b/packages/registry/docs/_static/favicon.ico differ diff --git a/packages/registry/docs/_static/logo.svg b/packages/registry/docs/_static/logo.svg new file mode 100644 index 0000000000..6bfdc1ee90 --- /dev/null +++ b/packages/registry/docs/_static/logo.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/registry/docs/_static/print.css b/packages/registry/docs/_static/print.css new file mode 100644 index 0000000000..8dbc2d5794 --- /dev/null +++ b/packages/registry/docs/_static/print.css @@ -0,0 +1,3 @@ +.tooltip { + display: none; +} diff --git a/packages/registry/docs/conceptual-guides/add-on-registry.md b/packages/registry/docs/conceptual-guides/add-on-registry.md new file mode 100644 index 0000000000..763d9f54d9 --- /dev/null +++ b/packages/registry/docs/conceptual-guides/add-on-registry.md @@ -0,0 +1,38 @@ +--- +myst: + html_meta: + "description": "An explanation of the add-on registry in @plone/registry" + "property=og:description": "An explanation of the add-on registry in @plone/registry" + "property=og:title": "Add-on registry" + "keywords": "@plone/registry, registry, add-on" +--- + +# Add-on registry + +An add-on registry is a facility that allows an app, which was built on an existing framework, to itself be extensible and pluggable. + +The add-on registry is a store where you can register a number of add-ons that your app consumes. + +The add-on registry can be queried, so it can provide a list of add-ons installed in the registry and their properties. + + +## What is an add-on? + +Add-on packages are just CommonJS or ESM packages. +Their main purpose is to encapsulate logic, configuration and customizations in a reusable way. +The only requirement is that their primary entry point (the `main` key of their `package.json`) points to a module that exports a default function, which acts as a default configuration loader for that package. + +Add-ons are applied in the order they are declared in the `addons` key of {file}`package.json` or programatically via a provided configuration file. +Add-ons can override configuration coming from other add-ons, providing a hierarchy of configuration stacks. + +An add-on can be published in an npm registry, just as any other package. +However, add-ons are meant to not be transpiled, but built along with your app code. +They can be released as "source" packages or used directly in your app as local code. + +Add-ons can define shadowed components. +"Component shadowing" is a technique for overriding modules of other packages at build time. +This technique builds upon the `resolve.aliases` facilities of bundlers, so modules can be replaced when the app is being built. + +Add-ons can be chained, where each one can configure the app in some way. +If needed, each add-on in the chain can override or extend the previous configuration that other add-ons set. +Thus, the order in which you register add-ons matters. diff --git a/packages/registry/docs/conceptual-guides/component-registry.md b/packages/registry/docs/conceptual-guides/component-registry.md new file mode 100644 index 0000000000..08fb9226d9 --- /dev/null +++ b/packages/registry/docs/conceptual-guides/component-registry.md @@ -0,0 +1,20 @@ +--- +myst: + html_meta: + "description": "An explanation of the component registry in @plone/registry" + "property=og:description": "An explanation of the component registry in @plone/registry" + "property=og:title": "Component registry" + "keywords": "@plone/registry, registry, component" +--- + +# Component registry + +The configuration registry stores a component registry in itself. +The component registry is a mapping of names to components. +You can look up a name, and receive a component that you can reference in your code. +This provides an alternative, and more convenient, way to customize components in a pluggable way. + +You can programmatically override such registrations from your add-on or projects because it's stored in the configuration registry. +You can customize a component without using shadowing at all, if the code that uses the component retrieves from the component registry, rather than import it directly. +You can even have modifiers to the component registrations through dependencies. +Thus you can adapt the call, given an array of such dependencies. diff --git a/packages/registry/docs/conceptual-guides/configuration-registry.md b/packages/registry/docs/conceptual-guides/configuration-registry.md new file mode 100644 index 0000000000..c1fd19db20 --- /dev/null +++ b/packages/registry/docs/conceptual-guides/configuration-registry.md @@ -0,0 +1,78 @@ +--- +myst: + html_meta: + "description": "An explanation of the configuration registry in @plone/registry" + "property=og:description": "An explanation of the configuration registry in @plone/registry" + "property=og:title": "Configuration registry" + "keywords": "@plone/registry, registry, configuration" +--- + +# Configuration registry + +The configuration registry supplements the add-on registry. +They both work together to provide extensibility and pluggability capabilities. +The configuration registry is a facility that stores app configuration to share in the app. +The add-ons save configuration from the registry using their default export function on app bootstrap time. +They retrieve this configuration as needed by the functionality and components they expose. + +## Example use case - Pluggable block system + +Let's say that your app is the user interface of a content management system (CMS). +This CMS uses blocks as its main fundamental unit of content. +The pages that the CMS builds are made up of these blocks. +The CMS has some basic available blocks, yet it's a requirement that integrators can register more blocks in a pluggable way. +This app will use the add-on registry to extend the basic CMS capabilities, so an external add-on can supplement their own add-ons to those in the basic CMS. + +Let's assume we've defined a key in the registry `config.blocks.blocksConfig`, and defined a way to register the available blocks in the CMS as the keys in that object in the configuration registry: + +```js + config.blocks.blocksConfig.faq_viewer = { + id: 'faq_viewer', + title: 'FAQ Viewer', + edit: FAQBlockEdit, + view: FAQBlockView, + icon: chartIcon, + group: 'common', + restricted: false, + mostUsed: true, + sidebarTab: 1, + }; +``` + +The configuration registry will have other keys already set by default, which will compose the initial set of basic blocks used by the CMS. +Then the CMS will populate the available blocks in the user interface. + +The add-on is meant to extend the initial configuration. +From the default export function of our add-on, you should provide the configuration of the new block: + +```ts +export default function applyConfig(config: ConfigData) { + config.blocks.blocksConfig.faq_viewer = { + id: 'faq_viewer', + title: 'FAQ Viewer', + edit: FAQBlockEdit, + view: FAQBlockView, + icon: chartIcon, + group: 'common', + restricted: false, + mostUsed: true, + sidebarTab: 1, + }; + + return config; +} +``` + +Once the app starts, the add-on registry will execute, in order, all the registered add-ons' default export functions, configuring the new block. +The add-on will then become available to the CMS when it asks the configuration registry for it. + + +## Configuration registry artifacts + +The configuration registry also stores special elements that can be queried and retrieved in a pluggable way. + +- Components +- Slots +- Utilities + +Some of the components are particular to the use case of a CMS, such as slots, but the abstraction can be ported and applied to different scenarios. diff --git a/packages/registry/docs/conceptual-guides/utility-registry.md b/packages/registry/docs/conceptual-guides/utility-registry.md new file mode 100644 index 0000000000..5c0d284bae --- /dev/null +++ b/packages/registry/docs/conceptual-guides/utility-registry.md @@ -0,0 +1,14 @@ +--- +myst: + html_meta: + "description": "An explanation of the utility registry in @plone/registry" + "property=og:description": "An explanation of the utility registry in @plone/registry" + "property=og:title": "Utility registry" + "keywords": "@plone/registry, registry, utility" +--- + +# Utility registry + +The configuration registry stores a utility registry in itself. +The component registry is a mapping of a `name` and a `type` to a method or function. +The utility registry works similarly, but for methods and functions, and with additional query argument `type`. diff --git a/packages/registry/docs/conf.py b/packages/registry/docs/conf.py new file mode 100644 index 0000000000..796fc4b620 --- /dev/null +++ b/packages/registry/docs/conf.py @@ -0,0 +1,297 @@ +# Configuration file for the Sphinx documentation builder. +# Plone Documentation build configuration file + + +# -- Path setup -------------------------------------------------------------- + +from datetime import datetime + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath(".")) + +import os +import json + + +# -- Project information ----------------------------------------------------- + +project = "@plone/registry documentation" +copyright = "Plone Foundation" +author = "Plone Community" +trademark_name = "Plone" +now = datetime.now() +year = str(now.year) + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +with open( + os.path.join(os.path.abspath("."), "../package.json"), "r" +) as package_json: + data = package_json.read() + +version_from_package_json = json.loads(data)["version"] + +if version_from_package_json: + # The short X.Y version. + version = version_from_package_json + # The full version, including alpha/beta/rc tags. + release = version_from_package_json +else: + version = "1.8.0" + release = "1.8.0" + + +# -- General configuration ---------------------------------------------------- + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ["_templates"] + +# Add any Sphinx extension module names here, as strings. +# They can be extensions coming with Sphinx (named "sphinx.ext.*") +# or your custom ones. +extensions = [ + "myst_parser", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx_copybutton", + "sphinxext.opengraph", +] + + +# If true, the Docutils Smart Quotes transform, originally based on SmartyPants +# (limited to English) and currently applying to many languages, will be used +# to convert quotes and dashes to typographically correct entities. +# Note to maintainers: setting this to `True` will cause contractions and +# hyphenated words to be marked as misspelled by spellchecker. +smartquotes = False + +# The name of the Pygments (syntax highlighting) style to use. +# pygments_style = "sphinx.pygments_styles.PyramidStyle" +pygments_style = "sphinx" + +# Options for the linkcheck builder +# Ignore localhost +linkcheck_ignore = [ + # TODO: Before release, clean up any links to ignore + r"http://127.0.0.1", + r"http://localhost", + # Ignore pages that require authentication + r"https://github.com/plone/volto/issues/new/choose", # requires auth + # Ignore github.com pages with anchors + r"https://github.com/.*#.*", + # Ignore other specific anchors +] +linkcheck_anchors = True +linkcheck_timeout = 5 +linkcheck_retries = 1 + +# The suffix of source filenames. +source_suffix = { + ".md": "markdown", + ".bugfix": "markdown", + ".breaking": "markdown", + ".documentation": "markdown", + ".feature": "markdown", + ".internal": "markdown", +} + +# The master toctree document. +master_doc = "index" + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [ +] + +suppress_warnings = [ + # "toc.excluded", # Suppress `WARNING: document isn't included in any toctree` + "toc.not_readable", # Suppress `WARNING: toctree contains reference to nonexisting document 'news*'` +] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "plone_sphinx_theme" +html_logo = "_static/logo.svg" +html_favicon = "_static/favicon.ico" +html_theme_options = { + "article_header_start": ["toggle-primary-sidebar"], + "extra_footer": """

The text and illustrations in this website are licensed by the Plone Foundation under a Creative Commons Attribution 4.0 International license. Plone and the Plone® logo are registered trademarks of the Plone Foundation, registered in the United States and other countries. For guidelines on the permitted uses of the Plone trademarks, see https://plone.org/foundation/logo. All other trademarks are owned by their respective owners.

+

Pull request previews by Read the Docs.

""", + "footer_end": ["version.html"], + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/plone/volto", + "icon": "fa-brands fa-square-github", + "type": "fontawesome", + "attributes": { + "target": "_blank", + "rel": "noopener me", + "class": "nav-link custom-fancy-css" + } + }, + { + "name": "Mastodon", + "url": "https://plone.social/@plone", + "icon": "fa-brands fa-mastodon", + "type": "fontawesome", + "attributes": { + "target": "_blank", + "rel": "noopener me", + "class": "nav-link custom-fancy-css" + } + }, + { + "name": "X (formerly Twitter)", + "url": "https://x.com/plone", + "icon": "fa-brands fa-square-x-twitter", + "type": "fontawesome", + "attributes": { + "target": "_blank", + "rel": "noopener me", + "class": "nav-link custom-fancy-css" + } + }, + ], + "logo": { + "text": "@plone/registry Documentation", + }, + "navigation_with_keys": True, + "path_to_docs": "docs", + "repository_branch": "main", + "repository_url": "https://github.com/plone/volto/tree/main/packages/registry", + "search_bar_text": "Search", # TODO: Confirm usage of search_bar_text in plone-sphinx-theme + "use_edit_page_button": True, + "use_issues_button": True, + "use_repository_button": True, +} + +# Announce that we have an opensearch plugin +# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_use_opensearch +html_use_opensearch = "https://plone-registry.readthedocs.io/" # TODO: Confirm usage of opensearch in theme + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = "%(project)s v%(release)s" % {"project": project, "release": release} + +html_css_files = ["custom.css", ("print.css", {"media": "print"})] + +# If false, no index is generated. +html_use_index = True + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = "%(project)s v%(release)s" % {"project": project, "release": release} + +html_extra_path = [ + "robots.txt", +] + +html_static_path = [ + "_static", +] + + +# -- Options for MyST markdown conversion to HTML ----------------------------- + +# For more information see: +# https://myst-parser.readthedocs.io/en/latest/syntax/optional.html +myst_enable_extensions = [ + "deflist", # Support definition lists. + # https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#definition-lists + "linkify", # Identify "bare" web URLs and add hyperlinks. + "colon_fence", # You can also use ::: delimiters to denote code fences,\ + # instead of ```. + "html_image", # For inline images. See https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#html-images +] + + +# -- Intersphinx configuration ---------------------------------- + +# This extension can generate automatic links to the documentation of objects +# in other projects. Usage is simple: whenever Sphinx encounters a +# cross-reference that has no matching target in the current documentation set, +# it looks for targets in the documentation sets configured in +# intersphinx_mapping. A reference like :py:class:`zipfile.ZipFile` can then +# linkto the Python documentation for the ZipFile class, without you having to +# specify where it is located exactly. +# +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html +# +intersphinx_mapping = { + "plone": ("https://6.docs.plone.org/", None), + "python": ("https://docs.python.org/3/", None), + "training": ("https://training.plone.org/", None), +} + + +# -- OpenGraph configuration ---------------------------------- + +ogp_site_url = "https://plone-registry.readthedocs.io/" +ogp_description_length = 200 +ogp_image = "https://plone-registry.readthedocs.io/en/latest/_static/Plone_logo_square.png" +ogp_site_name = "@plone/registry Documentation" +ogp_type = "website" +ogp_custom_meta_tags = [ + '', +] + + +# -- Options for sphinx.ext.todo ----------------------- +# See http://sphinx-doc.org/ext/todo.html#confval-todo_include_todos +todo_include_todos = True + + +# -- Options for HTML help output ------------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "PloneRegistryDocumentation" + + +# -- Options for LaTeX output ------------------------------------------------- + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]) +latex_documents = [ + ( + "index", + "PloneRegistryDocumentation.tex", + "@plone/registry Documentation", + "Plone Community", + "manual", + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +latex_logo = "_static/logo_2x.png" + + +# An extension that allows replacements for code blocks that +# are not supported in `rst_epilog` or other substitutions. +# https://stackoverflow.com/a/56328457/2214933 +def source_replace(app, docname, source): + result = source[0] + for key in app.config.source_replacements: + result = result.replace(key, app.config.source_replacements[key]) + source[0] = result + + +# Dict of replacements. +source_replacements = { + "{NVM_VERSION}": "0.39.5", +} + + +def setup(app): + app.add_config_value("source_replacements", {}, True) + app.connect("source-read", source_replace) diff --git a/packages/registry/docs/how-to-guides/access-registry.md b/packages/registry/docs/how-to-guides/access-registry.md new file mode 100644 index 0000000000..cbe6923633 --- /dev/null +++ b/packages/registry/docs/how-to-guides/access-registry.md @@ -0,0 +1,20 @@ +--- +myst: + html_meta: + "description": "How to access the configuration registry in @plone/registry" + "property=og:description": "How to access the configuration registry in @plone/registry" + "property=og:title": "Access the configuration registry" + "keywords": "@plone/registry, registry, configuration, guide" +--- + +# Access the configuration registry + +You can access the configuration registry as follows. + +```ts +import config from '@plone/registry' + +const blocksConfig = config.blocks.blocksConfig +``` + +This method assumes that either you previously created a `blocksConfig` key in `blocks` in your add-on, or another add-on sets it. diff --git a/packages/registry/docs/how-to-guides/instantiate-registry.md b/packages/registry/docs/how-to-guides/instantiate-registry.md new file mode 100644 index 0000000000..f915e983fe --- /dev/null +++ b/packages/registry/docs/how-to-guides/instantiate-registry.md @@ -0,0 +1,56 @@ +--- +myst: + html_meta: + "description": "How to instantiate the registry in @plone/registry" + "property=og:description": "How to instantiate the registry in @plone/registry" + "property=og:title": "Instantiate the registry" + "keywords": "@plone/registry, registry, instantiate" +--- + +# Instantiate the registry + +The registry is instantiated in the context of your app folder. +It gets your app folder path as argument. + +```js +import path from 'path'; +import { AddonRegistry } from '@plone/registry/addon-registry'; + +const appRootPath = path.resolve('.'); +const { registry } = AddonRegistry.init(appRootPath) +``` + +You have full access to the add-on registry API in the `registry` object. + +By default, you also get these objects after calling `init`. + +```js +const { registry, addons, theme, shadowAliases } = AddonRegistry.init(appRootPath) +``` + +This can be useful for configuring your build process. + + +## Initialization + +By default, the configuration registry is empty. +It only contains the base object keys which are required for it to work properly. +These are the keys present on initialization. +The optional keys are excluded. + +```ts +export type ConfigData = { + settings: SettingsConfig | Record; + blocks: BlocksConfig | Record; + views: ViewsConfig | Record; + widgets: WidgetsConfig | Record; + addonReducers?: AddonReducersConfig; + addonRoutes?: AddonRoutesConfig; + slots: SlotsConfig | Record; + components: ComponentsConfig | Record; + utilities: UtilitiesConfig | Record; + experimental?: ExperimentalConfig; +}; +``` + +In the context of a Volto app, the registry gets initialized by Volto by default. diff --git a/packages/registry/docs/how-to-guides/register-an-add-on.md b/packages/registry/docs/how-to-guides/register-an-add-on.md new file mode 100644 index 0000000000..874b35471d --- /dev/null +++ b/packages/registry/docs/how-to-guides/register-an-add-on.md @@ -0,0 +1,94 @@ +--- +myst: + html_meta: + "description": "How to register an add-on in @plone/registry" + "property=og:description": "How to register an add-on in @plone/registry" + "property=og:title": "Register an add-on" + "keywords": "@plone/registry, registry, add-on" +--- + +# Register an add-on + +You have two ways available to register an add-on in your app. +You can do so either through the `addons` key in your {file}`package.json` or by using a configuration file. + +```{note} +Using a configuration file is useful when you want to add some logic to the `addons` list, if you want it to be dynamic. +For example, you can make the list dynamic given an environment variable. +``` + + +## Via `addons` key in `package.json` + +The following code sample shows how to register your add-on in your app through the `addons` key in your {file}`package.json`. + +```json +{ + "name": "my-app-project", + "addons": [ + "acme-volto-foo-addon", + "@plone/some-addon", + "collective-another-volto-addon" + ] +} +``` + +The add-ons are registered in the order they are found in the `addons` key. +The last add-on takes precedence over the others. +This means that if you configure something in `acme-volto-foo-addon`, then the same thing later in `collective-another-volto-addon`, the latter configured thing will win and its configuration will be applied. + +All add-ons should set the value for the `main` entry module, such as `src/index.js`, in {file}`package.json`. +This function should have a signature of `config => config`. +That is, it should take the configuration registry object and return it, possibly mutated or changed. + +```ts +import type { ConfigType } from '@plone/registry' + +export default function applyConfig(config: ConfigType) { + return config +}; +``` + +The `addons` key ensures the add-on's main default export function is executed, being passed the configuration registry. + + +## Via a configuration file + +The configuration file can be provided via an environment variable. +You can use one of these two environment variables. + +- `REGISTRYCONFIG` +- `VOLTOCONFIG` + +The value of the environment variable must point to a file that exists relative to the app folder, that is, the one you pass to the instantiation of the add-on registry. +You can also pass the full path of the file. + +For example, if your configuration file is named {file}`my-add-on-registry-config.js` and is located at the root of your add-on package, you would set your environment variable as shown. + +```shell +set REGISTRYCONFIG="my-add-on-registry.config.js" +``` + +```{note} +This is useful when you want to provide different `addon` configuration files under different scenarios. +``` + +If the file that you specify in the environment variable exists, then `@plone/registry` uses it to configure your add-on. +If it does not exist, then `@plone/registry` looks for the configuration file in the following locations in the root of your app in order. +The first found configuration file wins. + +- {file}`registry.config.js` +- {file}`volto.config.js` + +This is an example of a configuration file. +You must define it in [CommonJS](https://en.wikipedia.org/wiki/CommonJS) format. + +```js +module.exports = { + addons: ['my-volto-config-addon'], +}; +``` + +If your app is in ESM (`"type": "module"` in {file}`package.json`), then you should use the `.cjs` suffix for the configuration file to mark it as a proper `CommonJS` file. + +If `@plone/registry` finds no configuration file, then it only relies on the configuration, if any, in the `addons` key in {file}`package.json`. diff --git a/packages/registry/docs/how-to-guides/register-and-retrieve-components.md b/packages/registry/docs/how-to-guides/register-and-retrieve-components.md new file mode 100644 index 0000000000..08e8623e0b --- /dev/null +++ b/packages/registry/docs/how-to-guides/register-and-retrieve-components.md @@ -0,0 +1,114 @@ +--- +myst: + html_meta: + "description": "How to register, retrieve, adapt, and load components in @plone/registry" + "property=og:description": "How to register, retrieve, adapt, and load components in @plone/registry" + "property=og:title": "Register and retrieve components" + "keywords": "@plone/registry, registry, register, retrieve, components" +--- + +# Register and retrieve components + +This section of the documentation describes how to register, retrieve, adapt, and load components. + + +## Register components by name + +You can register components by name, typically from an add-on or project configuration, using `config.registerComponent`. + +```js +import MyToolbarComponent from './MyToolbarComponent' + +config.registerComponent({ + name: 'Toolbar', + component: MyToolbarComponent, +}); +``` + + +## Retrieve a component from the component registry + +You can programmatically retrieve a component from the component registry using `config.getComponent`. + +```js +const Toolbar = config.getComponent('Toolbar').component +``` + +Alternatively, you can retrieve a component by using the convenience component `Component`, if you want to use it in JSX code directly. + +```jsx +import Component from '@plone/volto/components/theme/Component/Component'; + + +``` + +Note that you can pass `props` down to the retrieved component. + + +## Adapt the component + +You can register components, then retrieve them, given a list of modifiers using the `dependencies` array. + +```js +import MyTeaserNewsItemComponent from './MyTeaserNewsItemComponent' + +config.registerComponent({ + name: 'Teaser', + component: MyTeaserNewsItemComponent, + dependencies: ['News Item'], + }); +``` + +To retrieve the component, use `getComponent`. + +```js +config.getComponent({ + name: 'Teaser', + dependencies: ['News Item'], + }).component +``` + +If you have a single dependency, you can use a string instead of an array. + +You can have both, either with or without dependencies. + +```js +import MyTeaserDefaultComponent from './MyTeaserDefaultComponent' +import MyTeaserNewsItemComponent from './MyTeaserNewsItemComponent' + +config.registerComponent({ + name: 'Teaser', + component: MyTeaserDefaultComponent, + }); + +config.registerComponent({ + name: 'Teaser', + component: MyTeaserNewsItemComponent, + dependencies: 'News Item', + }); +``` + +After you register them, you can retrieve both or either of them, depending on your use case. +In the next example, given a content type value coming from the `content` prop, you can retrieve them. + +```jsx + +``` + + +## Lazy load components + +```{todo} +Test it properly. +``` + +You can lazy load the component in the registry, too, if you need it. + +```js +const MyTeaserDefaultComponent = lazy(()=> import(./MyTeaserDefaultComponent)) + +config.registerComponent({ + name: 'Teaser', + component: MyTeaserDefaultComponent, + }); +``` diff --git a/packages/registry/docs/how-to-guides/register-and-retrieve-utilities.md b/packages/registry/docs/how-to-guides/register-and-retrieve-utilities.md new file mode 100644 index 0000000000..d58fc92d4c --- /dev/null +++ b/packages/registry/docs/how-to-guides/register-and-retrieve-utilities.md @@ -0,0 +1,113 @@ +--- +myst: + html_meta: + "description": "How to register and retrieve utilities in @plone/registry" + "property=og:description": "How to register and retrieve utilities in @plone/registry" + "property=og:title": "Register and retrieve utilities" + "keywords": "@plone/registry, registry, utilities, register, retrieve" +--- + +# Register and retrieve utilities + +This section of the documentation describes how to register and retrieve utilities. + + +## Register utilities by `name` and `type` + +You can register a utility using `config.registerUtility` by using its specific `name` and `type` arguments. + +```js +config.registerUtility({ + name: 'url', + type: 'validator', + method: () => 'this is a simple validator utility', +}); +``` + +For utilities of the same `type`, you can register different `name` utilities. + +```js +config.registerUtility({ + name: 'url', + type: 'validator', + method: () => 'this is a simple URL validator utility', +}); + +config.registerUtility({ + name: 'email', + type: 'validator', + method: () => 'this is a simple email validator utility', +}); +``` + +However, if you register two utilities under the same `name`, then the latter will override the former. +Thus you can override existing utilities in your add-ons. + +```js +config.registerUtility({ + name: 'url', + type: 'validator', + method: () => 'this is a simple URL validator utility', +}); + +config.registerUtility({ + name: 'url', + type: 'validator', + method: () => 'this registered URL validator utility is the last defined, and therefore overrides all previous utilities with the same `name`', +}); +``` + + +## Register utilities using a `dependencies` object + +It is possible to register utilities using a `dependencies` object. +This is useful to further specify the utility. + +```js +config.registerUtility({ + name: 'email', + type: 'validator', + dependencies: { fieldType: 'email' }, + method: () => 'this is an email validator utility with dependencies for email', +}); +``` + + +## Retrieve a specific utility + +You can retrieve one specific utility using `config.getUtility`, given the `name` and `type`. + +```js +config.getUtility({ name: 'url', type: 'validator' }).method() +``` + +You can do the same using a `dependencies` object. + +```js +config.getUtility({ + name: 'email', + dependencies: { fieldType: 'string' }, + type: 'validator', +}).method() +``` + + +### Retrieve groups of utilities + +You can retrieve all utilities registered under the same `type`. + +```js +config.getUtilities({ type: 'validator' }) +``` + +You can do the same using a `dependencies` object. + +```js +config.getUtilities({ + type: 'validator', + dependencies: { fieldType: 'string' }, +}).length +``` + +This is useful when building pluggable systems, so you can query all the utilities present in the registry. +For example, retrieve all validator utilities with the `fieldType` of `string`. diff --git a/packages/registry/docs/how-to-guides/shadow-a-component.md b/packages/registry/docs/how-to-guides/shadow-a-component.md new file mode 100644 index 0000000000..90179a1f04 --- /dev/null +++ b/packages/registry/docs/how-to-guides/shadow-a-component.md @@ -0,0 +1,61 @@ +--- +myst: + html_meta: + "description": "How to shadow a component or module in @plone/registry" + "property=og:description": "How to shadow a component or module in @plone/registry" + "property=og:title": "Shadow a component or module" + "keywords": "@plone/registry, registry, shadow, component" +--- + +# Shadow a component or module + +Component or module shadowing is a technique that allows you to define an alternative module for a specific module. +You normally would want to override a module from another add-on. +This add-on should not be transpiled. + +This technique relies on the `resolve.alias` feature of the bundlers, so the module is effectively being replaced by the alternative one supplied. +You will need to modify some imports in the alternative module to comply with the new placement and convert relative imports to absolute ones. + +To override the component, first, you should identify the component you want to shadow in the package. +Then, replicate the same folder structure that the original component has in the source code and place it inside the `customizations` folder of your add-on. + +Start by using the name of the package you want to shadow. +If the package has a namespace, then use a folder to define it. + +To identify a component to shadow, you can use several approaches. +The primary method uses [React Developer Tools](https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi). +You can inspect the app and find out the name of the component (the name +of the tag), then search for it in the source code. +Alternatively, you can browse the contents of the source package you want to shadow by searching for it inside {file}`node_modules` of your app folder. + + +## Example: customize the `Logo` resource + +To replace the `Logo` resource, your folder structure needs to match the folder structure of the package in the `customizations` folder. +The `Logo` resource is located in the `@plone/slots` package in the {file}`components/Logo/Logo.svg` file. +├── slots + +```text +node_modules +└── @plone + └── slots + └── components + └── Logo.svg +``` + +The structure inside your `customizations` of the component shadowing the original should be {file}`src/customizations/@plone/slots/components/Logo/Logo.svg`. + +```text +src +└── customizations + └── @plone + └── slots + └── components + └── Logo.svg +``` + +```{warning} +When upgrading add-ons in your project, it's important to review any shadowed components from the updated add-on. +Changes in the add-on's public API could potentially break your application. +Ensure that your shadowed components are updated to align with the new specifications of the original module. +``` diff --git a/packages/registry/docs/index.md b/packages/registry/docs/index.md new file mode 100644 index 0000000000..ad433cc9b6 --- /dev/null +++ b/packages/registry/docs/index.md @@ -0,0 +1,43 @@ +--- +myst: + html_meta: + "description": "@plone/registry provides support for building an add-on and configuration registry with infrastructure for JavaScript and TypeScript-based apps." + "property=og:description": "@plone/registry provides support for building an add-on and configuration registry with infrastructure for JavaScript and TypeScript-based apps." + "property=og:title": "@plone/registry" + "keywords": "@plone/registry, registry, add-on, configuration, component, utility, JavaScript, TypeScript, app" +--- + +# `@plone/registry` + +`@plone/registry` provides support for building an add-on and configuration registry with infrastructure for JavaScript and TypeScript-based apps. + +As a developer when you build an app, regardless of the framework and technologies used, it's a one-off app. +That means you have to build something that has very specific requirements, behavior, and look and feel. + +Sometimes you need to build something generic that is pluggable and extensible. +In the JavaScript and TypeScript ecosystem, this is often quite complex, and the existing frameworks do not provide the means to do this. +`@plone/registry` helps developers extend their apps in a pluggable way. + + +```{toctree} +:maxdepth: 1 +:hidden: +:caption: How-to guides +how-to-guides/instantiate-registry +how-to-guides/register-an-add-on +how-to-guides/access-registry +how-to-guides/register-and-retrieve-components +how-to-guides/register-and-retrieve-utilities +how-to-guides/shadow-a-component +``` + + +```{toctree} +:maxdepth: 1 +:hidden: +:caption: Conceptual guides +conceptual-guides/add-on-registry +conceptual-guides/configuration-registry +conceptual-guides/component-registry +conceptual-guides/utility-registry +``` diff --git a/packages/registry/docs/robots.txt b/packages/registry/docs/robots.txt new file mode 100644 index 0000000000..fa4599903a --- /dev/null +++ b/packages/registry/docs/robots.txt @@ -0,0 +1,8 @@ +# Disallow all user agents from the following directories and files +User-agent: * +Disallow: /_sources/ +Disallow: /.doctrees/ +Disallow: /*.txt$ +Disallow: /.buildinfo$ +Disallow: /objects.inv$ +Sitemap: https://plone-registry.readthedocs.io/sitemap.xml diff --git a/packages/registry/news/6399.breaking b/packages/registry/news/6399.breaking new file mode 100644 index 0000000000..d0315478f6 --- /dev/null +++ b/packages/registry/news/6399.breaking @@ -0,0 +1,6 @@ +Moved the package to ESM and refactored the add-on registry scripts to TypeScript. @sneridagh +Breaking: +- For maximum compatibility with CommonJS builds, the default exports have been moved to named exports. +- The modules affected are now built, and the import paths have changed, too. +- These changes force the modification in imports in a couple of files. +Please see the [Upgrade Guide](https://6.docs.plone.org/volto/upgrade-guide/index.html). diff --git a/packages/registry/news/6399.feature b/packages/registry/news/6399.feature new file mode 100644 index 0000000000..1a5f49ddbc --- /dev/null +++ b/packages/registry/news/6399.feature @@ -0,0 +1 @@ +Added an experimental Vite plugin. @sneridagh diff --git a/packages/registry/package.json b/packages/registry/package.json index 5b4d3e8284..78f14702d7 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -31,16 +31,36 @@ "publishConfig": { "access": "public" }, + "type": "module", "source": "src/index.ts", - "main": "dist/main.js", - "module": "dist/module.js", + "main": "dist/index.cjs", + "module": "dist/index.js", "types": "dist/types.d.ts", "exports": { - "./src/*": "./src/*.js", + "./src/*": "./src/*.cjs", + "./addon-registry": { + "require": "./dist/cjs/addon-registry.cjs", + "import": "./dist/esm/addon-registry.js", + "types": "./dist/esm/addon-registry.d.ts" + }, + "./create-addons-loader": { + "require": "./dist/cjs/create-addons-loader.cjs", + "import": "./dist/esm/create-addons-loader.js", + "types": "./dist/esm/create-addons-loader.d.ts" + }, + "./create-theme-loader": { + "require": "./dist/cjs/create-theme-loader.cjs", + "import": "./dist/esm/create-theme-loader.js", + "types": "./dist/esm/create-theme-loader.d.ts" + }, + "./vite-plugin": { + "import": "./vite-plugin.js", + "types": "./vite-plugin.d.ts" + }, ".": { "types": "./dist/types.d.ts", - "import": "./dist/module.js", - "require": "./dist/main.js" + "import": "./dist/index.js", + "require": "./dist/index.cjs" } }, "targets": { @@ -50,9 +70,12 @@ }, "scripts": { "watch": "parcel watch", - "build": "parcel build", - "build:force": "parcel build --no-cache", + "build": "parcel build && pnpm build:node:esm && pnpm build:node:cjs", + "build:force": "rm -rf dist && parcel build --no-cache && pnpm build:node:esm && pnpm build:node:cjs", + "build:node:esm": "tsc --project tsconfig.node.json || true", + "build:node:cjs": "tsc --project tsconfig.node.json --module commonjs --moduleResolution Node --outDir dist/cjs || true && make rename-to-cjs", "test": "vitest", + "test:debug": "vitest --inspect-brk --no-file-parallelism registry", "dry-release": "release-it --dry-run", "release": "release-it", "release-major-alpha": "release-it major --preRelease=alpha", @@ -71,20 +94,26 @@ "crypto-random-string": "3.2.0", "debug": "4.3.2", "dependency-graph": "0.10.0", - "glob": "7.1.6" + "glob": "^10.4.5", + "tmp": "0.2.1" }, "devDependencies": { "@parcel/packager-ts": "^2.12.0", "@parcel/transformer-typescript-types": "^2.12.0", "@plone/types": "workspace:*", + "@types/debug": "^4.1.12", + "@types/glob": "^8.1.0", + "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/tmp": "^0.2.6", "parcel": "^2.12.0", "react": "^18.2.0", "react-dom": "^18.2.0", "release-it": "16.2.1", "tsconfig": "workspace:*", "typescript": "^5.6.3", + "vite": "^5.4.8", "vitest": "^2.1.3" } } diff --git a/packages/registry/src/addon-registry.js b/packages/registry/src/addon-registry/addon-registry.ts similarity index 65% rename from packages/registry/src/addon-registry.js rename to packages/registry/src/addon-registry/addon-registry.ts index 26ae57555c..576d439d4d 100644 --- a/packages/registry/src/addon-registry.js +++ b/packages/registry/src/addon-registry/addon-registry.ts @@ -1,64 +1,120 @@ /* eslint no-console: 0 */ -const glob = require('glob').sync; -const path = require('path'); -const fs = require('fs'); -const debug = require('debug')('shadowing'); -const { DepGraph } = require('dependency-graph'); - -function getPackageBasePath(base) { +import { globSync as glob } from 'glob'; +import path from 'path'; +import fs from 'fs'; +import _debug from 'debug'; +import { DepGraph } from 'dependency-graph'; + +const debug = _debug('shadowing'); + +export type Package = { + name: string; + version: string; + isPublishedPackage: boolean; + isRegisteredAddon: boolean; + modulePath: string; + packageJson: string; + basePath?: string; + tsConfigPaths?: [string, any] | null; + addons: Array; + razzleExtender?: string; + eslintExtender?: string; +}; +type VoltoConfigJS = { + addons: Array; + theme: string; +}; +type Aliases = Record; +type AliasesObject = { find: string; replacement: string }[]; +type CoreAddons = { [x: string]: { package: string } }; +type PackageJsonObject = { + type: 'module' | 'commonjs'; + addons: Array; + coreAddons: CoreAddons; + theme: string; + customizationPaths: string[]; +}; + +type flatAliases = Record; + +type AddonRegistryGet = { + /** The ordered list of addons */ + addons: Array; + /** The theme name */ + theme: string; + /** The customizations (shadows) aliases */ + shadowAliases: AliasesObject; + /** The add-ons aliases - Only for Volto add-ons for which code lives inside `src` */ + addonAliases: AliasesObject; +}; + +function getPackageBasePath(base: string) { while (!fs.existsSync(`${base}/package.json`)) { base = path.join(base, '../'); } return path.resolve(base); } -function fromEntries(pairs) { - const res = {}; +function fromEntries(pairs: [string, any][]) { + const res: { [key: string]: any } = {}; pairs.forEach((p) => { res[p[0]] = p[1]; }); return res; } -function buildDependencyGraph(addons, extractDependency) { +function flatAliasesToObject(flatAliases: flatAliases): AliasesObject { + return Object.entries(flatAliases).map(([key, value]) => ({ + find: key, + replacement: value, + })); +} + +function buildDependencyGraph( + addons: Array, + extractDependency: (name: string) => Array, +) { // getAddonsLoaderChain - const graph = new DepGraph({ circular: true }); + const graph = new DepGraph({ circular: true }); graph.addNode('@root'); const seen = ['@root']; - const stack = [['@root', addons]]; + const stack: Array<[string, Array]> = [['@root', addons]]; while (stack.length > 0) { - const [pkgName, addons] = stack.shift(); - if (!graph.hasNode(pkgName)) { - graph.addNode(pkgName, []); - } - - if (!seen.includes(pkgName)) { - stack.push([pkgName, extractDependency(pkgName)]); - seen.push(pkgName); - } - - addons.forEach((loaderString) => { - const [name, extra] = loaderString.split(':'); - if (!graph.hasNode(name)) { - graph.addNode(name, []); + const [pkgName, addons] = stack.shift() || []; + if (pkgName && addons) { + if (!graph.hasNode(pkgName)) { + graph.addNode(pkgName, []); } - const data = graph.getNodeData(name) || []; - if (extra) { - extra.split(',').forEach((funcName) => { - if (!data.includes(funcName)) data.push(funcName); - }); + if (!seen.includes(pkgName)) { + stack.push([pkgName, extractDependency(pkgName)]); + seen.push(pkgName); } - graph.setNodeData(name, data); - graph.addDependency(pkgName, name); + addons.forEach((loaderString) => { + const [name, extra] = loaderString.split(':'); + if (!graph.hasNode(name)) { + graph.addNode(name, []); + } - if (!seen.includes(name)) { - stack.push([name, extractDependency(name)]); - } - }); + const data = graph.getNodeData(name) || []; + if (extra) { + extra.split(',').forEach((funcName) => { + // @ts-expect-error TODO: fix this + if (!data.includes(funcName)) data.push(funcName); + }); + } + graph.setNodeData(name, data); + + graph.addDependency(pkgName, name); + + if (!seen.includes(name)) { + stack.push([name, extractDependency(name)]); + } + }); + } } return graph; @@ -68,7 +124,7 @@ function buildDependencyGraph(addons, extractDependency) { * Given an addons loader string, it generates an addons loader string with * a resolved chain of dependencies */ -function getAddonsLoaderChain(graph) { +function getAddonsLoaderChain(graph: DepGraph) { return graph.dependenciesOf('@root').map((name) => { const extras = graph.getNodeData(name) || [].join(','); return extras.length ? `${name}:${extras}` : name; @@ -92,26 +148,29 @@ function getAddonsLoaderChain(graph) { * addons to customize the webpack configuration) * */ -class AddonConfigurationRegistry { - constructor(projectRootPath) { - const packageJson = (this.packageJson = require( - path.join(projectRootPath, 'package.json'), +class AddonRegistry { + public packageJson: PackageJsonObject; + public voltoConfigJS: VoltoConfigJS; + public projectRootPath: string; + public isVoltoProject: boolean; + public voltoPath: string; + public coreAddons: CoreAddons; + public resultantMergedAddons: Array; + public addonNames: Array; + public packages: Record; + public customizations: any; + public theme: any; + public dependencyGraph: DepGraph; + + constructor(projectRootPath: string) { + const packageJson = (this.packageJson = JSON.parse( + fs.readFileSync(path.join(projectRootPath, 'package.json'), { + encoding: 'utf-8', + }), )); - this.voltoConfigJS = {}; + // Loads the dynamic config, if any - if (process.env.VOLTOCONFIG) { - if (fs.existsSync(path.resolve(process.env.VOLTOCONFIG))) { - const voltoConfigPath = path.resolve(process.env.VOLTOCONFIG); - console.log(`Using volto.config.js in: ${voltoConfigPath}`); - this.voltoConfigJS = require(voltoConfigPath); - } - } else if (fs.existsSync(path.join(projectRootPath, 'volto.config.js'))) { - this.voltoConfigJS = require( - path.join(projectRootPath, 'volto.config.js'), - ); - } else { - this.voltoConfigJS = {}; - } + this.voltoConfigJS = this.getRegistryConfig(projectRootPath); this.projectRootPath = projectRootPath; this.isVoltoProject = packageJson.name !== '@plone/volto'; @@ -123,15 +182,21 @@ class AddonConfigurationRegistry { this.coreAddons = packageJson.name === '@plone/volto' ? packageJson.coreAddons || {} - : require(`${getPackageBasePath(this.voltoPath)}/package.json`) - .coreAddons || {}; + : JSON.parse( + fs.readFileSync( + `${getPackageBasePath(this.voltoPath)}/package.json`, + 'utf-8', + ), + ).coreAddons || {}; this.resultantMergedAddons = [ ...(packageJson.addons || []), ...(this.voltoConfigJS.addons || []), ]; - this.addonNames = this.resultantMergedAddons.map((s) => s.split(':')[0]); + this.addonNames = this.resultantMergedAddons.map( + (s: string) => s.split(':')[0], + ); this.packages = {}; this.customizations = new Map(); @@ -161,22 +226,77 @@ class AddonConfigurationRegistry { this.initAddonExtenders(); } + public get(): AddonRegistryGet { + return { + addons: this.getAddonDependencies(), + theme: this.theme, + addonAliases: flatAliasesToObject(this.getResolveAliases()), + shadowAliases: flatAliasesToObject(this.getAddonCustomizationPaths()), + }; + } + + public static init(projectRootPath: string) { + const registry = new AddonRegistry(projectRootPath); + return { + registry, + addons: registry.getAddonDependencies(), + theme: registry.theme, + shadowAliases: flatAliasesToObject(registry.getAddonCustomizationPaths()), + }; + } + + isESM = () => this.packageJson.type === 'module'; + + getRegistryConfig(projectRootPath: string) { + let config: VoltoConfigJS = { + addons: [], + theme: '', + }; + const CONFIGMAP = { + REGISTRYCONFIG: this.isESM() + ? 'registry.config.cjs' + : 'registry.config.js', + VOLTOCONFIG: this.isESM() ? 'volto.config.cjs' : 'volto.config.js', + }; + + for (const key in CONFIGMAP) { + if (process.env[key]) { + const resolvedPath = path.resolve(process.env[key]); + if (fs.existsSync(resolvedPath)) { + const voltoConfigPath = resolvedPath; + console.log(`Using configuration file in: ${voltoConfigPath}`); + config = require(voltoConfigPath); + break; + } + } else if (fs.existsSync(path.join(projectRootPath, CONFIGMAP[key]))) { + config = require(path.join(projectRootPath, CONFIGMAP[key])); + break; + } + } + + return config; + } + /** * Gets the `tsconfig.json` `compilerOptions.baseUrl` and `compilerOptions.paths` * Returns a tuple `[baseUrl, pathsConfig]` * */ - getTSConfigPaths(rootPath = this.projectRootPath) { - let configFile; + getTSConfigPaths( + rootPath = this.projectRootPath, + ): [string, Record | undefined] { + let configFile: string | undefined; if (fs.existsSync(`${rootPath}/tsconfig.json`)) configFile = `${rootPath}/tsconfig.json`; else if (fs.existsSync(`${rootPath}/jsconfig.json`)) configFile = `${rootPath}/jsconfig.json`; - let pathsConfig; - let baseUrl; + let pathsConfig: Record | undefined; + let baseUrl: string = ''; if (configFile) { - const jsConfig = require(configFile).compilerOptions; + const jsConfig = JSON.parse( + fs.readFileSync(configFile, 'utf-8'), + ).compilerOptions; pathsConfig = jsConfig.paths; baseUrl = jsConfig.baseUrl; } @@ -209,28 +329,31 @@ class AddonConfigurationRegistry { * Given an add-on name, it registers it as a development package * */ - initDevelopmentPackage(name) { + initDevelopmentPackage(name: string) { const [baseUrl, pathsConfig] = this.getTSConfigPaths(); - const packagePath = `${this.projectRootPath}/${baseUrl}/${pathsConfig[name][0]}`; - const packageJsonPath = `${getPackageBasePath(packagePath)}/package.json`; - const innerAddons = require(packageJsonPath).addons || []; - const innerAddonsNormalized = innerAddons.map((s) => s.split(':')[0]); - if (this.addonNames.includes(name) && innerAddonsNormalized.length > 0) { - innerAddonsNormalized.forEach((name) => { - if (!this.addonNames.includes(name)) this.addonNames.push(name); - }); - } - const pkg = { - modulePath: packagePath, - packageJson: packageJsonPath, - version: require(packageJsonPath).version, - isPublishedPackage: false, - isRegisteredAddon: this.addonNames.includes(name), - name, - addons: require(packageJsonPath).addons || [], - }; + if (pathsConfig && pathsConfig.hasOwnProperty(name)) { + const packagePath = `${this.projectRootPath}/${baseUrl}/${pathsConfig[name][0]}`; + const packageJsonPath = `${getPackageBasePath(packagePath)}/package.json`; + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + const innerAddons: Array = packageJson.addons || []; + const innerAddonsNormalized = innerAddons.map((s) => s.split(':')[0]); + if (this.addonNames.includes(name) && innerAddonsNormalized.length > 0) { + innerAddonsNormalized.forEach((name) => { + if (!this.addonNames.includes(name)) this.addonNames.push(name); + }); + } + const pkg = { + modulePath: packagePath, + packageJson: packageJsonPath, + version: packageJson.version, + isPublishedPackage: false, + isRegisteredAddon: this.addonNames.includes(name), + name, + addons: packageJson.addons || [], + }; - this.packages[name] = Object.assign(this.packages[name] || {}, pkg); + this.packages[name] = Object.assign(this.packages[name] || {}, pkg); + } } /** @@ -243,11 +366,11 @@ class AddonConfigurationRegistry { this.addonNames.forEach(this.initPublishedPackage.bind(this)); } - initPublishedPackage(name) { + initPublishedPackage(name: string) { // I am in the paths list, if so, register it as a development package // instead than a published one const [, pathsConfig] = this.getTSConfigPaths(); - if (pathsConfig.hasOwnProperty(name)) { + if (pathsConfig && pathsConfig.hasOwnProperty(name)) { return this.initDevelopmentPackage(name); } @@ -255,10 +378,10 @@ class AddonConfigurationRegistry { const resolved = require.resolve(name, { paths: [this.projectRootPath] }); const basePath = getPackageBasePath(resolved); const packageJson = path.join(basePath, 'package.json'); - const pkg = require(packageJson); + const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf-8')); const main = pkg.main || 'src/index.js'; const modulePath = path.dirname(require.resolve(`${basePath}/${main}`)); - const innerAddons = pkg.addons || []; + const innerAddons: Array = pkg.addons || []; const innerAddonsNormalized = innerAddons.map((s) => s.split(':')[0]); if (this.addonNames.includes(name) && innerAddonsNormalized.length > 0) { innerAddonsNormalized.forEach((name) => { @@ -290,7 +413,7 @@ class AddonConfigurationRegistry { } // An add-on from the ADDONS env var can only be a published one - initAddonFromEnvVar(name) { + initAddonFromEnvVar(name: string) { const normalizedAddonName = name.split(':')[0]; this.initPublishedPackage(normalizedAddonName); } @@ -341,7 +464,10 @@ class AddonConfigurationRegistry { } getCustomThemeAddons() { - const customThemeAddonsInfo = { + const customThemeAddonsInfo: { + variables: string[]; + main: string[]; + } = { variables: [], main: [], }; @@ -372,11 +498,11 @@ class AddonConfigurationRegistry { /** * Returns a list of aliases given the defined paths in `tsconfig.json` */ - getAliasesFromTSConfig(basePath, tsConfig) { + getAliasesFromTSConfig(basePath: string, tsConfig: [string, any]) { const [baseUrl, options] = tsConfig; const fullPathsPath = baseUrl ? `${basePath}/${baseUrl}` : basePath; - let aliases = {}; + const aliases: { [x: string]: string } = {}; Object.keys(options || {}).forEach((item) => { const name = item.replace(/\/\*$/, ''); // webpack5 allows arrays here, fix later @@ -397,12 +523,10 @@ class AddonConfigurationRegistry { * defined in the `tsconfig.json` files of the add-ons. */ getResolveAliases() { - const pairs = [ - ...Object.keys(this.packages).map((o) => [ - o, - this.packages[o].modulePath, - ]), - ]; + const pairs: [string, string][] = Object.keys(this.packages).map((o) => [ + o, + this.packages[o].modulePath, + ]); let aliasesFromTSPaths = {}; Object.keys(this.packages).forEach((o) => { @@ -410,7 +534,7 @@ class AddonConfigurationRegistry { aliasesFromTSPaths = { ...aliasesFromTSPaths, ...this.getAliasesFromTSConfig( - this.packages[o].basePath, + this.packages[o].basePath || '', this.packages[o].tsConfigPaths, ), }; @@ -435,21 +559,25 @@ class AddonConfigurationRegistry { * convention, we use a folder called "volto" inside customizations folder * and separate folder for each addon, identified by its addon (package) name. */ - getCustomizationPaths(packageJson, rootPath) { - const aliases = {}; + getCustomizationPaths(packageJson: PackageJsonObject, rootPath: string) { + const aliases: Aliases = {}; let { customizationPaths } = packageJson; if (!customizationPaths) { - customizationPaths = ['src/customizations']; + customizationPaths = ['src/customizations', 'customizations']; } customizationPaths.forEach((customizationPath) => { customizationPath = customizationPath.endsWith('/') ? customizationPath.slice(0, customizationPath.length - 1) : customizationPath; const base = path.join(rootPath, customizationPath); - const reg = []; - - // All registered addon packages (in tsconfig.json/jsconfig.json and package.json:addons) - // can be customized + const reg: Array<{ + customPath: string; + sourcePath: string; + name: string; + }> = []; + + // All registered addon packages (in tsconfig.json/jsconfig.json and + // package.json:addons) can be customized by other addons Object.values(this.packages).forEach((addon) => { const { name, modulePath } = addon; if (fs.existsSync(path.join(base, name))) { @@ -491,12 +619,14 @@ class AddonConfigurationRegistry { glob( `${customPath}/**/*.*(svg|png|jpg|jpeg|gif|ico|less|js|jsx|ts|tsx)`, ).map((filename) => { - function changeFileExtension(filePath) { + function changeFileExtension(filePath: string) { // Extract the current file extension - const currentExtension = filePath.split('.').pop(); + const currentExtension = filePath.split('.').pop() || ''; // Define the mapping between file extensions - const extensionMapping = { + const extensionMapping: { + [key: string]: string; + } = { jsx: 'tsx', tsx: 'jsx', js: 'ts', @@ -523,7 +653,7 @@ class AddonConfigurationRegistry { * * @param {*} filePath */ - function simpleSwapFileExtension(filePath) { + function simpleSwapFileExtension(filePath: string) { // Extract the current file extension const currentExtension = filePath.split('.').pop(); return filePath.replace(`.${currentExtension}`, '.jsx'); @@ -569,12 +699,12 @@ class AddonConfigurationRegistry { * `addons:volto-addonA,volto-addonB` */ getAddonCustomizationPaths() { - let aliases = {}; + let aliases: Aliases = {}; this.getAddons().forEach((addon) => { aliases = { ...aliases, ...this.getCustomizationPaths( - require(addon.packageJson), + JSON.parse(fs.readFileSync(addon.packageJson, 'utf-8')), getPackageBasePath(addon.modulePath), ), }; @@ -586,9 +716,13 @@ class AddonConfigurationRegistry { * Allow packages from addons set in env vars to customize Volto and other addons. * * Same as the above one, but specific for Volto addons coming from env vars + * + * This is no longer necessary in the pnpm setup, as all valid packages have to be + * released or declared as a workspace + * */ getAddonsFromEnvVarCustomizationPaths() { - let aliases = {}; + let aliases: Aliases = {}; if (process.env.ADDONS) { process.env.ADDONS.split(';').forEach((addon) => { const normalizedAddonName = addon.split(':')[0]; @@ -598,7 +732,10 @@ class AddonConfigurationRegistry { const packageJson = path.join(basePath, 'package.json'); aliases = { ...aliases, - ...this.getCustomizationPaths(require(packageJson), basePath), + ...this.getCustomizationPaths( + JSON.parse(fs.readFileSync(packageJson, 'utf-8')), + basePath, + ), }; } }); @@ -626,11 +763,11 @@ class AddonConfigurationRegistry { "@root" [color = red fillcolor=yellow style=filled] `; - let queue = ['@root']; - let name; + const queue = ['@root']; + let name: string; while (queue.length > 0) { - name = queue.pop(); + name = queue.pop() || ''; const deps = this.dependencyGraph.directDependenciesOf(name); for (let i = 0; i < deps.length; i++) { @@ -648,6 +785,4 @@ class AddonConfigurationRegistry { } } -module.exports = AddonConfigurationRegistry; -module.exports.getAddonsLoaderChain = getAddonsLoaderChain; -module.exports.buildDependencyGraph = buildDependencyGraph; +export { AddonRegistry, getAddonsLoaderChain, buildDependencyGraph }; diff --git a/packages/registry/src/create-addons-loader.js b/packages/registry/src/addon-registry/create-addons-loader.ts similarity index 66% rename from packages/registry/src/create-addons-loader.js rename to packages/registry/src/addon-registry/create-addons-loader.ts index 7eb5f55757..f03349693f 100644 --- a/packages/registry/src/create-addons-loader.js +++ b/packages/registry/src/addon-registry/create-addons-loader.ts @@ -1,13 +1,16 @@ -const fs = require('fs'); -const tmp = require('tmp'); -const cryptoRandomString = require('crypto-random-string'); +import fs from 'fs'; +import path from 'path'; +import tmp from 'tmp'; +import cryptoRandomString from 'crypto-random-string'; +import type { Package } from './addon-registry'; -const titleCase = (w) => w.slice(0, 1).toUpperCase() + w.slice(1, w.length); +const titleCase = (w: string) => + w.slice(0, 1).toUpperCase() + w.slice(1, w.length); /* * Transforms a package name to javascript variable name */ -function nameFromPackage(name) { +function nameFromPackage(name: string) { name = name.replace(/[@~./\\:\s]/gi, '') || cryptoRandomString({ length: 10, characters: 'abcdefghijk' }); @@ -21,19 +24,26 @@ function nameFromPackage(name) { * Creates a static file with code necessary to load the addons configuration * */ -function getAddonsLoaderCode(addons = [], addonsInfo) { +function getAddonsLoaderCode( + addons: string[], + addonsInfo: Package[], + loadProjectConfig?: boolean, +) { let buf = `/* This file is autogenerated. Don't change it directly. Instead, change the "addons" setting in your package.json file. */ -const projectConfigLoader = require('@root/config'); `; - let configsToLoad = [], + if (loadProjectConfig) { + buf += `const projectConfigLoader = require('@root/config');\n`; + } + + let configsToLoad: string[] = [], counter = 0; addons.forEach((addonConfigString) => { - let extras = []; + let extras: string[] | string[][] = []; // TODO: Improve this typing const addonConfigLoadInfo = addonConfigString.split(':'); const pkgName = addonConfigLoadInfo[0]; const defaultImport = nameFromPackage(pkgName); @@ -41,7 +51,6 @@ const projectConfigLoader = require('@root/config'); extras = addonConfigLoadInfo[1].split(','); } extras = extras.map((name) => [name, `${name}${counter++}`]); - const line = `import ${defaultImport}${ extras.length ? `, { ${extras @@ -77,8 +86,9 @@ const safeWrapper = (func) => (config) => { return res; } +${loadProjectConfig ? '' : 'const projectConfigLoader = false;'} const projectConfig = (config) => { - return typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; + return projectConfigLoader && typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; } const load = (config) => { @@ -96,21 +106,27 @@ export default load; return buf; } -module.exports = (addons, addonsInfo, { tempInProject } = {}) => { +export function createAddonsLoader( + addons: string[], + addonsInfo: Package[], + { + tempInProject, + loadProjectConfig = false, + }: { tempInProject?: boolean; loadProjectConfig?: boolean } = {}, +) { // Some frameworks do not allow to load code from outside the project. // the `tempInProject` allows to place it inside - let addonsLoaderPath; + let addonsLoaderPath: string; if (tempInProject) { - const path = require('path'); addonsLoaderPath = path.join(process.cwd(), 'src', '.addons-loader.js'); } else { addonsLoaderPath = tmp.tmpNameSync({ postfix: '.js' }); } - const code = getAddonsLoaderCode(addons, addonsInfo); + const code = getAddonsLoaderCode(addons, addonsInfo, loadProjectConfig); + // @ts-expect-error No clue why it's complaining fs.writeFileSync(addonsLoaderPath, Buffer.from(code)); return addonsLoaderPath; -}; +} -module.exports.getAddonsLoaderCode = getAddonsLoaderCode; -module.exports.nameFromPackage = nameFromPackage; +export { getAddonsLoaderCode, nameFromPackage }; diff --git a/packages/registry/src/create-theme-addons-loader.js b/packages/registry/src/addon-registry/create-theme-loader.ts similarity index 50% rename from packages/registry/src/create-theme-addons-loader.js rename to packages/registry/src/addon-registry/create-theme-loader.ts index 6b46257744..d5f4dbbc24 100644 --- a/packages/registry/src/create-theme-addons-loader.js +++ b/packages/registry/src/addon-registry/create-theme-loader.ts @@ -1,13 +1,14 @@ -const fs = require('fs'); -const tmp = require('tmp'); -const cryptoRandomString = require('crypto-random-string'); +import fs from 'fs'; +import tmp from 'tmp'; +import cryptoRandomString from 'crypto-random-string'; -const titleCase = (w) => w.slice(0, 1).toUpperCase() + w.slice(1, w.length); +const titleCase = (w: string) => + w.slice(0, 1).toUpperCase() + w.slice(1, w.length); /* * Transforms a package name to javascript variable name */ -function nameFromPackage(name) { +function nameFromPackage(name: string) { name = name.replace(/[@~./\\:\s]/gi, '') || cryptoRandomString({ length: 10, characters: 'abcdefghijk' }); @@ -21,7 +22,7 @@ function nameFromPackage(name) { * Creates a static file with code necessary to load the addons configuration * */ -function getAddonsLoaderCode(name, customThemeAddons = []) { +function getThemeLoaderCode(name, customThemeAddons = []) { let buf = `/* This file is autogenerated. Don't change it directly. Add a ./theme/_${name}.scss in your add-on to load your theme customizations in the current theme. @@ -37,42 +38,27 @@ Add a ./theme/_${name}.scss in your add-on to load your theme customizations in return buf; } -module.exports = ({ main, variables }) => { - // const addonsThemeLoaderVariablesPath = path.join( - // process.cwd(), - // 'src', - // '_variables.scss', - // ); - // const addonsThemeLoaderMainPath = path.join( - // process.cwd(), - // 'src', - // '_main.scss', - // ); - - // const addonsThemeLoaderVariablesPath = path.join( - // process.cwd(), - // 'src', - // '_variables.scss', - // ); - // const addonsThemeLoaderMainPath = path.join( - // process.cwd(), - // 'src', - // '_main.scss', - // ); - +export function createThemeAddonsLoader({ + main, + variables, +}: { + main: string[]; + variables: string[]; +}) { const addonsThemeLoaderVariablesPath = tmp.tmpNameSync({ postfix: '.scss' }); const addonsThemeLoaderMainPath = tmp.tmpNameSync({ postfix: '.scss' }); fs.writeFileSync( addonsThemeLoaderVariablesPath, - new Buffer.from(getAddonsLoaderCode('variables', variables)), + //@ts-expect-error No clue why this is erroring + new Buffer.from(getThemeLoaderCode('variables', variables)), ); fs.writeFileSync( addonsThemeLoaderMainPath, - new Buffer.from(getAddonsLoaderCode('main', main)), + //@ts-expect-error No clue why this is erroring + new Buffer.from(getThemeLoaderCode('main', main)), ); return [addonsThemeLoaderVariablesPath, addonsThemeLoaderMainPath]; -}; +} -module.exports.getAddonsLoaderCode = getAddonsLoaderCode; -module.exports.nameFromPackage = nameFromPackage; +export { getThemeLoaderCode, nameFromPackage }; diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 7f229a66a8..5394254281 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -16,16 +16,16 @@ import type { } from '@plone/types'; export type ConfigData = { - settings: SettingsConfig; - blocks: BlocksConfig; - views: ViewsConfig; - widgets: WidgetsConfig; - addonReducers: AddonReducersConfig; - addonRoutes: AddonRoutesConfig; - slots: SlotsConfig; - components: ComponentsConfig; - utilities: UtilitiesConfig; - experimental: ExperimentalConfig; + settings: SettingsConfig | Record; + blocks: BlocksConfig | Record; + views: ViewsConfig | Record; + widgets: WidgetsConfig | Record; + addonReducers?: AddonReducersConfig; + addonRoutes?: AddonRoutesConfig; + slots: SlotsConfig | Record; + components: ComponentsConfig | Record; + utilities: UtilitiesConfig | Record; + experimental?: ExperimentalConfig; }; type GetComponentResult = { @@ -44,7 +44,15 @@ class Config { constructor() { if (!Config.instance) { - this._data = {}; + this._data = { + settings: {}, + blocks: {}, + views: {}, + widgets: {}, + slots: {}, + components: {}, + utilities: {}, + }; Config.instance = this; } @@ -205,7 +213,10 @@ class Config { return; } const { slots, data } = this._data.slots[name]; - const slotComponents = []; + const slotComponents: { + component: SlotComponent['component']; + name: string; + }[] = []; // For all enabled slots for (const slotName of slots) { // For all registered components for that slot, inversed, since the last one registered wins diff --git a/packages/registry/tsconfig.json b/packages/registry/tsconfig.json index c25bedd978..1319106020 100644 --- a/packages/registry/tsconfig.json +++ b/packages/registry/tsconfig.json @@ -17,7 +17,7 @@ "jsx": "react-jsx", "paths": {} }, - "include": ["src", "src/**/*.js"], + "include": ["src/index.ts", "src/**/*.js", "src/**/*.cjs"], "exclude": [ "node_modules", "build", diff --git a/packages/registry/tsconfig.node.json b/packages/registry/tsconfig.node.json new file mode 100644 index 0000000000..a25380a557 --- /dev/null +++ b/packages/registry/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + /* Base Options: */ + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + /* Strictness */ + "strict": true, + "noUncheckedIndexedAccess": true, + /* If transpiling with TypeScript: */ + "moduleResolution": "NodeNext", + "module": "NodeNext", + "outDir": "dist/esm", + "sourceMap": true, + /* If your code doesn't run in the DOM: */ + "lib": ["es2022"], + "declaration": true + }, + "include": ["src/addon-registry"] +} diff --git a/packages/registry/vite-plugin.d.ts b/packages/registry/vite-plugin.d.ts new file mode 100644 index 0000000000..2af076c87a --- /dev/null +++ b/packages/registry/vite-plugin.d.ts @@ -0,0 +1,5 @@ +import * as vite from 'vite'; + +declare function PloneRegistryVitePlugin(): vite.Plugin; + +export { PloneRegistryVitePlugin }; diff --git a/packages/registry/vite-plugin.js b/packages/registry/vite-plugin.js new file mode 100644 index 0000000000..6ec97dbe64 --- /dev/null +++ b/packages/registry/vite-plugin.js @@ -0,0 +1,60 @@ +import path from 'path'; +import { AddonRegistry } from '@plone/registry/addon-registry'; +import { createAddonsLoader } from '@plone/registry/create-addons-loader'; +import { createThemeAddonsLoader } from '@plone/registry/create-theme-loader'; + +export const PloneRegistryVitePlugin = () => { + const projectRootPath = path.resolve('.'); + const { registry, shadowAliases } = AddonRegistry.init(projectRootPath); + + const addonsLoaderPath = createAddonsLoader( + registry.getAddonDependencies(), + registry.getAddons(), + ); + + const [addonsThemeLoaderVariablesPath, addonsThemeLoaderMainPath] = + createThemeAddonsLoader(registry.getCustomThemeAddons()); + + const addOns = Object.keys(registry.packages); + + return { + name: 'plone-registry', + enforce: 'pre', + config: () => ({ + ssr: { + optimizeDeps: { + exclude: addOns, + }, + }, + esbuild: { + supported: { + 'top-level-await': true, //browsers can handle top-level-await features + }, + }, + optimizeDeps: { + exclude: addOns, + }, + resolve: { + alias: [ + ...shadowAliases, + // Remove in case that we have addons aliases (Volto add-ons which need the `src` path hack) + // ...addonAliases, + ...(registry.theme + ? // Load the theme aliases from the theme config + [ + { + find: 'addonsThemeCustomizationsVariables', + replacement: addonsThemeLoaderVariablesPath, + }, + { + find: 'addonsThemeCustomizationsMain', + replacement: addonsThemeLoaderMainPath, + }, + ] + : []), + { find: 'load-plone-registry-addons', replacement: addonsLoaderPath }, + ], + }, + }), + }; +}; diff --git a/packages/scripts/i18n.cjs b/packages/scripts/i18n.cjs index bd4a1a1f87..439c860e67 100755 --- a/packages/scripts/i18n.cjs +++ b/packages/scripts/i18n.cjs @@ -276,10 +276,25 @@ function main({ addonMode }) { console.log('Synchronizing messages to po files...'); syncPoByPot(); if (!addonMode) { - let AddonConfigurationRegistry; + let AddonRegistry, AddonConfigurationRegistry, registry; try { // Detect where is the registry (if we are in Volto 18 or above for either core and projects) if ( + fs.existsSync( + path.join( + projectRootPath, + '/node_modules/@plone/registry/dist/cjs/addon-registry.cjs', + ), + ) + ) { + AddonRegistry = require( + path.join( + projectRootPath, + '/node_modules/@plone/registry/dist/cjs/addon-registry.cjs', + ), + ).AddonRegistry; + // Detect where is the registry (if we are in Volto 18-alpha.46 or below) + } else if ( fs.existsSync( path.join( projectRootPath, @@ -324,7 +339,11 @@ function main({ addonMode }) { process.exit(); } console.log('Generating the language JSON files...'); - const registry = new AddonConfigurationRegistry(projectRootPath); + if (AddonConfigurationRegistry) { + registry = new AddonConfigurationRegistry(projectRootPath); + } else if (AddonRegistry) { + registry = AddonRegistry.init(projectRootPath).registry; + } poToJson({ registry, addonMode }); } console.log('done!'); diff --git a/packages/scripts/news/6399.feature b/packages/scripts/news/6399.feature new file mode 100644 index 0000000000..5558c340af --- /dev/null +++ b/packages/scripts/news/6399.feature @@ -0,0 +1 @@ +Support for the new `@plone/registry` ESM format. @sneridagh diff --git a/packages/volto/.eslintrc b/packages/volto/.eslintrc index 4e24e2badd..9b98fbf685 100644 --- a/packages/volto/.eslintrc +++ b/packages/volto/.eslintrc @@ -37,7 +37,6 @@ "map": [ ["@plone/volto", "./src"], ["@plone/volto-slate", "../volto-slate/src"], - ["@plone/registry", "../registry/src"], ["@plone/types", "../types"], ["@package", "./src"], ["@root", "./src"], diff --git a/packages/volto/.storybook/main.js b/packages/volto/.storybook/main.js index c745006748..36cb9a3017 100644 --- a/packages/volto/.storybook/main.js +++ b/packages/volto/.storybook/main.js @@ -102,8 +102,8 @@ module.exports = { [], defaultRazzleOptions, ); - const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); - const registry = new AddonConfigurationRegistry(projectRootPath); + const { AddonRegistry } = require('@plone/registry/addon-registry'); + const { registry } = AddonRegistry.init(projectRootPath); config = lessPlugin({ registry, }).modifyWebpackConfig({ diff --git a/packages/volto/__tests__/addon-registry-project.test.js b/packages/volto/__tests__/addon-registry-project.test.js index 08d4bad832..fad432bc88 100644 --- a/packages/volto/__tests__/addon-registry-project.test.js +++ b/packages/volto/__tests__/addon-registry-project.test.js @@ -1,9 +1,11 @@ -const path = require('path'); -const AddonConfigurationRegistry = require('../../registry/src/addon-registry'); -const { buildDependencyGraph, getAddonsLoaderChain } = - AddonConfigurationRegistry; +import path from 'path'; +import { + AddonRegistry, + buildDependencyGraph, + getAddonsLoaderChain, +} from '@plone/registry/addon-registry'; -describe('AddonConfigurationRegistry - Project', () => { +describe('AddonRegistry - Project', () => { jest.mock( `${path.join( __dirname, @@ -19,14 +21,14 @@ describe('AddonConfigurationRegistry - Project', () => { it('works in a mock project directory', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); + const { registry } = AddonRegistry.init(base); const voltoPath = `${base}/node_modules/@plone/volto`; - expect(reg.projectRootPath).toStrictEqual(base); - expect(reg.voltoPath).toStrictEqual(voltoPath); + expect(registry.projectRootPath).toStrictEqual(base); + expect(registry.voltoPath).toStrictEqual(voltoPath); - expect(reg.addonNames).toStrictEqual([ + expect(registry.addonNames).toStrictEqual([ 'test-addon', 'test-released-addon', 'test-released-source-addon', @@ -35,7 +37,7 @@ describe('AddonConfigurationRegistry - Project', () => { 'test-released-unmentioned', ]); - expect(reg.packages).toEqual({ + expect(registry.packages).toEqual({ 'test-addon': { isPublishedPackage: false, modulePath: `${base}/addons/test-addon/src`, @@ -102,8 +104,8 @@ describe('AddonConfigurationRegistry - Project', () => { it('provides aliases for addons', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); - expect(reg.getResolveAliases()).toStrictEqual({ + const { registry } = AddonRegistry.init(base); + expect(registry.getResolveAliases()).toStrictEqual({ 'my-volto-config-addon': `${base}/addons/my-volto-config-addon/src`, 'test-addon': `${base}/addons/test-addon/src`, 'test-released-addon': `${base}/node_modules/test-released-addon`, @@ -115,14 +117,14 @@ describe('AddonConfigurationRegistry - Project', () => { it('provides addon extenders', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); - expect(reg.getAddonExtenders().length).toBe(1); + const { registry } = AddonRegistry.init(base); + expect(registry.getAddonExtenders().length).toBe(1); }); it('provides a list of addon records ordered by initial package declaration', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); - const addons = reg.getAddons(); + const { registry } = AddonRegistry.init(base); + const addons = registry.getAddons(); expect(addons.map((a) => a.name)).toStrictEqual([ 'test-released-unmentioned', 'test-released-dummy', @@ -135,8 +137,8 @@ describe('AddonConfigurationRegistry - Project', () => { it('provides customization paths declared in a Volto project', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); - expect(reg.getProjectCustomizationPaths()).toStrictEqual({ + const { registry } = AddonRegistry.init(base); + expect(registry.getProjectCustomizationPaths()).toStrictEqual({ '@plone/volto/LanguageSwitcher': `${base}/src/customizations/LanguageSwitcher.js`, '@plone/volto/TSComponent': `${base}/src/customizations/TSComponent.jsx`, '@plone/volto/client': `${base}/src/customizations/client.js`, @@ -148,8 +150,8 @@ describe('AddonConfigurationRegistry - Project', () => { it('provides customization paths declared in addons', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); - expect(reg.getAddonCustomizationPaths()).toStrictEqual({ + const { registry } = AddonRegistry.init(base); + expect(registry.getAddonCustomizationPaths()).toStrictEqual({ '@plone/volto/LanguageSwitcher': `${base}/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js`, '@plone/volto/TSComponent': `${base}/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx`, '@plone/volto/client': `${base}/node_modules/test-released-source-addon/src/customizations/client.js`, @@ -231,9 +233,11 @@ describe('Addon via env var - Released addon', () => { it('addons can be specified on the fly using ADDONS env var - Released addon', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); + const { registry } = AddonRegistry.init(base); expect( - Object.keys(reg.packages).includes('test-released-via-addons-env-var'), + Object.keys(registry.packages).includes( + 'test-released-via-addons-env-var', + ), ).toBe(true); }); }); @@ -255,9 +259,9 @@ describe('Addon via env var - local packages folder addon', () => { it('addons can be specified on the fly using ADDONS env var - local packages folder addon', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); - const reg = new AddonConfigurationRegistry(base); + const { registry } = AddonRegistry.init(base); expect( - Object.keys(reg.packages).includes( + Object.keys(registry.packages).includes( 'test-local-packages-via-addons-env-var', ), ).toBe(true); diff --git a/packages/volto/__tests__/addon-registry-volto.test.js b/packages/volto/__tests__/addon-registry-volto.test.js index 5bea088c21..79fc58af0d 100644 --- a/packages/volto/__tests__/addon-registry-volto.test.js +++ b/packages/volto/__tests__/addon-registry-volto.test.js @@ -1,14 +1,14 @@ -const path = require('path'); -const AddonConfigurationRegistry = require('../../registry/src/addon-registry'); +import path from 'path'; +import { AddonRegistry } from '@plone/registry/addon-registry'; -describe('AddonConfigurationRegistry - Volto', () => { +describe('AddonRegistry - Volto', () => { it('works in Volto', () => { const base = path.join(__dirname, '..'); - const reg = new AddonConfigurationRegistry(base); - expect(reg.projectRootPath).toStrictEqual(base); + const { registry } = AddonRegistry.init(base); + expect(registry.projectRootPath).toStrictEqual(base); // TODO: rename initPackagesFolder to proper name after the refactor - // expect(reg.addonNames).toStrictEqual(['@plone/volto-slate']); - expect(reg.packages['@plone/volto-slate'].name).toStrictEqual( + // expect(registry.addonNames).toStrictEqual(['@plone/volto-slate']); + expect(registry.packages['@plone/volto-slate'].name).toStrictEqual( '@plone/volto-slate', ); }); diff --git a/packages/volto/__tests__/create-addons-loader.test.js b/packages/volto/__tests__/create-addons-loader.test.js index 570fa10a18..dfe9d174cb 100644 --- a/packages/volto/__tests__/create-addons-loader.test.js +++ b/packages/volto/__tests__/create-addons-loader.test.js @@ -1,11 +1,51 @@ -const fs = require('fs'); -const transform = require('@babel/core').transform; - -const getLoader = require('../../registry/src/create-addons-loader'); +import fs from 'fs'; +import { transform } from '@babel/core'; +import { + createAddonsLoader, + getAddonsLoaderCode, + nameFromPackage, +} from '@plone/registry/create-addons-loader'; describe('create-addons-loader code generation', () => { - test('no addon creates simple loader', () => { - const code = getLoader.getAddonsLoaderCode([]); + test('no addon creates simple loader, default = no loadProjectConfig', () => { + const code = getAddonsLoaderCode([]); + expect(code).toBe(`/* +This file is autogenerated. Don't change it directly. +Instead, change the "addons" setting in your package.json file. +*/ + + +const addonsInfo = {}; +export { addonsInfo }; + +const safeWrapper = (func) => (config) => { + const res = func(config); + if (typeof res === 'undefined') { + throw new Error("Configuration function doesn't return config"); + } + return res; +} + +const projectConfigLoader = false; +const projectConfig = (config) => { + return projectConfigLoader && typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; +} + +const load = (config) => { + const addonLoaders = []; + if(!addonLoaders.every((el) => typeof el === "function")) { + throw new TypeError( + 'Each addon has to provide a function applying its configuration to the projects configuration.', + ); + } + return projectConfig(addonLoaders.reduce((acc, apply) => safeWrapper(apply)(acc), config)); +}; +export default load; +`); + }); + + test('no addon creates simple loader, loadProjectConfig set to true', () => { + const code = getAddonsLoaderCode([], {}, true); expect(code).toBe(`/* This file is autogenerated. Don't change it directly. Instead, change the "addons" setting in your package.json file. @@ -24,8 +64,9 @@ const safeWrapper = (func) => (config) => { return res; } + const projectConfig = (config) => { - return typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; + return projectConfigLoader && typeof projectConfigLoader.default === "function" ? projectConfigLoader.default(config) : config; } const load = (config) => { @@ -42,17 +83,14 @@ export default load; }); test('one addon creates loader', () => { - const code = getLoader.getAddonsLoaderCode(['volto-addon1']); + const code = getAddonsLoaderCode(['volto-addon1']); expect(code.indexOf("import voltoAddon1 from 'volto-addon1';") > 0).toBe( true, ); }); test('two addons create loaders', () => { - const code = getLoader.getAddonsLoaderCode([ - 'volto-addon1', - 'volto-addon2', - ]); + const code = getAddonsLoaderCode(['volto-addon1', 'volto-addon2']); expect( code.indexOf(` import voltoAddon1 from 'volto-addon1'; @@ -61,7 +99,7 @@ import voltoAddon2 from 'volto-addon2';`) > 0, }); test('one addons plus one extra creates loader', () => { - const code = getLoader.getAddonsLoaderCode(['volto-addon1:loadExtra1']); + const code = getAddonsLoaderCode(['volto-addon1:loadExtra1']); expect( code.indexOf(` import voltoAddon1, { loadExtra1 as loadExtra10 } from 'volto-addon1'; @@ -70,9 +108,7 @@ import voltoAddon1, { loadExtra1 as loadExtra10 } from 'volto-addon1'; }); test('one addons plus two extras creates loader', () => { - const code = getLoader.getAddonsLoaderCode([ - 'volto-addon1:loadExtra1,loadExtra2', - ]); + const code = getAddonsLoaderCode(['volto-addon1:loadExtra1,loadExtra2']); expect( code.indexOf(` import voltoAddon1, { loadExtra1 as loadExtra10, loadExtra2 as loadExtra21 } from 'volto-addon1'; @@ -81,7 +117,7 @@ import voltoAddon1, { loadExtra1 as loadExtra10, loadExtra2 as loadExtra21 } fro }); test('two addons plus extras creates loader', () => { - const code = getLoader.getAddonsLoaderCode([ + const code = getAddonsLoaderCode([ 'volto-addon1:loadExtra1,loadExtra2', 'volto-addon2:loadExtra3,loadExtra4', ]); @@ -95,7 +131,7 @@ import voltoAddon2, { loadExtra3 as loadExtra32, loadExtra4 as loadExtra43 } fro }); describe('create-addons-loader default name generation', () => { - const getName = getLoader.nameFromPackage; + const getName = nameFromPackage; test('passing a simple word returns a word', () => { expect(getName('something')).toBe('something'); @@ -149,7 +185,7 @@ function makeAddonLoader(addons, load = true) { : require.resolve(name), ); - const loaderPath = getLoader(addons); + const loaderPath = createAddonsLoader(addons); transpile(loaderPath); if (load) { diff --git a/packages/volto/__tests__/webpack-relative-resolver.test.js b/packages/volto/__tests__/webpack-relative-resolver.test.js index 429946a216..cc91dbfb4f 100644 --- a/packages/volto/__tests__/webpack-relative-resolver.test.js +++ b/packages/volto/__tests__/webpack-relative-resolver.test.js @@ -1,10 +1,10 @@ const path = require('path'); -const AddonConfigurationRegistry = require('../../registry/src/addon-registry'); +const { AddonRegistry } = require('@plone/registry/addon-registry'); const WebpackRelativeResolver = require('../../volto/webpack-plugins/webpack-relative-resolver'); const base = path.join(__dirname, '..'); const makeRegistry = () => { - const registry = new AddonConfigurationRegistry(base); + const { registry } = AddonRegistry.init(base); registry.packages = { '@plone/volto-addon': { modulePath: '/somewhere/volto-addon/src', diff --git a/packages/volto/news/6399.internal b/packages/volto/news/6399.internal new file mode 100644 index 0000000000..363ce2c2a7 --- /dev/null +++ b/packages/volto/news/6399.internal @@ -0,0 +1 @@ +Adapt `@plone/registry` as an ESM module, and fix its imports. @sneridagh diff --git a/packages/volto/package.json b/packages/volto/package.json index ac4b17141d..2de2443dd7 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -85,6 +85,8 @@ "@plone/volto/babel": "/babel.js", "@plone/volto/(.*)$": "/src/$1", "@plone/volto-slate/(.*)$": "/../volto-slate/src/$1", + "@plone/registry/addon-registry$": "/node_modules/@plone/registry/dist/esm/addon-registry.js", + "@plone/registry/create-addons-loader$": "/node_modules/@plone/registry/dist/esm/create-addons-loader.js", "@plone/registry": "/../registry/src", "@plone/registry/(.*)$": "/../registry/src/$1", "@plone/volto": "/src/index.js", @@ -280,11 +282,11 @@ "@babel/plugin-syntax-export-namespace-from": "7.8.3", "@babel/runtime": "7.20.6", "@babel/types": "7.20.5", - "@fiverr/afterbuild-webpack-plugin": "^1.0.0", - "@jest/globals": "^29.7.0", "@dnd-kit/core": "6.0.8", "@dnd-kit/sortable": "7.0.2", "@dnd-kit/utilities": "3.2.2", + "@fiverr/afterbuild-webpack-plugin": "^1.0.0", + "@jest/globals": "^29.7.0", "@loadable/babel-plugin": "5.13.2", "@loadable/webpack-plugin": "5.15.2", "@plone/types": "workspace:*", diff --git a/packages/volto/razzle.config.js b/packages/volto/razzle.config.js index b2ec16d7f7..a37df657ac 100644 --- a/packages/volto/razzle.config.js +++ b/packages/volto/razzle.config.js @@ -8,9 +8,11 @@ const fs = require('fs'); const RootResolverPlugin = require('./webpack-plugins/webpack-root-resolver'); const RelativeResolverPlugin = require('./webpack-plugins/webpack-relative-resolver'); const { poToJson } = require('@plone/scripts/i18n.cjs'); -const createAddonsLoader = require('@plone/registry/src/create-addons-loader'); -const createThemeAddonsLoader = require('@plone/registry/src/create-theme-addons-loader'); -const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); +const { createAddonsLoader } = require('@plone/registry/create-addons-loader'); +const { + createThemeAddonsLoader, +} = require('@plone/registry/create-theme-loader'); +const { AddonRegistry } = require('@plone/registry/addon-registry'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); @@ -23,8 +25,7 @@ const projectRootPath = path.resolve('.'); const languages = require('./src/constants/Languages.cjs'); const packageJson = require(path.join(projectRootPath, 'package.json')); - -const registry = new AddonConfigurationRegistry(projectRootPath); +const { registry } = AddonRegistry.init(projectRootPath); const defaultModify = ({ env: { target, dev }, @@ -290,6 +291,8 @@ const defaultModify = ({ const addonsLoaderPath = createAddonsLoader( registry.getAddonDependencies(), registry.getAddons(), + // The load of the project config is deprecated and will be removed in Volto 19. + { loadProjectConfig: true }, ); config.resolve.plugins = [ diff --git a/packages/volto/src/express-middleware/static.js b/packages/volto/src/express-middleware/static.js index 258af2136e..5b648b09f0 100644 --- a/packages/volto/src/express-middleware/static.js +++ b/packages/volto/src/express-middleware/static.js @@ -1,10 +1,10 @@ import express from 'express'; import path from 'path'; -import AddonConfigurationRegistry from '@plone/registry/src/addon-registry'; +import { AddonRegistry } from '@plone/registry/addon-registry'; import config from '@plone/volto/registry'; const projectRootPath = path.resolve('.'); -const registry = new AddonConfigurationRegistry(projectRootPath); +const { registry } = AddonRegistry.init(projectRootPath); const staticDirectory = () => { if (process.env.BUILD_DIR) { diff --git a/patches/jest-resolve@26.6.2.patch b/patches/jest-resolve@26.6.2.patch new file mode 100644 index 0000000000..20c5d96a80 Binary files /dev/null and b/patches/jest-resolve@26.6.2.patch differ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ce225d6be..f081099b4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,11 @@ overrides: '@pmmmwh/react-refresh-webpack-plugin': 0.5.11 react-refresh: 0.14.0 +patchedDependencies: + jest-resolve@26.6.2: + hash: tmr5fnfjpir3crfu6ze44v7tue + path: patches/jest-resolve@26.6.2.patch + importers: .: @@ -479,7 +484,7 @@ importers: version: 3.11.11(react@18.2.0) '@storybook/test': specifier: ^8.0.4 - version: 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) + version: 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -522,7 +527,7 @@ importers: version: 8.0.8(@types/react@18.2.79)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@storybook/addon-interactions': specifier: ^8.0.4 - version: 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) + version: 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) '@storybook/addon-links': specifier: ^8.0.4 version: 8.0.8(react@18.2.0) @@ -546,7 +551,7 @@ importers: version: 8.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@testing-library/jest-dom': specifier: 6.4.2 - version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) + version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) '@testing-library/react': specifier: 14.2.1 version: 14.2.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -822,8 +827,11 @@ importers: specifier: 0.10.0 version: 0.10.0 glob: - specifier: 7.1.6 - version: 7.1.6 + specifier: ^10.4.5 + version: 10.4.5 + tmp: + specifier: 0.2.1 + version: 0.2.1 devDependencies: '@parcel/packager-ts': specifier: ^2.12.0 @@ -834,12 +842,24 @@ importers: '@plone/types': specifier: workspace:* version: link:../types + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 + '@types/glob': + specifier: ^8.1.0 + version: 8.1.0 + '@types/node': + specifier: ^20 + version: 20.12.7 '@types/react': specifier: ^18 version: 18.2.79 '@types/react-dom': specifier: ^18 version: 18.2.25 + '@types/tmp': + specifier: ^0.2.6 + version: 0.2.6 parcel: specifier: ^2.12.0 version: 2.12.0(@swc/helpers@0.5.10)(postcss@8.4.47)(relateurl@0.2.7)(srcset@4.0.0)(terser@5.30.3)(typescript@5.6.3) @@ -858,6 +878,9 @@ importers: typescript: specifier: ^5.6.3 version: 5.6.3 + vite: + specifier: ^5.4.8 + version: 5.4.9(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3) vitest: specifier: ^2.1.3 version: 2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3) @@ -1715,7 +1738,7 @@ importers: version: 10.0.1(cypress@13.13.2) '@testing-library/jest-dom': specifier: 6.4.2 - version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) + version: 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) '@testing-library/react': specifier: 12.1.5 version: 12.1.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -3595,10 +3618,23 @@ packages: resolution: {integrity: sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==} engines: {node: '>= 10.14.2'} + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/core@26.6.3': resolution: {integrity: sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==} engines: {node: '>= 10.14.2'} + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + '@jest/environment@26.6.2': resolution: {integrity: sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==} engines: {node: '>= 10.14.2'} @@ -3635,6 +3671,15 @@ packages: resolution: {integrity: sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==} engines: {node: '>= 10.14.2'} + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3643,14 +3688,26 @@ packages: resolution: {integrity: sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==} engines: {node: '>= 10.14.2'} + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-result@26.6.2': resolution: {integrity: sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==} engines: {node: '>= 10.14.2'} + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/test-sequencer@26.6.3': resolution: {integrity: sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==} engines: {node: '>= 10.14.2'} + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/transform@26.6.2': resolution: {integrity: sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==} engines: {node: '>= 10.14.2'} @@ -6259,6 +6316,9 @@ packages: '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + '@types/glob@8.1.0': + resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -6473,6 +6533,9 @@ packages: '@types/testing-library__react@9.1.3': resolution: {integrity: sha512-iCdNPKU3IsYwRK9JieSYAiX0+aYDXOGAmrC/3/M7AqqSDKnWWVv07X+Zk1uFSL7cMTUYzv4lQRfohucEocn5/w==} + '@types/tmp@0.2.6': + resolution: {integrity: sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==} + '@types/unist@2.0.10': resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} @@ -7314,6 +7377,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + babel-loader@8.3.0: resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} engines: {node: '>= 8.9'} @@ -7349,6 +7418,10 @@ packages: resolution: {integrity: sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==} engines: {node: '>= 10.14.2'} + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-lodash@3.3.4: resolution: {integrity: sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg==} @@ -7410,6 +7483,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + babel-preset-razzle@4.2.17: resolution: {integrity: sha512-Pg0yFCn2uTRBKjdj2pu61JWMcokVdxWNmpeBC2W+fNJ3JFyYP379TMIMmRi84g61snAzmwzwIlKMlVsVZT1IiA==} @@ -8304,6 +8383,11 @@ packages: resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} engines: {node: '>= 14'} + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + croner@8.1.2: resolution: {integrity: sha512-ypfPFcAXHuAZRCzo3vJL6ltENzniTjwe/qsLleH1V2/7SRDjgvRQyrLmumFTLmjFax4IuSxfGXEn79fozXcJog==} engines: {node: '>=18.0'} @@ -8647,6 +8731,14 @@ packages: dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + deep-copy@1.4.2: resolution: {integrity: sha512-VxZwQ/1+WGQPl5nE67uLhh7OqdrmqI1OazrraO9Bbw/M8Bt6Mol/RxzDA6N6ZgRXpsG/W9PgUj8E1LHHBEq2GQ==} engines: {node: '>=4.0.0'} @@ -9007,6 +9099,10 @@ packages: electron-to-chromium@1.5.41: resolution: {integrity: sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==} + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + emittery@0.7.2: resolution: {integrity: sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==} engines: {node: '>=10'} @@ -9686,10 +9782,6 @@ packages: resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==} engines: {node: '>=0.10.0'} - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -10074,9 +10166,8 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true - glob@10.3.12: - resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} - engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true glob@7.1.6: @@ -10084,6 +10175,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} @@ -11134,6 +11226,10 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} @@ -11163,6 +11259,9 @@ packages: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jake@10.8.7: resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} engines: {node: '>=10'} @@ -11179,11 +11278,29 @@ packages: resolution: {integrity: sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==} engines: {node: '>= 10.14.2'} + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-cli@26.6.3: resolution: {integrity: sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==} engines: {node: '>= 10.14.2'} hasBin: true + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + jest-config@26.6.3: resolution: {integrity: sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==} engines: {node: '>= 10.14.2'} @@ -11193,6 +11310,18 @@ packages: ts-node: optional: true + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + jest-diff@26.6.2: resolution: {integrity: sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==} engines: {node: '>= 10.14.2'} @@ -11205,10 +11334,18 @@ packages: resolution: {integrity: sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==} engines: {node: '>= 10.14.2'} + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-each@26.6.2: resolution: {integrity: sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==} engines: {node: '>= 10.14.2'} + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-environment-jsdom@26.6.2: resolution: {integrity: sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==} engines: {node: '>= 10.14.2'} @@ -11217,6 +11354,10 @@ packages: resolution: {integrity: sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==} engines: {node: '>= 10.14.2'} + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-file@1.0.0: resolution: {integrity: sha512-QqcbPCfpVCxixoF0gia825AGBGXYWVi4cQpLsXBQVxkWswnZ39XHpT490NvTMtokY6v6BM+vw8rlhtjORabplA==} @@ -11244,6 +11385,10 @@ packages: resolution: {integrity: sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==} engines: {node: '>= 10.14.2'} + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@26.6.2: resolution: {integrity: sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==} engines: {node: '>= 10.14.2'} @@ -11293,19 +11438,35 @@ packages: resolution: {integrity: sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==} engines: {node: '>= 10.14.2'} + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-resolve@26.6.2: resolution: {integrity: sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==} engines: {node: '>= 10.14.2'} + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runner@26.6.3: resolution: {integrity: sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==} engines: {node: '>= 10.14.2'} + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-runtime@26.6.3: resolution: {integrity: sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==} engines: {node: '>= 10.14.2'} hasBin: true + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-serializer@26.6.2: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} @@ -11330,10 +11491,18 @@ packages: resolution: {integrity: sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==} engines: {node: '>= 10.14.2'} + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-watcher@26.6.2: resolution: {integrity: sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==} engines: {node: '>= 10.14.2'} + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-worker@25.5.0: resolution: {integrity: sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==} engines: {node: '>= 8.3'} @@ -11355,6 +11524,16 @@ packages: engines: {node: '>= 10.14.2'} hasBin: true + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true @@ -12546,8 +12725,8 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} - minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} minizlib@2.1.2: @@ -13191,6 +13370,9 @@ packages: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} engines: {node: '>=8'} @@ -13300,9 +13482,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.10.2: - resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} @@ -13902,6 +14084,9 @@ packages: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} engines: {node: '>=12.20'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} @@ -14752,10 +14937,12 @@ packages: rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup-plugin-visualizer@5.12.0: @@ -15210,6 +15397,9 @@ packages: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -16479,6 +16669,10 @@ packages: resolution: {integrity: sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==} engines: {node: '>=10.10.0'} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -17179,7 +17373,7 @@ snapshots: '@babel/code-frame@7.24.2': dependencies: '@babel/highlight': 7.24.2 - picocolors: 1.0.0 + picocolors: 1.1.0 '@babel/code-frame@7.25.7': dependencies: @@ -17429,7 +17623,7 @@ snapshots: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.0 + picocolors: 1.1.0 '@babel/highlight@7.25.7': dependencies: @@ -17558,16 +17752,31 @@ snapshots: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 @@ -17613,11 +17822,21 @@ snapshots: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 @@ -17633,31 +17852,61 @@ snapshots: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 @@ -17673,6 +17922,11 @@ snapshots: '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.24.0 + '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.4)': dependencies: '@babel/core': 7.24.4 @@ -18907,6 +19161,16 @@ snapshots: jest-util: 26.6.2 slash: 3.0.0 + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + optional: true + '@jest/core@26.6.3': dependencies: '@jest/console': 26.6.2 @@ -18924,7 +19188,7 @@ snapshots: jest-haste-map: 26.6.2 jest-message-util: 26.6.2 jest-regex-util: 26.0.0 - jest-resolve: 26.6.2 + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) jest-resolve-dependencies: 26.6.3 jest-runner: 26.6.3 jest-runtime: 26.6.3 @@ -18932,7 +19196,7 @@ snapshots: jest-util: 26.6.2 jest-validate: 26.6.2 jest-watcher: 26.6.2 - micromatch: 4.0.5 + micromatch: 4.0.8 p-each-series: 2.2.0 rimraf: 3.0.2 slash: 3.0.0 @@ -18944,6 +19208,42 @@ snapshots: - ts-node - utf-8-validate + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.12.7) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + optional: true + '@jest/environment@26.6.2': dependencies: '@jest/fake-timers': 26.6.2 @@ -19020,7 +19320,7 @@ snapshots: istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.7 jest-haste-map: 26.6.2 - jest-resolve: 26.6.2 + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) jest-util: 26.6.2 jest-worker: 26.6.2 slash: 3.0.0 @@ -19033,6 +19333,36 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.12.7 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.1.6 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + optional: true + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -19043,6 +19373,13 @@ snapshots: graceful-fs: 4.2.11 source-map: 0.6.1 + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + optional: true + '@jest/test-result@26.6.2': dependencies: '@jest/console': 26.6.2 @@ -19050,6 +19387,14 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + optional: true + '@jest/test-sequencer@26.6.3': dependencies: '@jest/test-result': 26.6.2 @@ -19064,9 +19409,17 @@ snapshots: - ts-node - utf-8-validate + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + optional: true + '@jest/transform@26.6.2': dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.25.8 '@jest/types': 26.6.2 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 @@ -19076,7 +19429,7 @@ snapshots: jest-haste-map: 26.6.2 jest-regex-util: 26.0.0 jest-util: 26.6.2 - micromatch: 4.0.5 + micromatch: 4.0.8 pirates: 4.0.6 slash: 3.0.0 source-map: 0.6.1 @@ -19086,7 +19439,7 @@ snapshots: '@jest/transform@29.7.0': dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.25.8 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -19097,7 +19450,7 @@ snapshots: jest-haste-map: 29.7.0 jest-regex-util: 29.6.3 jest-util: 29.7.0 - micromatch: 4.0.5 + micromatch: 4.0.8 pirates: 4.0.6 slash: 3.0.0 write-file-atomic: 4.0.2 @@ -19447,7 +19800,7 @@ snapshots: '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 - semver: 7.6.0 + semver: 7.6.3 '@npmcli/fs@3.1.0': dependencies: @@ -19529,7 +19882,7 @@ snapshots: '@npmcli/package-json@4.0.1': dependencies: '@npmcli/git': 4.1.0 - glob: 10.3.12 + glob: 10.4.5 hosted-git-info: 6.1.1 json-parse-even-better-errors: 3.0.1 normalize-package-data: 5.0.0 @@ -22094,11 +22447,11 @@ snapshots: dependencies: '@storybook/global': 5.0.0 - '@storybook/addon-interactions@8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': + '@storybook/addon-interactions@8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': dependencies: '@storybook/global': 5.0.0 '@storybook/instrumenter': 8.0.8 - '@storybook/test': 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) + '@storybook/test': 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) '@storybook/types': 8.0.8 polished: 4.3.1 ts-dedent: 2.2.0 @@ -22389,7 +22742,7 @@ snapshots: find-cache-dir: 3.3.2 find-up: 5.0.0 fs-extra: 11.2.0 - glob: 10.3.12 + glob: 10.4.5 handlebars: 4.7.8 lazy-universal-dotenv: 4.0.0 node-fetch: 2.7.0(encoding@0.1.13) @@ -22720,14 +23073,14 @@ snapshots: - encoding - supports-color - '@storybook/test@8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': + '@storybook/test@8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': dependencies: '@storybook/client-logger': 8.0.8 '@storybook/core-events': 8.0.8 '@storybook/instrumenter': 8.0.8 '@storybook/preview-api': 8.0.8 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) + '@testing-library/jest-dom': 6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 '@vitest/spy': 1.5.0 @@ -23103,7 +23456,7 @@ snapshots: jest: 26.6.3 vitest: 2.1.3(@types/node@20.12.7)(jsdom@16.7.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3) - '@testing-library/jest-dom@6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': + '@testing-library/jest-dom@6.4.2(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': dependencies: '@adobe/css-tools': 4.3.3 '@babel/runtime': 7.20.6 @@ -23116,6 +23469,7 @@ snapshots: optionalDependencies: '@jest/globals': 29.7.0 '@types/jest': 29.5.12 + jest: 29.7.0(@types/node@20.12.7) vitest: 2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3) '@testing-library/react-hooks@8.0.1(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react-test-renderer@18.2.0(react@18.2.0))(react@18.2.0)': @@ -23267,7 +23621,7 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 8.56.10 - '@types/estree': 0.0.51 + '@types/estree': 1.0.6 '@types/eslint@8.56.10': dependencies: @@ -23307,6 +23661,11 @@ snapshots: '@types/minimatch': 5.1.2 '@types/node': 20.12.7 + '@types/glob@8.1.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 20.12.7 + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.12.7 @@ -23543,6 +23902,8 @@ snapshots: '@types/testing-library__dom': 7.5.0 pretty-format: 25.5.0 + '@types/tmp@0.2.6': {} + '@types/unist@2.0.10': {} '@types/unist@3.0.2': {} @@ -24049,7 +24410,7 @@ snapshots: '@vue/shared': 3.4.23 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 '@vue/compiler-dom@3.4.23': dependencies: @@ -24369,7 +24730,7 @@ snapshots: archiver-utils@5.0.2: dependencies: - glob: 10.3.12 + glob: 10.4.5 graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 @@ -24699,6 +25060,34 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@26.6.3(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@jest/transform': 26.6.2 + '@jest/types': 26.6.2 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 26.6.2(@babel/core@7.25.8) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-jest@29.7.0(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.25.8) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + babel-loader@8.3.0(@babel/core@7.24.4)(webpack@5.90.1(esbuild@0.20.2)): dependencies: '@babel/core': 7.24.4 @@ -24745,6 +25134,14 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.5 + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.25.7 + '@babel/types': 7.20.5 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + optional: true + babel-plugin-lodash@3.3.4: dependencies: '@babel/helper-module-imports': 7.24.3 @@ -24840,12 +25237,41 @@ snapshots: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) + babel-preset-current-node-syntax@1.0.1(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.8) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.8) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.8) + babel-preset-jest@26.6.2(@babel/core@7.24.4): dependencies: '@babel/core': 7.24.4 babel-plugin-jest-hoist: 26.6.2 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.4) + babel-preset-jest@26.6.2(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + babel-plugin-jest-hoist: 26.6.2 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.25.8) + + babel-preset-jest@29.6.3(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.25.8) + optional: true + babel-preset-razzle@4.2.17: dependencies: '@babel/core': 7.24.4 @@ -25123,7 +25549,7 @@ snapshots: braces@3.0.2: dependencies: - fill-range: 7.0.1 + fill-range: 7.1.1 braces@3.0.3: dependencies: @@ -25322,9 +25748,9 @@ snapshots: dependencies: '@npmcli/fs': 3.1.0 fs-minipass: 3.0.3 - glob: 10.3.12 + glob: 10.4.5 lru-cache: 7.18.3 - minipass: 7.0.4 + minipass: 7.1.2 minipass-collect: 1.0.2 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 @@ -25955,6 +26381,22 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.5.2 + create-jest@29.7.0(@types/node@20.12.7): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.12.7) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + croner@8.1.2: {} cross-spawn@5.1.0: @@ -26352,6 +26794,9 @@ snapshots: dedent@0.7.0: {} + dedent@1.5.3: + optional: true + deep-copy@1.4.2: {} deep-eql@4.1.3: @@ -26707,6 +27152,9 @@ snapshots: electron-to-chromium@1.5.41: {} + emittery@0.13.1: + optional: true + emittery@0.7.2: {} emoji-regex@10.3.0: {} @@ -27867,10 +28315,6 @@ snapshots: repeat-string: 1.6.1 to-regex-range: 2.1.1 - fill-range@7.0.1: - dependencies: - to-regex-range: 5.0.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -28119,7 +28563,7 @@ snapshots: fs-minipass@3.0.3: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 fs-monkey@1.0.5: {} @@ -28317,16 +28761,17 @@ snapshots: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.4 - minipass: 7.0.4 - path-scurry: 1.10.2 + minipass: 7.1.2 + path-scurry: 1.11.1 - glob@10.3.12: + glob@10.4.5: dependencies: foreground-child: 3.1.1 - jackspeak: 2.3.6 + jackspeak: 3.4.3 minimatch: 9.0.4 - minipass: 7.0.4 - path-scurry: 1.10.2 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 glob@7.1.6: dependencies: @@ -29446,7 +29891,7 @@ snapshots: istanbul-lib-instrument@4.0.3: dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.25.8 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -29463,6 +29908,17 @@ snapshots: transitivePeerDependencies: - supports-color + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.25.8 + '@babel/parser': 7.25.8 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + optional: true + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 @@ -29511,6 +29967,12 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jake@10.8.7: dependencies: async: 3.2.5 @@ -29533,6 +29995,40 @@ snapshots: execa: 4.1.0 throat: 5.0.0 + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + optional: true + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + jest-cli@26.6.3: dependencies: '@jest/core': 26.6.3 @@ -29555,12 +30051,32 @@ snapshots: - ts-node - utf-8-validate + jest-cli@29.7.0(@types/node@20.12.7): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.12.7) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@20.12.7) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + jest-config@26.6.3: dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.25.8 '@jest/test-sequencer': 26.6.3 '@jest/types': 26.6.2 - babel-jest: 26.6.3(@babel/core@7.24.4) + babel-jest: 26.6.3(@babel/core@7.25.8) chalk: 4.1.2 deepmerge: 4.3.1 glob: 7.1.6 @@ -29570,10 +30086,10 @@ snapshots: jest-get-type: 26.3.0 jest-jasmine2: 26.6.3 jest-regex-util: 26.0.0 - jest-resolve: 26.6.2 + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) jest-util: 26.6.2 jest-validate: 26.6.2 - micromatch: 4.0.5 + micromatch: 4.0.8 pretty-format: 26.6.2 transitivePeerDependencies: - bufferutil @@ -29581,6 +30097,37 @@ snapshots: - supports-color - utf-8-validate + jest-config@29.7.0(@types/node@20.12.7): + dependencies: + '@babel/core': 7.25.8 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.25.8) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.1.6 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.12.7 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + optional: true + jest-diff@26.6.2: dependencies: chalk: 4.1.2 @@ -29599,6 +30146,11 @@ snapshots: dependencies: detect-newline: 3.1.0 + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + optional: true + jest-each@26.6.2: dependencies: '@jest/types': 26.6.2 @@ -29607,6 +30159,15 @@ snapshots: jest-util: 26.6.2 pretty-format: 26.6.2 + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + optional: true + jest-environment-jsdom@26.6.2: dependencies: '@jest/environment': 26.6.2 @@ -29631,6 +30192,16 @@ snapshots: jest-mock: 26.6.2 jest-util: 26.6.2 + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + jest-mock: 29.7.0 + jest-util: 29.7.0 + optional: true + jest-file@1.0.0: {} jest-get-type@26.3.0: {} @@ -29649,7 +30220,7 @@ snapshots: jest-serializer: 26.6.2 jest-util: 26.6.2 jest-worker: 26.6.2 - micromatch: 4.0.5 + micromatch: 4.0.8 sane: 4.1.0 walker: 1.0.8 optionalDependencies: @@ -29668,14 +30239,14 @@ snapshots: jest-regex-util: 29.6.3 jest-util: 29.7.0 jest-worker: 29.7.0 - micromatch: 4.0.5 + micromatch: 4.0.8 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 jest-jasmine2@26.6.3: dependencies: - '@babel/traverse': 7.24.1 + '@babel/traverse': 7.25.7 '@jest/environment': 26.6.2 '@jest/source-map': 26.6.2 '@jest/test-result': 26.6.2 @@ -29705,6 +30276,12 @@ snapshots: jest-get-type: 26.3.0 pretty-format: 26.6.2 + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + optional: true + jest-matcher-utils@26.6.2: dependencies: chalk: 4.1.2 @@ -29761,9 +30338,14 @@ snapshots: '@types/node': 20.12.7 jest-util: 29.7.0 - jest-pnp-resolver@1.2.3(jest-resolve@26.6.2): + jest-pnp-resolver@1.2.3(jest-resolve@26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue)): + optionalDependencies: + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): optionalDependencies: - jest-resolve: 26.6.2 + jest-resolve: 29.7.0 + optional: true jest-regex-util@26.0.0: {} @@ -29777,17 +30359,38 @@ snapshots: transitivePeerDependencies: - supports-color - jest-resolve@26.6.2: + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + optional: true + + jest-resolve@26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue): dependencies: '@jest/types': 26.6.2 chalk: 4.1.2 graceful-fs: 4.2.11 - jest-pnp-resolver: 1.2.3(jest-resolve@26.6.2) + jest-pnp-resolver: 1.2.3(jest-resolve@26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue)) jest-util: 26.6.2 read-pkg-up: 7.0.1 resolve: 1.22.8 slash: 3.0.0 + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 + optional: true + jest-runner@26.6.3: dependencies: '@jest/console': 26.6.2 @@ -29804,7 +30407,7 @@ snapshots: jest-haste-map: 26.6.2 jest-leak-detector: 26.6.2 jest-message-util: 26.6.2 - jest-resolve: 26.6.2 + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) jest-runtime: 26.6.3 jest-util: 26.6.2 jest-worker: 26.6.2 @@ -29817,6 +30420,33 @@ snapshots: - ts-node - utf-8-validate + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + optional: true + jest-runtime@26.6.3: dependencies: '@jest/console': 26.6.2 @@ -29839,7 +30469,7 @@ snapshots: jest-message-util: 26.6.2 jest-mock: 26.6.2 jest-regex-util: 26.0.0 - jest-resolve: 26.6.2 + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) jest-snapshot: 26.6.2 jest-util: 26.6.2 jest-validate: 26.6.2 @@ -29853,6 +30483,34 @@ snapshots: - ts-node - utf-8-validate + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.2 + glob: 7.1.6 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + optional: true + jest-serializer@26.6.2: dependencies: '@types/node': 20.12.7 @@ -29862,7 +30520,7 @@ snapshots: dependencies: '@babel/types': 7.20.5 '@jest/types': 26.6.2 - '@types/babel__traverse': 7.20.5 + '@types/babel__traverse': 7.20.6 '@types/prettier': 2.7.3 chalk: 4.1.2 expect: 26.6.2 @@ -29872,24 +30530,24 @@ snapshots: jest-haste-map: 26.6.2 jest-matcher-utils: 26.6.2 jest-message-util: 26.6.2 - jest-resolve: 26.6.2 + jest-resolve: 26.6.2(patch_hash=tmr5fnfjpir3crfu6ze44v7tue) natural-compare: 1.4.0 pretty-format: 26.6.2 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.24.4 - '@babel/generator': 7.24.4 - '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) + '@babel/core': 7.25.8 + '@babel/generator': 7.25.7 + '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-typescript': 7.25.7(@babel/core@7.25.8) '@babel/types': 7.20.5 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.4) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.25.8) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -29900,7 +30558,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -29931,6 +30589,16 @@ snapshots: leven: 3.1.0 pretty-format: 26.6.2 + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + optional: true + jest-watcher@26.6.2: dependencies: '@jest/test-result': 26.6.2 @@ -29941,6 +30609,18 @@ snapshots: jest-util: 26.6.2 string-length: 4.0.2 + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.12.7 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + optional: true + jest-worker@25.5.0: dependencies: merge-stream: 2.0.0 @@ -29977,6 +30657,19 @@ snapshots: - ts-node - utf-8-validate + jest@29.7.0(@types/node@20.12.7): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@20.12.7) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + optional: true + jiti@1.21.0: {} jiti@1.21.6: {} @@ -31610,7 +32303,7 @@ snapshots: minipass-fetch@3.0.4: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 minipass-sized: 1.0.3 minizlib: 2.1.2 optionalDependencies: @@ -31639,7 +32332,7 @@ snapshots: minipass@5.0.0: {} - minipass@7.0.4: {} + minipass@7.1.2: {} minizlib@2.1.2: dependencies: @@ -32032,7 +32725,7 @@ snapshots: dependencies: growly: 1.3.0 is-wsl: 2.2.0 - semver: 7.6.0 + semver: 7.6.3 shellwords: 0.1.1 uuid: 8.3.2 which: 2.0.2 @@ -32517,6 +33210,8 @@ snapshots: degenerator: 5.0.1 netmask: 2.0.2 + package-json-from-dist@1.0.1: {} + package-json@6.5.0: dependencies: got: 9.6.0 @@ -32690,10 +33385,10 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.10.2: + path-scurry@1.11.1: dependencies: lru-cache: 10.2.0 - minipass: 7.0.4 + minipass: 7.1.2 path-to-regexp@0.1.10: {} @@ -33088,9 +33783,9 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-sorting@8.0.2(postcss@8.4.38): + postcss-sorting@8.0.2(postcss@8.4.47): dependencies: - postcss: 8.4.38 + postcss: 8.4.47 postcss-svgo@4.0.3: dependencies: @@ -33371,6 +34066,9 @@ snapshots: dependencies: escape-goat: 4.0.0 + pure-rand@6.1.0: + optional: true + q@1.5.1: {} qs@6.10.4: @@ -34192,7 +34890,7 @@ snapshots: read-package-json@6.0.4: dependencies: - glob: 10.3.12 + glob: 10.4.5 json-parse-even-better-errors: 3.0.1 normalize-package-data: 5.0.0 npm-normalize-package-bin: 3.0.1 @@ -35306,6 +36004,12 @@ snapshots: source-map-url: 0.4.1 urix: 0.1.0 + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + optional: true + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -35394,7 +36098,7 @@ snapshots: ssri@10.0.5: dependencies: - minipass: 7.0.4 + minipass: 7.1.2 ssri@7.1.1: dependencies: @@ -35659,8 +36363,8 @@ snapshots: stylelint-order@6.0.4(stylelint@16.3.1(typescript@5.6.3)): dependencies: - postcss: 8.4.38 - postcss-sorting: 8.0.2(postcss@8.4.38) + postcss: 8.4.47 + postcss-sorting: 8.0.2(postcss@8.4.47) stylelint: 16.3.1(typescript@5.6.3) stylelint-prettier@5.0.0(prettier@3.2.5)(stylelint@16.3.1(typescript@5.6.3)): @@ -35720,7 +36424,7 @@ snapshots: dependencies: '@jridgewell/gen-mapping': 0.3.5 commander: 4.1.1 - glob: 10.3.12 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 @@ -36699,6 +37403,13 @@ snapshots: convert-source-map: 1.9.0 source-map: 0.7.4 + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + optional: true + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0