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 @@
+
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.