From 70559c188399b9b38254a512ec7eb9ef529b0101 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sat, 12 Oct 2024 12:47:32 +0200 Subject: [PATCH 01/89] WIP Update README for @plone/registry, added utilities registry --- packages/registry/README.md | 154 +++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 9 deletions(-) diff --git a/packages/registry/README.md b/packages/registry/README.md index 552133573c..1408f6559e 100644 --- a/packages/registry/README.md +++ b/packages/registry/README.md @@ -16,14 +16,14 @@ An add-on registry is a facility that allows an app, which was built on an exist The add-on registry is a store where you can register a number of add-ons that your app consumes. -Add-on packages are just CommonJS packages. +Add-on packages are just CommonJS/ESM 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 +### 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: @@ -40,16 +40,22 @@ This is done in your app's `package.json`'s `addons` key: ``` 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. +In that function, the add-on can customize the configuration 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`. +The default export of any add-on `main` entry module in `package.json` (eg. `src/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. +```ts +import type { ConfigType } from '@plone/registry' + +export default applyConfig(config: ConfigType) => config) +``` + ## Configuration registry The configuration registry supplements the add-on registry. @@ -106,7 +112,13 @@ export default function applyConfig(config: ConfigData) { 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 +### Initialization + +**TODO** +By default, the configuration registry is empty. +In the context of a Volto app, the registry gets initialized by Volto by default. + +### Accessing the configuration registry The configuration registry can be accessed by: @@ -118,16 +130,17 @@ const blocksConfig = config.blocks.blocksConfig ## Component registry +The configuration registry can also store special elements that can be queried and retrieved in a pluggable way. 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. +This provides an alternative, and more convenient, way to customize components, in a pluggable way. 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` +### Register components by name using `config.registerComponent` You can register components by name, typically from an add-on or project configuration: @@ -140,7 +153,7 @@ config.registerComponent({ }); ``` -## Retrieve a component from the component registry +### Retrieve a component from the component registry You can programmatically retrieve a component from the registry using `config.getComponent`: @@ -158,7 +171,7 @@ 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 +### Adapt the component using the `dependencies` array You can register components, then retrieve them, given a list of modifiers using `dependencies`. @@ -205,3 +218,126 @@ In the example, given a content type value coming from the `content` prop, you w ```jsx ``` + +### Lazy load components +**TODO** Test it properly + +You could lazy load the component too in the registry, if you need it. + +```js +const MyTeaserDefaultComponent = lazy(()=> import(./MyTeaserDefaultComponent)) + +config.registerComponent({ + name: 'Teaser', + component: MyTeaserDefaultComponent, + }); +``` + +## Utilities registry + +The configuration registry also stores a utilities registry in itself. +The components registry is a mapping of a `name` and a `type` to a method or function. +It works in a similar way as the components registry, but for methods and functions and by adding an additional query argument `type`. + +### Register utilities using `config.registerUtility` + +You register an utility using a specific `name` and `type` arguments. + +```js +config.registerUtility({ + name: 'url', + type: 'validator', + method: () => 'this is a simple validator utility', +}); +``` + +For a same `type` you can register different `name` utilities. + +```js +config.registerUtility({ + name: 'url', + type: 'validator', + method: () => 'this is a simple validator utility', +}); + +config.registerUtility({ + name: 'email', + type: 'validator', + method: () => 'this is a simple validator utility', +}); +``` + +However, registering two utilities under the same `name`, the latter will override the former. +So 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 will prevail, as defined later', +}); +``` + +### Register utilities using a `dependencies` object + +It is possible to register utilities using a `dependencies` object. +This is useful in order to narrow even more the specificity of the utility. + +```js +config.registerUtility({ + name: 'email', + type: 'validator', + dependencies: { fieldType: 'email' }, + method: () => 'this is a validator utility with dependencies for email', +}); +``` + +### Retrieve an utility from the utilities registry + +You can retrieve one specific utility using `config.getUtility`, given the `name` and `type`. + +```js +config.getUtility({ name: 'url', type: 'validator' }).method(), +``` + +or using a `dependencies` object: + +```js +config.getUtility({ + name: 'email', + dependencies: { fieldType: 'string' }, + type: 'validator', +}).method(), +``` + +### Retrieve all the utilities of the same `type` + +You can also retrieve all the utilities registered under the same `type`. + +```js +config.getUtilities({ type: 'validator' }) +``` + +or given 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 for the `fieldType` `string`. + +### + +## Shadow components registry + +**TODO** From db99028379195f351d8cf0ad7eaca7823fcdd79b Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 11:32:04 +0200 Subject: [PATCH 02/89] First implementation of moving addon registry to TS and ESM. --- apps/plone/.eslintrc.js | 3 +- package.json | 2 +- .../registry/{.eslintrc.js => .eslintrc.cjs} | 6 +- .../registry/__tests__/addon-registry.test.js | 265 +++++++ .../__tests__/create-addons-loader.test.js | 128 +++ .../addons/my-volto-config-addon/package.json | 10 + .../my-volto-config-addon/src/testaddon.js | 0 .../addons/non-volto-addon-lib/index.js | 1 + .../addons/non-volto-addon-lib/package.json | 5 + .../addons/test-addon/package.json | 10 + .../test-released-source-addon/index.js | 1 + .../src/custom-addons/volto/server.jsx | 1 + .../addons/test-addon/src/testaddon.js | 0 .../addons/test-released-dummy/index.js | 1 + .../addons/test-released-dummy/package.json | 8 + .../fixtures/test-volto-project/jsconfig.json | 11 + .../fixtures/test-volto-project/package.json | 12 + .../package.json | 4 + .../src/index.js | 1 + .../src/custom-addons/test-addon/testaddon.js | 1 + .../src/custom-addons/volto/client.js | 1 + .../src/customizations/LanguageSwitcher.js | 1 + .../src/customizations/TSComponent.jsx | 0 .../src/customizations/client.js | 1 + .../src/customizations/routes.tsx | 0 .../src/customizations/server.jsx | 1 + .../fixtures/test-volto-project/src/marker.js | 0 .../test-volto-project/volto.config.js | 3 + .../__tests__/fixtures/volto-addon1.js | 14 + .../__tests__/fixtures/volto-addon2.js | 29 + .../__tests__/fixtures/volto-addon3.js | 41 + packages/registry/package.json | 26 +- ...don-registry.js => addon-registry.old.cjs} | 0 .../src/addon-registry/addon-registry.ts | 727 ++++++++++++++++++ .../create-addons-loader.cjs} | 0 .../create-theme-addons-loader.cjs} | 0 packages/registry/src/index.ts | 5 +- packages/registry/tsconfig.json | 2 +- packages/registry/tsconfig.node.json | 24 + packages/volto/razzle.config.js | 4 +- pnpm-lock.yaml | 9 + 41 files changed, 1344 insertions(+), 14 deletions(-) rename packages/registry/{.eslintrc.js => .eslintrc.cjs} (91%) create mode 100644 packages/registry/__tests__/addon-registry.test.js create mode 100644 packages/registry/__tests__/create-addons-loader.test.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/non-volto-addon-lib/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/test-released-source-addon/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/volto/server.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/testaddon.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/addons/test-released-dummy/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/jsconfig.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/packages/test-local-packages-via-addons-env-var/src/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/test-addon/testaddon.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/custom-addons/volto/client.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/customizations/LanguageSwitcher.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/customizations/TSComponent.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/customizations/client.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/customizations/routes.tsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/customizations/server.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/src/marker.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/volto.config.js create mode 100644 packages/registry/__tests__/fixtures/volto-addon1.js create mode 100644 packages/registry/__tests__/fixtures/volto-addon2.js create mode 100644 packages/registry/__tests__/fixtures/volto-addon3.js rename packages/registry/src/{addon-registry.js => addon-registry.old.cjs} (100%) create mode 100644 packages/registry/src/addon-registry/addon-registry.ts rename packages/registry/src/{create-addons-loader.js => addon-registry/create-addons-loader.cjs} (100%) rename packages/registry/src/{create-theme-addons-loader.js => addon-registry/create-theme-addons-loader.cjs} (100%) create mode 100644 packages/registry/tsconfig.node.json diff --git a/apps/plone/.eslintrc.js b/apps/plone/.eslintrc.js index 6148e2e62f..a451dd82d8 100644 --- a/apps/plone/.eslintrc.js +++ b/apps/plone/.eslintrc.js @@ -1,6 +1,7 @@ const fs = require('fs'); const projectRootPath = __dirname; -const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); +const AddonConfigurationRegistry = + require('@plone/registry/addon-registry').default; let voltoPath = './node_modules/@plone/volto'; diff --git a/package.json b/package.json index c5be31bcc6..4c929550a3 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build": "pnpm --filter @plone/volto build", "start": "pnpm --filter @plone/volto start", "start:project": "pnpm --filter plone run start", - "lint": "pnpm build:all && eslint --max-warnings=0 '{apps,packages}/**/*.{js,jsx,ts,tsx}'", + "lint": "make build-deps && eslint --max-warnings=0 '{apps,packages}/**/*.{js,jsx,ts,tsx}'", "lint:volto": "pnpm --filter @plone/volto run lint", "test": "pnpm --filter @plone/volto run test", "test:ci": "pnpm --filter @plone/volto run test:ci", diff --git a/packages/registry/.eslintrc.js b/packages/registry/.eslintrc.cjs similarity index 91% rename from packages/registry/.eslintrc.js rename to packages/registry/.eslintrc.cjs index b60637a973..4505e9e715 100644 --- a/packages/registry/.eslintrc.js +++ b/packages/registry/.eslintrc.cjs @@ -61,7 +61,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/__tests__/addon-registry.test.js b/packages/registry/__tests__/addon-registry.test.js new file mode 100644 index 0000000000..36abf9a8e0 --- /dev/null +++ b/packages/registry/__tests__/addon-registry.test.js @@ -0,0 +1,265 @@ +import path from 'path'; +import AddonConfigurationRegistry, { + 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('AddonConfigurationRegistry - Project', () => { + it('works in a mock project directory', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const reg = new AddonConfigurationRegistry(base); + + const voltoPath = `${base}/node_modules/@plone/volto`; + + expect(reg.projectRootPath).toStrictEqual(base); + expect(reg.voltoPath).toStrictEqual(voltoPath); + + expect(reg.addonNames).toStrictEqual([ + 'test-addon', + 'test-released-addon', + 'test-released-source-addon', + 'my-volto-config-addon', + 'test-released-dummy', + 'test-released-unmentioned', + ]); + + expect(reg.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 reg = new AddonConfigurationRegistry(base); + expect(reg.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 reg = new AddonConfigurationRegistry(base); + expect(reg.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 reg = new AddonConfigurationRegistry(base); + const addons = reg.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 reg = new AddonConfigurationRegistry(base); + expect(reg.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 reg = new AddonConfigurationRegistry(base); + expect(reg.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 reg = new AddonConfigurationRegistry(base); + expect( + Object.keys(reg.packages).includes('test-released-via-addons-env-var'), + ).toBe(true); + }); +}); 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..35f3d839bc --- /dev/null +++ b/packages/registry/__tests__/create-addons-loader.test.js @@ -0,0 +1,128 @@ +import getLoader from '../src/addon-registry/create-addons-loader.cjs'; +import { describe, expect, test } from 'vitest'; + +describe('create-addons-loader code generation', () => { + test('no addon creates simple loader', () => { + const code = getLoader.getAddonsLoaderCode([]); + 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 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 = getLoader.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', + ]); + 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 = getLoader.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 = getLoader.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 = getLoader.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 = getLoader.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-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/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js b/packages/registry/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js new file mode 100644 index 0000000000..e69de29bb2 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/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.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/package.json b/packages/registry/package.json index 1132e4ede8..d911d8f490 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -31,16 +31,22 @@ "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" + }, ".": { "types": "./dist/types.d.ts", - "import": "./dist/module.js", - "require": "./dist/main.js" + "import": "./dist/index.js", + "require": "./dist/index.cjs" } }, "targets": { @@ -50,9 +56,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": "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 && mv dist/cjs/addon-registry.js dist/cjs/addon-registry.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", @@ -77,6 +86,9 @@ "@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", "parcel": "^2.12.0", diff --git a/packages/registry/src/addon-registry.js b/packages/registry/src/addon-registry.old.cjs similarity index 100% rename from packages/registry/src/addon-registry.js rename to packages/registry/src/addon-registry.old.cjs diff --git a/packages/registry/src/addon-registry/addon-registry.ts b/packages/registry/src/addon-registry/addon-registry.ts new file mode 100644 index 0000000000..42e53d8d9a --- /dev/null +++ b/packages/registry/src/addon-registry/addon-registry.ts @@ -0,0 +1,727 @@ +/* eslint no-console: 0 */ +import { sync 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'); + +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 Aliases = Record; +type CoreAddons = { [x: string]: { package: string } }; +type PackageJsonObject = { + addons: Array; + coreAddons: CoreAddons; + theme: string; + customizationPaths: string[]; +}; + +function getPackageBasePath(base: string) { + while (!fs.existsSync(`${base}/package.json`)) { + base = path.join(base, '../'); + } + return path.resolve(base); +} + +function fromEntries(pairs: [string, any][]) { + const res: { [key: string]: any } = {}; + pairs.forEach((p) => { + res[p[0]] = p[1]; + }); + return res; +} + +function buildDependencyGraph( + addons: Array, + extractDependency: (name: string) => Array, +) { + // getAddonsLoaderChain + const graph = new DepGraph({ circular: true }); + graph.addNode('@root'); + + const seen = ['@root']; + const stack: Array<[string, Array]> = [['@root', addons]]; + + while (stack.length > 0) { + const [pkgName, addons] = stack.shift() || []; + if (pkgName && addons) { + 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 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; +} + +/** + * Given an addons loader string, it generates an addons loader string with + * a resolved chain of dependencies + */ +function getAddonsLoaderChain(graph: DepGraph) { + return graph.dependenciesOf('@root').map((name) => { + const extras = graph.getNodeData(name) || [].join(','); + return extras.length ? `${name}:${extras}` : name; + }); +} + +/** + * A registry to discover and publish information about the structure of Volto + * projects and their addons. + * + * The registry builds information about both addons and other development + * packages, structured as an object with the following information: + * + * - name + * - isPublishedPackage (just for info, to distinguish addons from other Javascript development packages) + * - modulePath (the path that would be resolved from Javascript package name) + * - packageJson (the path to the addon's package.json file) + * - extraConfigLoaders (names for extra functions to be loaded from the addon + * for configuration purposes) + * - razzleExtender (the path to the addon's razzle.extend.js path, to allow + * addons to customize the webpack configuration) + * + */ +class AddonConfigurationRegistry { + public packageJson: PackageJsonObject; + public voltoConfigJS: { + addons: Array; + theme: string; + }; + 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 = require( + path.join(projectRootPath, 'package.json'), + )); + this.voltoConfigJS = { + addons: [], + theme: '', + }; + // 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'), + ); + } + + this.projectRootPath = projectRootPath; + this.isVoltoProject = packageJson.name !== '@plone/volto'; + this.voltoPath = + packageJson.name === '@plone/volto' + ? `${projectRootPath}` + : `${projectRootPath}/node_modules/@plone/volto`; + + this.coreAddons = + packageJson.name === '@plone/volto' + ? packageJson.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: string) => s.split(':')[0], + ); + this.packages = {}; + this.customizations = new Map(); + + // Theme from an ENV VAR, from volto.config.js or from a package.json key + // in this order of preference + this.theme = + process.env.THEME || this.voltoConfigJS.theme || packageJson.theme; + + this.initDevelopmentPackages(); + this.initPublishedPackages(); + this.initAddonsFromEnvVar(); + + this.dependencyGraph = buildDependencyGraph( + [ + ...(Object.keys(this.coreAddons).map( + (key) => this.coreAddons[key].package, + ) || []), + ...this.resultantMergedAddons, + ...(process.env.ADDONS ? process.env.ADDONS.split(';') : []), + ], + (name) => { + this.initPublishedPackage(name); + return this.packages[name].addons || []; + }, + ); + + this.initAddonExtenders(); + } + + /** + * Gets the `tsconfig.json` `compilerOptions.baseUrl` and `compilerOptions.paths` + * Returns a tuple `[baseUrl, pathsConfig]` + * + */ + 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: Record | undefined; + let baseUrl: string = ''; + if (configFile) { + const jsConfig = JSON.parse( + fs.readFileSync(configFile, 'utf-8'), + ).compilerOptions; + pathsConfig = jsConfig.paths; + baseUrl = jsConfig.baseUrl; + } + + return [baseUrl, pathsConfig]; + } + + /** + * Volto is able to register packages while in development thanks to + * `mrs-developer`. It uses the JS/TS environment config `compilerOptions.paths` + * to resolve them through the system. + */ + initDevelopmentPackages() { + const [, pathsConfig] = this.getTSConfigPaths(); + if (pathsConfig) { + // We only initialize the development addons present in `compilerOptions.paths` + // configured by `mrs-developer` filtered by the add-ons registered (via config) + // since we can have other paths there that are not addons + const developmentAddons = this.addonNames.filter((key) => + pathsConfig.hasOwnProperty(key), + ); + + developmentAddons.forEach((name) => { + this.initDevelopmentPackage(name); + }); + } + } + + /** + * Given an add-on name, it registers it as a development package + * + */ + initDevelopmentPackage(name: string) { + const [baseUrl, pathsConfig] = this.getTSConfigPaths(); + 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); + } + } + + /** + * Add path to the "src" of npm-released packages. These packages can + * release their source code in src, or transpile. The "main" of their + * package.json needs to point to the module that exports `applyConfig` as + * default. + */ + initPublishedPackages() { + this.addonNames.forEach(this.initPublishedPackage.bind(this)); + } + + 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 && pathsConfig.hasOwnProperty(name)) { + return this.initDevelopmentPackage(name); + } + + if (!Object.keys(this.packages).includes(name)) { + const resolved = require.resolve(name, { paths: [this.projectRootPath] }); + const basePath = getPackageBasePath(resolved); + const packageJson = path.join(basePath, 'package.json'); + 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: Array = pkg.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 packageTSConfig = this.getTSConfigPaths(basePath); + + this.packages[name] = { + name, + version: pkg.version, + isPublishedPackage: true, + isRegisteredAddon: this.addonNames.includes(name), + modulePath, + packageJson, + basePath, + tsConfigPaths: packageTSConfig[1] ? packageTSConfig : null, + addons: pkg.addons || [], + }; + } + } + + initAddonsFromEnvVar() { + if (process.env.ADDONS) { + process.env.ADDONS.split(';').forEach( + this.initAddonFromEnvVar.bind(this), + ); + } + } + + // An add-on from the ADDONS env var can only be a published one + initAddonFromEnvVar(name: string) { + const normalizedAddonName = name.split(':')[0]; + this.initPublishedPackage(normalizedAddonName); + } + + /** + * Allow addons to provide various extenders. + * + * The razzle.extend.js modules (named razzle.extend.js) needs to provide + * two functions: + * `plugins(defaultPlugins) => plugins` and + * `modify(...) => config` + * + * The eslint.extend.js + */ + initAddonExtenders() { + this.getAddons().forEach((addon) => { + const base = path.dirname(addon.packageJson); + const razzlePath = path.resolve(`${base}/razzle.extend.js`); + if (fs.existsSync(razzlePath)) { + addon.razzleExtender = razzlePath; + } + const eslintPath = path.resolve(`${base}/eslint.extend.js`); + if (fs.existsSync(eslintPath)) { + addon.eslintExtender = eslintPath; + } + }); + } + + /** + * Returns the addon records, respects order from package.json:addons + */ + getAddons() { + return this.dependencyGraph + .dependenciesOf('@root') + .map((name) => this.packages[name]); + } + + getAddonExtenders() { + return this.getAddons() + .map((o) => o.razzleExtender) + .filter((e) => e); + } + + getEslintExtenders() { + return this.getAddons() + .map((o) => o.eslintExtender) + .filter((e) => e); + } + + getCustomThemeAddons() { + const customThemeAddonsInfo: { + variables: string[]; + main: string[]; + } = { + variables: [], + main: [], + }; + + this.getAddonDependencies().forEach((addon) => { + const normalizedAddonName = addon.split(':')[0]; + // We have two possible insertion points, variables and main + + const customThemeVariables = `${this.packages[normalizedAddonName].modulePath}/theme/_variables.scss`; + const customThemeMain = `${this.packages[normalizedAddonName].modulePath}/theme/_main.scss`; + if ( + fs.existsSync(customThemeVariables) && + normalizedAddonName !== this.theme + ) { + customThemeAddonsInfo.variables.push(normalizedAddonName); + } + if ( + fs.existsSync(customThemeMain) && + normalizedAddonName !== this.theme + ) { + customThemeAddonsInfo.main.push(normalizedAddonName); + } + }); + + return customThemeAddonsInfo; + } + + /** + * Returns a list of aliases given the defined paths in `tsconfig.json` + */ + getAliasesFromTSConfig(basePath: string, tsConfig: [string, any]) { + const [baseUrl, options] = tsConfig; + const fullPathsPath = baseUrl ? `${basePath}/${baseUrl}` : basePath; + + const aliases: { [x: string]: string } = {}; + Object.keys(options || {}).forEach((item) => { + const name = item.replace(/\/\*$/, ''); + // webpack5 allows arrays here, fix later + const value = path.resolve( + fullPathsPath, + options[item][0].replace(/\/\*$/, ''), + ); + + aliases[name] = value; + }); + + return aliases; + } + + /** + * Returns a mapping name:diskpath to be uses in webpack's resolve aliases. + * It includes all registered add-ons and their `src` paths, and also the paths + * defined in the `tsconfig.json` files of the add-ons. + */ + getResolveAliases() { + const pairs: [string, string][] = Object.keys(this.packages).map((o) => [ + o, + this.packages[o].modulePath, + ]); + + let aliasesFromTSPaths = {}; + Object.keys(this.packages).forEach((o) => { + if (this.packages[o].tsConfigPaths) { + aliasesFromTSPaths = { + ...aliasesFromTSPaths, + ...this.getAliasesFromTSConfig( + this.packages[o].basePath || '', + this.packages[o].tsConfigPaths, + ), + }; + } + }); + + return { ...fromEntries(pairs), ...aliasesFromTSPaths }; + } + + /** + * Retrieves a mapping (to be used in resolve aliases) of module name to + * filestype paths, to be used as customization paths. + * + * There are two styles of customizations: "classic style" and "addons style" + * + * In the classic style, the customization folders are only used to customize + * Volto itself, so inside it we'll find a mirror of the Volto project + * structure. + * + * In the "addons style" customization folder, we allow customization of + * addons along with Volto customization, so we need separate folders. By + * convention, we use a folder called "volto" inside customizations folder + * and separate folder for each addon, identified by its addon (package) name. + */ + getCustomizationPaths(packageJson: PackageJsonObject, rootPath: string) { + const aliases: Aliases = {}; + let { customizationPaths } = packageJson; + if (!customizationPaths) { + customizationPaths = ['src/customizations']; + } + customizationPaths.forEach((customizationPath) => { + customizationPath = customizationPath.endsWith('/') + ? customizationPath.slice(0, customizationPath.length - 1) + : customizationPath; + const base = path.join(rootPath, customizationPath); + 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 + Object.values(this.packages).forEach((addon) => { + const { name, modulePath } = addon; + if (fs.existsSync(path.join(base, name))) { + reg.push({ + customPath: path.join(base, name), + sourcePath: modulePath, + name, + }); + } + }); + + // allow customization of modules in the `@root` namespace from addons, + // by creating a folder called `@root`. + const rootBase = path.join(base, '@root'); + if (fs.existsSync(rootBase)) + reg.push({ + customPath: rootBase, + sourcePath: `${this.projectRootPath}/src`, + name: '@root', + }); + + reg.push( + fs.existsSync(path.join(base, 'volto')) + ? { + // new style (addons) customizations + customPath: path.join(base, 'volto'), + sourcePath: `${this.voltoPath}/src/`, + name: '@plone/volto', + } + : { + // old style, customizations directly in folder + customPath: base, + sourcePath: `${this.voltoPath}/src/`, + name: '@plone/volto', + }, + ); + + reg.forEach(({ customPath, name, sourcePath }) => { + glob( + `${customPath}/**/*.*(svg|png|jpg|jpeg|gif|ico|less|js|jsx|ts|tsx)`, + ).map((filename) => { + function changeFileExtension(filePath: string) { + // Extract the current file extension + const currentExtension = filePath.split('.').pop() || ''; + + // Define the mapping between file extensions + const extensionMapping: { + [key: string]: string; + } = { + jsx: 'tsx', + tsx: 'jsx', + js: 'ts', + ts: 'js', + }; + + // Check if the current extension is in the mapping + if (currentExtension in extensionMapping) { + // Replace the current extension with the corresponding one from the mapping + const newExtension = extensionMapping[currentExtension]; + const newPath = filePath.replace( + `.${currentExtension}`, + `.${newExtension}`, + ); + return newPath; + } else { + // If the current extension is not in the mapping, return the original path + return filePath; + } + } + + /** + *Covers the use case that the file was a jsx, but was created using .js extension + * + * @param {*} filePath + */ + function simpleSwapFileExtension(filePath: string) { + // Extract the current file extension + const currentExtension = filePath.split('.').pop(); + return filePath.replace(`.${currentExtension}`, '.jsx'); + } + + const targetPath = filename.replace(customPath, sourcePath); + // We try to find the source to shadow with the exact path + // and we try also with the extension changed in search for JS<->TS + // correspondence + if ( + fs.existsSync(targetPath) || + fs.existsSync(changeFileExtension(targetPath)) || + fs.existsSync(simpleSwapFileExtension(targetPath)) + ) { + aliases[ + filename + .replace(customPath, name) + .replace(/\.(js|jsx|ts|tsx)$/, '') + ] = path.resolve(filename); + } else { + debug( + `The file ${filename} doesn't exist in the ${name} (${targetPath}), unable to customize.`, + ); + } + }); + }); + }); + + return aliases; + } + + getProjectCustomizationPaths() { + return this.getCustomizationPaths(this.packageJson, this.projectRootPath); + } + + /** + * Allow addons to customize Volto and other addons. + * + * We follow the same logic for overriding as the main `package.json:addons`. + * First declared addon gets overridden by subsequent declared addons. That + * means: customization in volto-addonA will potentially be rewritten by + * customizations in volto-addonB if the declaration in package.json is like: + * `addons:volto-addonA,volto-addonB` + */ + getAddonCustomizationPaths() { + let aliases: Aliases = {}; + this.getAddons().forEach((addon) => { + aliases = { + ...aliases, + ...this.getCustomizationPaths( + JSON.parse(fs.readFileSync(addon.packageJson, 'utf-8')), + getPackageBasePath(addon.modulePath), + ), + }; + }); + return aliases; + } + + /** + * 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: Aliases = {}; + if (process.env.ADDONS) { + process.env.ADDONS.split(';').forEach((addon) => { + const normalizedAddonName = addon.split(':')[0]; + const testingPackagePath = `${this.projectRootPath}/packages/${normalizedAddonName}/src`; + if (fs.existsSync(testingPackagePath)) { + const basePath = getPackageBasePath(testingPackagePath); + const packageJson = path.join(basePath, 'package.json'); + aliases = { + ...aliases, + ...this.getCustomizationPaths( + JSON.parse(fs.readFileSync(packageJson, 'utf-8')), + basePath, + ), + }; + } + }); + + return aliases; + } else { + return []; + } + } + + /** + * Returns an agregated, dependency-resolved list of addon loader strings + */ + getAddonDependencies() { + return getAddonsLoaderChain(this.dependencyGraph); + } + + getDotDependencyGraph() { + const seen = new Set(); + let out = `digraph { + layout="fdp" + beautify=true + sep="+10" + splines=curved + + "@root" [color = red fillcolor=yellow style=filled] +`; + const queue = ['@root']; + let name: string; + + while (queue.length > 0) { + name = queue.pop() || ''; + + const deps = this.dependencyGraph.directDependenciesOf(name); + for (let i = 0; i < deps.length; i++) { + const dep = deps[i]; + + if (!seen.has(dep)) { + seen.add(dep); + queue.push(dep); + } + out += ` "${name}" -> "${dep}"\n`; + } + } + out += '}'; + return out; + } +} + +export default AddonConfigurationRegistry; +export { getAddonsLoaderChain, buildDependencyGraph }; diff --git a/packages/registry/src/create-addons-loader.js b/packages/registry/src/addon-registry/create-addons-loader.cjs similarity index 100% rename from packages/registry/src/create-addons-loader.js rename to packages/registry/src/addon-registry/create-addons-loader.cjs diff --git a/packages/registry/src/create-theme-addons-loader.js b/packages/registry/src/addon-registry/create-theme-addons-loader.cjs similarity index 100% rename from packages/registry/src/create-theme-addons-loader.js rename to packages/registry/src/addon-registry/create-theme-addons-loader.cjs diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 830202f797..a82285c3d6 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -205,7 +205,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/volto/razzle.config.js b/packages/volto/razzle.config.js index b2ec16d7f7..c8c1d798ab 100644 --- a/packages/volto/razzle.config.js +++ b/packages/volto/razzle.config.js @@ -10,7 +10,8 @@ const RelativeResolverPlugin = require('./webpack-plugins/webpack-relative-resol 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 AddonConfigurationRegistry = + require('@plone/registry/addon-registry').default; const CircularDependencyPlugin = require('circular-dependency-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); @@ -23,7 +24,6 @@ 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 defaultModify = ({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8403edf75c..46ef319086 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1519,6 +1519,15 @@ 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 From cfe41b7306d3f1c18edb428cd6ff1d5c11d3678d Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 11:34:02 +0200 Subject: [PATCH 03/89] Removing dangling pnpmlock --- packages/registry/pnpm-lock.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/registry/pnpm-lock.yaml diff --git a/packages/registry/pnpm-lock.yaml b/packages/registry/pnpm-lock.yaml deleted file mode 100644 index e69de29bb2..0000000000 From 944d6c1ce9ee43532650ecb06b79aaa86bf688fe Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 11:40:16 +0200 Subject: [PATCH 04/89] Fix path --- packages/volto/razzle.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/volto/razzle.config.js b/packages/volto/razzle.config.js index c8c1d798ab..a9a976a076 100644 --- a/packages/volto/razzle.config.js +++ b/packages/volto/razzle.config.js @@ -8,8 +8,8 @@ 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 createAddonsLoader = require('@plone/registry/src/addon-registry/create-addons-loader'); +const createThemeAddonsLoader = require('@plone/registry/src/addon-registry/create-theme-addons-loader'); const AddonConfigurationRegistry = require('@plone/registry/addon-registry').default; const CircularDependencyPlugin = require('circular-dependency-plugin'); From 62b47662f1245934e30f4d4d949302cdadc9b8ce Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 12:03:46 +0200 Subject: [PATCH 05/89] Fixes --- packages/volto/.eslintrc | 1 - packages/volto/.storybook/main.js | 2 +- packages/volto/src/express-middleware/static.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) 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..316687fd77 100644 --- a/packages/volto/.storybook/main.js +++ b/packages/volto/.storybook/main.js @@ -102,7 +102,7 @@ module.exports = { [], defaultRazzleOptions, ); - const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); + const AddonConfigurationRegistry = require('@plone/registry/addon-registry'); const registry = new AddonConfigurationRegistry(projectRootPath); config = lessPlugin({ registry, diff --git a/packages/volto/src/express-middleware/static.js b/packages/volto/src/express-middleware/static.js index 258af2136e..227851b5f8 100644 --- a/packages/volto/src/express-middleware/static.js +++ b/packages/volto/src/express-middleware/static.js @@ -1,6 +1,6 @@ import express from 'express'; import path from 'path'; -import AddonConfigurationRegistry from '@plone/registry/src/addon-registry'; +import AddonConfigurationRegistry from '@plone/registry/addon-registry'; import config from '@plone/volto/registry'; const projectRootPath = path.resolve('.'); From b80f9d525fcd707ce650c4310e8c834f5254333d Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 12:13:42 +0200 Subject: [PATCH 06/89] Fix missing fake node_modules in fixtures --- .../node_modules/@plone/volto/src/LanguageSwitcher.jsx | 1 + .../node_modules/@plone/volto/src/TSComponent.tsx | 1 + .../node_modules/@plone/volto/src/client.js | 1 + .../node_modules/@plone/volto/src/routes.jsx | 1 + .../node_modules/@plone/volto/src/server.jsx | 0 .../node_modules/test-released-addon/index.js | 1 + .../node_modules/test-released-addon/package.json | 8 ++++++++ .../node_modules/test-released-dummy/index.js | 2 ++ .../node_modules/test-released-dummy/package.json | 5 +++++ .../node_modules/test-released-source-addon/package.json | 5 +++++ .../test-released-source-addon/razzle.extend.js | 0 .../src/customizations/@root/marker.js | 0 .../src/customizations/LanguageSwitcher.js | 1 + .../src/customizations/TSComponent.jsx | 1 + .../src/customizations/client.js | 1 + .../src/customizations/routes.tsx | 1 + .../node_modules/test-released-source-addon/src/index.js | 0 .../node_modules/test-released-unmentioned/index.js | 2 ++ .../node_modules/test-released-unmentioned/package.json | 5 +++++ .../test-released-via-addons-env-var/index.js | 2 ++ .../test-released-via-addons-env-var/package.json | 4 ++++ 21 files changed, 42 insertions(+) create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/LanguageSwitcher.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/TSComponent.tsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/client.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/routes.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/src/server.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-addon/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-dummy/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/razzle.extend.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/@root/marker.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/client.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/routes.tsx create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-unmentioned/package.json create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/index.js create mode 100644 packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-via-addons-env-var/package.json 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/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" +} From d6082393c290ced802728a9ec8168279375d931a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 12:14:49 +0200 Subject: [PATCH 07/89] Changelog --- packages/registry/news/6399.breaking | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/registry/news/6399.breaking diff --git a/packages/registry/news/6399.breaking b/packages/registry/news/6399.breaking new file mode 100644 index 0000000000..9f7074652a --- /dev/null +++ b/packages/registry/news/6399.breaking @@ -0,0 +1,3 @@ +Move to ESM @sneridagh +Unfortunately, this forces a change in imports in `razzle.config.js` and other files. +Please see the [upgrade guide](https://6.docs.plone.org/volto/upgrade-guide/index.html) From 6d82dfd2c3bbf742fe35a4245d0b8fb0c43d7ded Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 15:07:15 +0200 Subject: [PATCH 08/89] Fix tests --- docs/source/upgrade-guide/index.md | 20 +++++++++++++++++++ packages/registry/README.md | 9 +++++++++ .../__tests__/addon-registry-project.test.js | 9 +++++---- .../__tests__/addon-registry-volto.test.js | 4 ++-- .../__tests__/create-addons-loader.test.js | 2 +- .../webpack-relative-resolver.test.js | 3 ++- packages/volto/package.json | 1 + 7 files changed, 40 insertions(+), 8 deletions(-) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 39e374ea36..8aaa78ccea 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -509,6 +509,26 @@ 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. +This unfortunately forces some import paths changes that should be patched in your Plone project or add-on boilerplates. + +```{note} +As always that something changes in the boilerplate, you have the option to regenerate one from Cookieplone and move your code into it instead of fiddling with it. +``` + +in `razzle.config.js`: + + + + +```{versionadded} Volto 18.0.0-alpha.21 +``` + +```{versionadded} @plone/scripts 3.6.1 +``` + (volto-upgrade-guide-17.x.x)= ## Upgrading to Volto 17.x.x diff --git a/packages/registry/README.md b/packages/registry/README.md index 1408f6559e..a008c24db9 100644 --- a/packages/registry/README.md +++ b/packages/registry/README.md @@ -10,6 +10,15 @@ 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. +`@plone/registry` provides tools for providing plug-ability to your app: + +- Add-on registry +- Configuration registry +- Component registry +- Utilities registry +- Shadow components registry +- Slot registry + ## 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. diff --git a/packages/volto/__tests__/addon-registry-project.test.js b/packages/volto/__tests__/addon-registry-project.test.js index 08d4bad832..c36c682ab1 100644 --- a/packages/volto/__tests__/addon-registry-project.test.js +++ b/packages/volto/__tests__/addon-registry-project.test.js @@ -1,7 +1,8 @@ -const path = require('path'); -const AddonConfigurationRegistry = require('../../registry/src/addon-registry'); -const { buildDependencyGraph, getAddonsLoaderChain } = - AddonConfigurationRegistry; +import path from 'path'; +import AddonConfigurationRegistry, { + buildDependencyGraph, + getAddonsLoaderChain, +} from '@plone/registry/addon-registry'; describe('AddonConfigurationRegistry - Project', () => { jest.mock( diff --git a/packages/volto/__tests__/addon-registry-volto.test.js b/packages/volto/__tests__/addon-registry-volto.test.js index 5bea088c21..26428bede4 100644 --- a/packages/volto/__tests__/addon-registry-volto.test.js +++ b/packages/volto/__tests__/addon-registry-volto.test.js @@ -1,5 +1,5 @@ -const path = require('path'); -const AddonConfigurationRegistry = require('../../registry/src/addon-registry'); +import path from 'path'; +import AddonConfigurationRegistry from '@plone/registry/addon-registry'; describe('AddonConfigurationRegistry - Volto', () => { it('works in Volto', () => { diff --git a/packages/volto/__tests__/create-addons-loader.test.js b/packages/volto/__tests__/create-addons-loader.test.js index 570fa10a18..ec4d52c614 100644 --- a/packages/volto/__tests__/create-addons-loader.test.js +++ b/packages/volto/__tests__/create-addons-loader.test.js @@ -1,7 +1,7 @@ const fs = require('fs'); const transform = require('@babel/core').transform; -const getLoader = require('../../registry/src/create-addons-loader'); +const getLoader = require('../../registry/src/addon-registry/create-addons-loader.cjs'); describe('create-addons-loader code generation', () => { test('no addon creates simple loader', () => { diff --git a/packages/volto/__tests__/webpack-relative-resolver.test.js b/packages/volto/__tests__/webpack-relative-resolver.test.js index 429946a216..369e80bfee 100644 --- a/packages/volto/__tests__/webpack-relative-resolver.test.js +++ b/packages/volto/__tests__/webpack-relative-resolver.test.js @@ -1,5 +1,6 @@ const path = require('path'); -const AddonConfigurationRegistry = require('../../registry/src/addon-registry'); +const AddonConfigurationRegistry = + require('@plone/registry/addon-registry').default; const WebpackRelativeResolver = require('../../volto/webpack-plugins/webpack-relative-resolver'); const base = path.join(__dirname, '..'); diff --git a/packages/volto/package.json b/packages/volto/package.json index 006349cddf..966a47680d 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -85,6 +85,7 @@ "@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": "/../registry/src", "@plone/registry/(.*)$": "/../registry/src/$1", "@plone/volto": "/src/index.js", From fa9e6751b3b4b77e1c198a77bb1cc4821c80835a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 16:16:36 +0200 Subject: [PATCH 09/89] Fix i18n to match the new ESM in @plone/registry --- packages/scripts/i18n.cjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/scripts/i18n.cjs b/packages/scripts/i18n.cjs index bd4a1a1f87..bb74f3af29 100755 --- a/packages/scripts/i18n.cjs +++ b/packages/scripts/i18n.cjs @@ -283,16 +283,16 @@ function main({ addonMode }) { fs.existsSync( path.join( projectRootPath, - '/node_modules/@plone/registry/src/addon-registry.js', + '/node_modules/@plone/registry/dist/cjs/addon-registry.cjs', ), ) ) { AddonConfigurationRegistry = require( path.join( projectRootPath, - '/node_modules/@plone/registry/src/addon-registry', + '/node_modules/@plone/registry/dist/cjs/addon-registry.cjs', ), - ); + ).default; } else { // We are in Volto 17 or below // Check if core Volto or project From 9aeaf6ab312ff7f064cd8736ffbc1cc7dfcbf419 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 16:20:46 +0200 Subject: [PATCH 10/89] Fix @plone/scripts --- packages/scripts/i18n.cjs | 15 +++++++++++++++ packages/scripts/news/6399.feature | 1 + 2 files changed, 16 insertions(+) create mode 100644 packages/scripts/news/6399.feature diff --git a/packages/scripts/i18n.cjs b/packages/scripts/i18n.cjs index bb74f3af29..5cf8e2b0d5 100755 --- a/packages/scripts/i18n.cjs +++ b/packages/scripts/i18n.cjs @@ -293,6 +293,21 @@ function main({ addonMode }) { '/node_modules/@plone/registry/dist/cjs/addon-registry.cjs', ), ).default; + // Detect where is the registry (if we are in Volto 18-alpha.46 or below) + } else if ( + fs.existsSync( + path.join( + projectRootPath, + '/node_modules/@plone/registry/src/addon-registry.js', + ), + ) + ) { + AddonConfigurationRegistry = require( + path.join( + projectRootPath, + '/node_modules/@plone/registry/src/addon-registry', + ), + ); } else { // We are in Volto 17 or below // Check if core Volto or project diff --git a/packages/scripts/news/6399.feature b/packages/scripts/news/6399.feature new file mode 100644 index 0000000000..bbbedd18f3 --- /dev/null +++ b/packages/scripts/news/6399.feature @@ -0,0 +1 @@ +Support for the new @plone/registry ESM format @sneridagh From 6f26edf958763e657372993b49e5e166eee01182 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 16:59:23 +0200 Subject: [PATCH 11/89] Build all again for linting --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c929550a3..c5be31bcc6 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build": "pnpm --filter @plone/volto build", "start": "pnpm --filter @plone/volto start", "start:project": "pnpm --filter plone run start", - "lint": "make build-deps && eslint --max-warnings=0 '{apps,packages}/**/*.{js,jsx,ts,tsx}'", + "lint": "pnpm build:all && eslint --max-warnings=0 '{apps,packages}/**/*.{js,jsx,ts,tsx}'", "lint:volto": "pnpm --filter @plone/volto run lint", "test": "pnpm --filter @plone/volto run test", "test:ci": "pnpm --filter @plone/volto run test:ci", From ef4c08f484753133e3ca3d2f33df0018ec7457f1 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 13 Oct 2024 17:29:35 +0200 Subject: [PATCH 12/89] Introduce new get() method in the addon registry --- .../registry/__tests__/addon-registry.test.js | 57 +++++++++++++++++++ .../src/addon-registry/addon-registry.ts | 31 +++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/packages/registry/__tests__/addon-registry.test.js b/packages/registry/__tests__/addon-registry.test.js index 36abf9a8e0..e99959e96b 100644 --- a/packages/registry/__tests__/addon-registry.test.js +++ b/packages/registry/__tests__/addon-registry.test.js @@ -14,6 +14,63 @@ vi.mock( { virtual: true }, ); +describe('AddonConfigurationRegistry - get()', () => { + it('Basic get() information', () => { + const base = path.join( + import.meta.dirname, + 'fixtures', + 'test-volto-project', + ); + const registry = new AddonConfigurationRegistry(base).get(); + expect(registry.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(registry.shadowAliases).toStrictEqual([ + { + find: 'test-released-source-addon/index', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/test-released-source-addon/index.js', + }, + { + find: '@plone/volto/server', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/volto/server.jsx', + }, + { + find: '@root/marker', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/@root/marker.js', + }, + { + find: '@plone/volto/client', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/client.js', + }, + { + find: '@plone/volto/LanguageSwitcher', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js', + }, + { + find: '@plone/volto/routes', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/routes.tsx', + }, + { + find: '@plone/volto/TSComponent', + replacement: + '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx', + }, + ]); + expect(registry.theme).toStrictEqual(undefined); + }); +}); + describe('AddonConfigurationRegistry - Project', () => { it('works in a mock project directory', () => { const base = path.join( diff --git a/packages/registry/src/addon-registry/addon-registry.ts b/packages/registry/src/addon-registry/addon-registry.ts index 42e53d8d9a..a2cf1d31f1 100644 --- a/packages/registry/src/addon-registry/addon-registry.ts +++ b/packages/registry/src/addon-registry/addon-registry.ts @@ -22,6 +22,7 @@ type Package = { }; type Aliases = Record; +type AliasesObject = { find: string; replacement: string }[]; type CoreAddons = { [x: string]: { package: string } }; type PackageJsonObject = { addons: Array; @@ -30,6 +31,17 @@ type PackageJsonObject = { 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; +}; + function getPackageBasePath(base: string) { while (!fs.existsSync(`${base}/package.json`)) { base = path.join(base, '../'); @@ -45,6 +57,13 @@ function fromEntries(pairs: [string, any][]) { return res; } +function flatAliasesToObject(flatAliases: flatAliases): AliasesObject { + return Object.entries(flatAliases).map(([key, value]) => ({ + find: key, + replacement: value, + })); +} + function buildDependencyGraph( addons: Array, extractDependency: (name: string) => Array, @@ -123,7 +142,7 @@ function getAddonsLoaderChain(graph: DepGraph) { * addons to customize the webpack configuration) * */ -class AddonConfigurationRegistry { +class AddonRegistry { public packageJson: PackageJsonObject; public voltoConfigJS: { addons: Array; @@ -215,6 +234,14 @@ class AddonConfigurationRegistry { this.initAddonExtenders(); } + public get(): AddonRegistryGet { + return { + addons: this.getAddonDependencies(), + theme: this.theme, + shadowAliases: flatAliasesToObject(this.getAddonCustomizationPaths()), + }; + } + /** * Gets the `tsconfig.json` `compilerOptions.baseUrl` and `compilerOptions.paths` * Returns a tuple `[baseUrl, pathsConfig]` @@ -723,5 +750,5 @@ class AddonConfigurationRegistry { } } -export default AddonConfigurationRegistry; +export default AddonRegistry; export { getAddonsLoaderChain, buildDependencyGraph }; From f415b34d40b9cb837feef5878c858ac06a931be3 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Oct 2024 00:10:32 +0200 Subject: [PATCH 13/89] Refactor to TS other scripts. More polishments. Better tests --- packages/registry/Makefile | 5 ++ .../__tests__/create-addons-loader.test.js | 66 +++++++++++++---- packages/registry/package.json | 16 +++- .../src/addon-registry/addon-registry.ts | 2 +- ...ons-loader.cjs => create-addons-loader.ts} | 52 ++++++++----- ...dons-loader.cjs => create-theme-loader.ts} | 54 +++++--------- .../__tests__/create-addons-loader.test.js | 73 ++++++++++++++----- packages/volto/package.json | 1 + packages/volto/razzle.config.js | 8 +- pnpm-lock.yaml | 16 ++++ 10 files changed, 202 insertions(+), 91 deletions(-) create mode 100644 packages/registry/Makefile rename packages/registry/src/addon-registry/{create-addons-loader.cjs => create-addons-loader.ts} (66%) rename packages/registry/src/addon-registry/{create-theme-addons-loader.cjs => create-theme-loader.ts} (50%) diff --git a/packages/registry/Makefile b/packages/registry/Makefile new file mode 100644 index 0000000000..8d5f703165 --- /dev/null +++ b/packages/registry/Makefile @@ -0,0 +1,5 @@ +.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/__tests__/create-addons-loader.test.js b/packages/registry/__tests__/create-addons-loader.test.js index 35f3d839bc..60b611ad67 100644 --- a/packages/registry/__tests__/create-addons-loader.test.js +++ b/packages/registry/__tests__/create-addons-loader.test.js @@ -1,9 +1,49 @@ -import getLoader from '../src/addon-registry/create-addons-loader.cjs'; +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', () => { - 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. @@ -22,8 +62,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) => { @@ -40,17 +81,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'; @@ -59,7 +97,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'; @@ -68,9 +106,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'; @@ -79,7 +115,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', ]); @@ -93,7 +129,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'); diff --git a/packages/registry/package.json b/packages/registry/package.json index d911d8f490..872e1b5260 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -43,6 +43,16 @@ "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" + }, ".": { "types": "./dist/types.d.ts", "import": "./dist/index.js", @@ -59,7 +69,7 @@ "build": "parcel build && pnpm build:node:esm && pnpm build:node:cjs", "build:force": "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 && mv dist/cjs/addon-registry.js dist/cjs/addon-registry.cjs", + "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", @@ -80,7 +90,8 @@ "crypto-random-string": "3.2.0", "debug": "4.3.2", "dependency-graph": "0.10.0", - "glob": "7.1.6" + "glob": "7.1.6", + "tmp": "0.2.1" }, "devDependencies": { "@parcel/packager-ts": "^2.12.0", @@ -91,6 +102,7 @@ "@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", diff --git a/packages/registry/src/addon-registry/addon-registry.ts b/packages/registry/src/addon-registry/addon-registry.ts index a2cf1d31f1..2585d7d41f 100644 --- a/packages/registry/src/addon-registry/addon-registry.ts +++ b/packages/registry/src/addon-registry/addon-registry.ts @@ -7,7 +7,7 @@ import { DepGraph } from 'dependency-graph'; const debug = _debug('shadowing'); -type Package = { +export type Package = { name: string; version: string; isPublishedPackage: boolean; diff --git a/packages/registry/src/addon-registry/create-addons-loader.cjs b/packages/registry/src/addon-registry/create-addons-loader.ts similarity index 66% rename from packages/registry/src/addon-registry/create-addons-loader.cjs rename to packages/registry/src/addon-registry/create-addons-loader.ts index 7eb5f55757..92f237ea1d 100644 --- a/packages/registry/src/addon-registry/create-addons-loader.cjs +++ 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 default function getaddonsLoader( + 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/addon-registry/create-theme-addons-loader.cjs b/packages/registry/src/addon-registry/create-theme-loader.ts similarity index 50% rename from packages/registry/src/addon-registry/create-theme-addons-loader.cjs rename to packages/registry/src/addon-registry/create-theme-loader.ts index 6b46257744..dfbee08e58 100644 --- a/packages/registry/src/addon-registry/create-theme-addons-loader.cjs +++ 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 default function getThemeLoader({ + 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/volto/__tests__/create-addons-loader.test.js b/packages/volto/__tests__/create-addons-loader.test.js index ec4d52c614..b7d4ef2193 100644 --- a/packages/volto/__tests__/create-addons-loader.test.js +++ b/packages/volto/__tests__/create-addons-loader.test.js @@ -1,11 +1,50 @@ -const fs = require('fs'); -const transform = require('@babel/core').transform; - -const getLoader = require('../../registry/src/addon-registry/create-addons-loader.cjs'); +import fs from 'fs'; +import { transform } from '@babel/core'; +import getAddonsLoader, { + 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 +63,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 +82,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 +98,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 +107,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 +116,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 +130,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 +184,7 @@ function makeAddonLoader(addons, load = true) { : require.resolve(name), ); - const loaderPath = getLoader(addons); + const loaderPath = getAddonsLoader(addons); transpile(loaderPath); if (load) { diff --git a/packages/volto/package.json b/packages/volto/package.json index 966a47680d..ab4d399089 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -86,6 +86,7 @@ "@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", diff --git a/packages/volto/razzle.config.js b/packages/volto/razzle.config.js index a9a976a076..1bafdb7a97 100644 --- a/packages/volto/razzle.config.js +++ b/packages/volto/razzle.config.js @@ -8,8 +8,10 @@ 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/addon-registry/create-addons-loader'); -const createThemeAddonsLoader = require('@plone/registry/src/addon-registry/create-theme-addons-loader'); +const createAddonsLoader = + require('@plone/registry/create-addons-loader').default; +const createThemeAddonsLoader = + require('@plone/registry/create-theme-loader').default; const AddonConfigurationRegistry = require('@plone/registry/addon-registry').default; const CircularDependencyPlugin = require('circular-dependency-plugin'); @@ -290,6 +292,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/pnpm-lock.yaml b/pnpm-lock.yaml index 46ef319086..a126344618 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1509,6 +1509,9 @@ importers: glob: specifier: 7.1.6 version: 7.1.6 + tmp: + specifier: 0.2.1 + version: 0.2.1 devDependencies: '@parcel/packager-ts': specifier: ^2.12.0 @@ -1534,6 +1537,9 @@ importers: '@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.4.2) @@ -8222,6 +8228,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==} @@ -12150,10 +12159,12 @@ 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==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-cache@1.2.1: resolution: {integrity: sha512-EOeUaup5DgWKlCMhA9YFqNRIlZwoxt731jCh47WBV9fQqHgXhr3Fa55hfgIUqilIcPsfdNKN7LHjrNY+Km40KA==} @@ -17252,14 +17263,17 @@ packages: rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true 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 ripemd160@2.0.2: @@ -28676,6 +28690,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': {} From 1d3725679860d244da1375fa6a05000e576da984 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 13 Oct 2024 17:04:02 -0700 Subject: [PATCH 14/89] Create .readthedocs.yml --- packages/registry/.readthedocs.yml | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 packages/registry/.readthedocs.yml diff --git a/packages/registry/.readthedocs.yml b/packages/registry/.readthedocs.yml new file mode 100644 index 0000000000..f89fc906dd --- /dev/null +++ b/packages/registry/.readthedocs.yml @@ -0,0 +1,32 @@ +# .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" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: 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: docs/requirements.txt From 52516651018a085e5c2877026582a4ea4884c29a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Oct 2024 09:16:05 +0200 Subject: [PATCH 15/89] Fix tests --- .github/workflows/acceptance.yml | 3 +++ .../generators/app/templates/.eslintrc.js | 3 ++- .../registry/__tests__/addon-registry.test.js | 21 +++++++------------ 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 6d93708898..37d56f656b 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/packages/generator-volto/generators/app/templates/.eslintrc.js b/packages/generator-volto/generators/app/templates/.eslintrc.js index 6148e2e62f..a451dd82d8 100644 --- a/packages/generator-volto/generators/app/templates/.eslintrc.js +++ b/packages/generator-volto/generators/app/templates/.eslintrc.js @@ -1,6 +1,7 @@ const fs = require('fs'); const projectRootPath = __dirname; -const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); +const AddonConfigurationRegistry = + require('@plone/registry/addon-registry').default; let voltoPath = './node_modules/@plone/volto'; diff --git a/packages/registry/__tests__/addon-registry.test.js b/packages/registry/__tests__/addon-registry.test.js index e99959e96b..f6e9bfd9c2 100644 --- a/packages/registry/__tests__/addon-registry.test.js +++ b/packages/registry/__tests__/addon-registry.test.js @@ -33,38 +33,31 @@ describe('AddonConfigurationRegistry - get()', () => { expect(registry.shadowAliases).toStrictEqual([ { find: 'test-released-source-addon/index', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/test-released-source-addon/index.js', + replacement: `${base}/addons/test-addon/src/custom-addons/test-released-source-addon/index.js`, }, { find: '@plone/volto/server', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/addons/test-addon/src/custom-addons/volto/server.jsx', + replacement: `${base}/addons/test-addon/src/custom-addons/volto/server.jsx`, }, { find: '@root/marker', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/@root/marker.js', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/@root/marker.js`, }, { find: '@plone/volto/client', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/client.js', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/client.js`, }, { find: '@plone/volto/LanguageSwitcher', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/LanguageSwitcher.js`, }, { find: '@plone/volto/routes', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/routes.tsx', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/routes.tsx`, }, { find: '@plone/volto/TSComponent', - replacement: - '/Users/sneridagh/Development/plone/volto/packages/registry/__tests__/fixtures/test-volto-project/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx', + replacement: `${base}/node_modules/test-released-source-addon/src/customizations/TSComponent.jsx`, }, ]); expect(registry.theme).toStrictEqual(undefined); From 2b34e98a221773c1dbc9ad1a9ef293b8fa851bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 14 Oct 2024 09:18:51 +0200 Subject: [PATCH 16/89] Apply suggestions from code review Co-authored-by: Steve Piercy --- docs/source/upgrade-guide/index.md | 6 +++--- packages/registry/README.md | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 8aaa78ccea..7b5c52f413 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -512,13 +512,13 @@ 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. -This unfortunately forces some import paths changes that should be patched in your Plone project or add-on boilerplates. +This unfortunately forces some import path changes that should be patched in your Plone project or add-on boilerplates. ```{note} -As always that something changes in the boilerplate, you have the option to regenerate one from Cookieplone and move your code into it instead of fiddling with it. +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. ``` -in `razzle.config.js`: +For example, in {file}`razzle.config.js`: diff --git a/packages/registry/README.md b/packages/registry/README.md index a008c24db9..f2de08ed68 100644 --- a/packages/registry/README.md +++ b/packages/registry/README.md @@ -10,7 +10,7 @@ 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. -`@plone/registry` provides tools for providing plug-ability to your app: +`@plone/registry` provides tools to facilitate pluggability for your app through the following registries: - Add-on registry - Configuration registry @@ -56,7 +56,7 @@ 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` entry module in `package.json` (eg. `src/index.js`) file should be a function with the signature `config => config`. +The default export of any add-on `main` entry module in `package.json` (for example, `src/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. ```ts @@ -143,7 +143,7 @@ The configuration registry can also store special elements that can be queried a 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, in a pluggable way. +This provides an alternative, and more convenient, way to customize components in a pluggable way. 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. @@ -250,7 +250,7 @@ It works in a similar way as the components registry, but for methods and functi ### Register utilities using `config.registerUtility` -You register an utility using a specific `name` and `type` arguments. +You can register a utility using specific `name` and `type` arguments. ```js config.registerUtility({ @@ -276,8 +276,8 @@ config.registerUtility({ }); ``` -However, registering two utilities under the same `name`, the latter will override the former. -So you can override existing utilities in your add-ons. +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({ @@ -296,7 +296,7 @@ config.registerUtility({ ### Register utilities using a `dependencies` object It is possible to register utilities using a `dependencies` object. -This is useful in order to narrow even more the specificity of the utility. +This is useful to further specify the utility. ```js config.registerUtility({ @@ -307,7 +307,7 @@ config.registerUtility({ }); ``` -### Retrieve an utility from the utilities registry +### Retrieve a utility from the utilities registry You can retrieve one specific utility using `config.getUtility`, given the `name` and `type`. @@ -315,7 +315,7 @@ You can retrieve one specific utility using `config.getUtility`, given the `name config.getUtility({ name: 'url', type: 'validator' }).method(), ``` -or using a `dependencies` object: +You can do the same using a `dependencies` object: ```js config.getUtility({ @@ -325,15 +325,15 @@ config.getUtility({ }).method(), ``` -### Retrieve all the utilities of the same `type` +### Retrieve all utilities of the same `type` -You can also retrieve all the utilities registered under the same `type`. +You can retrieve all utilities registered under the same `type`. ```js config.getUtilities({ type: 'validator' }) ``` -or given a `dependencies` object: +You can do the same using a `dependencies` object: ```js config.getUtilities({ From c266a702c1a960a7f3a7800c85cd19dba266d1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 14 Oct 2024 09:20:11 +0200 Subject: [PATCH 17/89] Update packages/registry/news/6399.breaking Co-authored-by: Steve Piercy --- packages/registry/news/6399.breaking | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/registry/news/6399.breaking b/packages/registry/news/6399.breaking index 9f7074652a..979f93fa05 100644 --- a/packages/registry/news/6399.breaking +++ b/packages/registry/news/6399.breaking @@ -1,3 +1,3 @@ Move to ESM @sneridagh Unfortunately, this forces a change in imports in `razzle.config.js` and other files. -Please see the [upgrade guide](https://6.docs.plone.org/volto/upgrade-guide/index.html) +Please see the [Upgrade Guide](https://6.docs.plone.org/volto/upgrade-guide/index.html). From 0ec33cd81859073565999ba80cc2e5dcbe2e3a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 14 Oct 2024 09:20:17 +0200 Subject: [PATCH 18/89] Update packages/scripts/news/6399.feature Co-authored-by: Steve Piercy --- packages/scripts/news/6399.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/news/6399.feature b/packages/scripts/news/6399.feature index bbbedd18f3..5558c340af 100644 --- a/packages/scripts/news/6399.feature +++ b/packages/scripts/news/6399.feature @@ -1 +1 @@ -Support for the new @plone/registry ESM format @sneridagh +Support for the new `@plone/registry` ESM format. @sneridagh From 2bf09b478b5d42eab7c02043662e4cf2bbd610d1 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Oct 2024 09:46:38 +0200 Subject: [PATCH 19/89] Fix storybook config --- docs/source/upgrade-guide/index.md | 17 ++++++++++++++++- .../generators/app/templates/.storybook/main.js | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 7b5c52f413..3a0c540138 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -518,10 +518,25 @@ This unfortunately forces some import path changes that should be patched in you 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 {file}`razzle.config.js`: +For example, in your projects {file}`.eslintrc.js`: +```diff + const fs = require('fs'); + const projectRootPath = __dirname; +-const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); ++const AddonConfigurationRegistry = ++ require('@plone/registry/addon-registry').default; +``` +also, in the Storybook configuration {file}` +```diff + defaultRazzleOptions, + ); +- const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); ++ const AddonConfigurationRegistry = ++ require('@plone/registry/addon-registry').default; +``` ```{versionadded} Volto 18.0.0-alpha.21 ``` diff --git a/packages/generator-volto/generators/app/templates/.storybook/main.js b/packages/generator-volto/generators/app/templates/.storybook/main.js index aa094cb6d7..95430d89d1 100644 --- a/packages/generator-volto/generators/app/templates/.storybook/main.js +++ b/packages/generator-volto/generators/app/templates/.storybook/main.js @@ -104,7 +104,8 @@ module.exports = { [], defaultRazzleOptions, ); - const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); + const AddonConfigurationRegistry = + require('@plone/registry/addon-registry').default; const registry = new AddonConfigurationRegistry(projectRootPath); From e3b98b76cc90bdf2415a24fc4c01639d666186ac Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Oct 2024 10:10:08 +0200 Subject: [PATCH 20/89] Fix storybook build for main --- packages/volto/.storybook/main.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/volto/.storybook/main.js b/packages/volto/.storybook/main.js index 316687fd77..c782e0cd2e 100644 --- a/packages/volto/.storybook/main.js +++ b/packages/volto/.storybook/main.js @@ -102,7 +102,8 @@ module.exports = { [], defaultRazzleOptions, ); - const AddonConfigurationRegistry = require('@plone/registry/addon-registry'); + const AddonConfigurationRegistry = + require('@plone/registry/addon-registry').default; const registry = new AddonConfigurationRegistry(projectRootPath); config = lessPlugin({ registry, From e80b94cd5391bcd7b5e1c564c60afafe9af2c3ed Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Oct 2024 10:23:04 +0200 Subject: [PATCH 21/89] Docs in @plone/registry --- packages/registry/.eslintrc.cjs | 2 + packages/registry/.gitignore | 7 +- .../{.readthedocs.yml => .readthedocs.yaml} | 0 packages/registry/Makefile | 81 +++ .../docs/_static/Plone_logo_square.png | Bin 0 -> 30912 bytes packages/registry/docs/_static/copy.svg | 6 + packages/registry/docs/_static/cut.svg | 3 + packages/registry/docs/_static/favicon.ico | Bin 0 -> 5430 bytes packages/registry/docs/_static/logo.png | Bin 0 -> 12314 bytes packages/registry/docs/_static/logo.svg | 48 ++ packages/registry/docs/_static/logo_2x.png | Bin 0 -> 3243 bytes packages/registry/docs/_static/paste.svg | 6 + packages/registry/docs/_static/print.css | 3 + packages/registry/docs/_static/searchtools.js | 553 ++++++++++++++++++ packages/registry/docs/_static/style_menu.png | Bin 0 -> 53267 bytes .../registry/docs/_static/tooltip_plugin.png | Bin 0 -> 32878 bytes .../user-manual/blocks/add-new-block.gif | Bin 0 -> 453165 bytes .../user-manual/blocks/block-copy-cut.mp4 | Bin 0 -> 447893 bytes .../blocks/block-left-add-icon.png | Bin 0 -> 5748 bytes .../user-manual/blocks/block-paste.mp4 | Bin 0 -> 1179621 bytes .../user-manual/blocks/block-types-menu.png | Bin 0 -> 34286 bytes .../blocks/grid-block-manage-blocks.png | Bin 0 -> 22131 bytes .../blocks/grid-block-number-of-columns.png | Bin 0 -> 17488 bytes .../_static/user-manual/blocks/html-block.png | Bin 0 -> 9913 bytes .../image-block-configuration-options.png | Bin 0 -> 45184 bytes .../user-manual/blocks/image-block.png | Bin 0 -> 15931 bytes .../blocks/listing-block-configuration.png | Bin 0 -> 30490 bytes .../user-manual/blocks/listing-block.png | Bin 0 -> 34875 bytes .../blocks/map-blocks-configuration.png | Bin 0 -> 17934 bytes .../_static/user-manual/blocks/maps-block.png | Bin 0 -> 26649 bytes .../blocks/search-block-configuration.png | Bin 0 -> 38155 bytes .../user-manual/blocks/search-block.png | Bin 0 -> 39593 bytes .../blocks/table-block-configuration.png | Bin 0 -> 38049 bytes .../user-manual/blocks/table-block.png | Bin 0 -> 8443 bytes .../table-of-contents-block-configuration.png | Bin 0 -> 20870 bytes .../table-of-contents-block-with-dropdown.mp4 | Bin 0 -> 2644952 bytes ...ble-of-contents-block-without-dropdown.mp4 | Bin 0 -> 2812839 bytes .../blocks/table-of-contents-block.png | Bin 0 -> 18178 bytes .../blocks/teaser-block-configuration.png | Bin 0 -> 150691 bytes .../user-manual/blocks/teaser-block.png | Bin 0 -> 32525 bytes .../blocks/video-block-configuration.png | Bin 0 -> 19084 bytes .../user-manual/blocks/video-block.png | Bin 0 -> 106662 bytes .../user-manual/manage/link-to-items.png | Bin 0 -> 55379 bytes packages/registry/docs/conf.py | 308 ++++++++++ packages/registry/docs/index.md | 352 +++++++++++ 45 files changed, 1366 insertions(+), 3 deletions(-) rename packages/registry/{.readthedocs.yml => .readthedocs.yaml} (100%) create mode 100644 packages/registry/docs/_static/Plone_logo_square.png create mode 100644 packages/registry/docs/_static/copy.svg create mode 100644 packages/registry/docs/_static/cut.svg create mode 100644 packages/registry/docs/_static/favicon.ico create mode 100644 packages/registry/docs/_static/logo.png create mode 100644 packages/registry/docs/_static/logo.svg create mode 100644 packages/registry/docs/_static/logo_2x.png create mode 100644 packages/registry/docs/_static/paste.svg create mode 100644 packages/registry/docs/_static/print.css create mode 100644 packages/registry/docs/_static/searchtools.js create mode 100644 packages/registry/docs/_static/style_menu.png create mode 100644 packages/registry/docs/_static/tooltip_plugin.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/add-new-block.gif create mode 100644 packages/registry/docs/_static/user-manual/blocks/block-copy-cut.mp4 create mode 100644 packages/registry/docs/_static/user-manual/blocks/block-left-add-icon.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/block-paste.mp4 create mode 100644 packages/registry/docs/_static/user-manual/blocks/block-types-menu.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/grid-block-manage-blocks.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/grid-block-number-of-columns.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/html-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/image-block-configuration-options.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/image-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/listing-block-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/listing-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/map-blocks-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/maps-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/search-block-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/search-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/table-block-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/table-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/table-of-contents-block-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/table-of-contents-block-with-dropdown.mp4 create mode 100644 packages/registry/docs/_static/user-manual/blocks/table-of-contents-block-without-dropdown.mp4 create mode 100644 packages/registry/docs/_static/user-manual/blocks/table-of-contents-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/teaser-block-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/teaser-block.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/video-block-configuration.png create mode 100644 packages/registry/docs/_static/user-manual/blocks/video-block.png create mode 100644 packages/registry/docs/_static/user-manual/manage/link-to-items.png create mode 100644 packages/registry/docs/conf.py create mode 100644 packages/registry/docs/index.md diff --git a/packages/registry/.eslintrc.cjs b/packages/registry/.eslintrc.cjs index 4505e9e715..8614b4cd25 100644 --- a/packages/registry/.eslintrc.cjs +++ b/packages/registry/.eslintrc.cjs @@ -16,6 +16,8 @@ module.exports = { // Base config extends: ['eslint:recommended'], + ignorePatterns: ['docs/_static/searchtools.js'], + overrides: [ // React { 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.yml b/packages/registry/.readthedocs.yaml similarity index 100% rename from packages/registry/.readthedocs.yml rename to packages/registry/.readthedocs.yaml diff --git a/packages/registry/Makefile b/packages/registry/Makefile index 8d5f703165..1fe08a6458 100644 --- a/packages/registry/Makefile +++ b/packages/registry/Makefile @@ -1,3 +1,84 @@ +### 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/ + +## 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 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 0000000000000000000000000000000000000000..f42a1220d761a40ad9f0f0df919136f61ef729e2 GIT binary patch literal 30912 zcmYJ5V|XS_^yXvRwx8HGCU!EhZBJ}F6Wg|JO>En?CKK%Q?*4c8Lw8qqRipaq>zq@+ zb5}K0ZsUCfq??9U)!OyffW!IBJv_2APw>GA4ZVCeQvX# z(&qB=AauYo3LgQ>nEl(0h*5~3t38h}z6K^6rmA}*e23_2`<0iWuy>gMLQz3j$tc~RSWQHzL) zQcQz0dl zws;Uoek$l3lmYrLv7f(9%Lx&t*R`Nj`=%T;T)WPcXxd9Gnl3eTEsatdbL@8{umU`~L3b&<<7O?-} z`^zd$agI8Q9pT*{{rz*d)*-DzWadA4EEE{84Zpth#e`=aL4Yoanv!bPPS7*KSo3S- zjFFj+qZPFgUT6#c5=oQes}$XTN_z4styg-#%>V2HrH^MVdB9AQ%MK!i)(%4NH>=F* zFooHcftQp1zOJTNm|wT%_$i-$j=Ln9>i`V6XyC#q%xVh}2bbwXWhr6ydekRNIX1>L zqE>xXb;C{gOrk$qhFx~XM<)Kl8XUjpO_1sREY)crUzOeO-KQa%V6WoB^XD%9SDYCA z+{N*H<9+_*(Q}3CbtrR#m5K(r`^a`s?aKuUR44MJ!We%GwBRL}LXRdH`5uB{ z#4;CoL{tl2=czHJyDnVCj;-!vaz$RgEPE8J=p#UQZ|hMFvs)mKdisfugmMV6vc zDz3Qxp8H*rz#y%7n6>$Inv}uCs5OYOxh?u9uiN_y^Iyd{b67LRA0@iJ77iI|6KU*# zE~SuW(Td{YSK2)GSfy0QrTZ6ArhO7)BSCT4E54+P%Gq9}GcSq@V?Y`<9+! zAwfBU7k;0-)A=j!V5Ov)=4i!rQ&rKrSOr_qC3UTY5DQ<2L6;z zGBC4ou=Q$X2CK69u7Wm_v^1&OKY6GwA9X>@*xE;NesCE*<$?Me*-C z2&kZ7=U0&)#CXjvPJDDSkLz6~D9$2Tn}H8mt}l}E)ZO37$% zbMJ3BoYrh;KEb{~CwAj9)v|}wZQ|F|4Xbq^bCT>BTd*{7NQ!yl-!ob2?0!NwG=zLu#g?UGOA`iTeHGo{==8%j>tAVO7GW`J88EKOQh* zvGQ7uXGOkL>1XBAuyJi}_6M|38)Ro`E5RUoCZ=MnG*2t>K@)U4ex<&?0ve}-mxD_@ z`|qK0#X+9m?CsU|r~{rASdYs55=u?zL;;!Yy&1RdlI=Od{ibyKibf97oX zRo-#6aaci^p}ohBf~hH-nCCV0X-ZRh%c_c{8st!AX?fkZ=N+?Hd7a;8eW&gIvND!@ zbW0h{Vx`W+BbzRRHkz zu{{x{7FGuz@E^ELBNoUVc5;6D#AnXC&Z*frCQv|{J*P7o6LI8Z5nZS-$SiEIvw+W7 zds~A_#bpIuKv@b^os&~C$TH?_acmvaP%PjREAmxBou@xne#}VdHtBgQmVU) z_%q$FL`2pjoIT-o(zhR`gxchk_Ef{#RXmGPcRgiGUXG=Th`Oeh6z6DgZYcpC(#T*H zp|l3e538IZS>l@J8WYHIxDZO8O2}XrVWx{q^Fvd}INxWi5kgLk-FH+rr31>K4)raQ zQab?uhWaqf8-TeQfrBVbfkvHM63eF2k>bfN9YU8(cB3Z7iM=W+V@pFJ9#Y<_<07Ue zo4D1Dc>Tk?P&q~z3})#q&eB!#g$fMz^{fpi8(FGk;O&Wf*@2?~ffo;GSumsE(Pt`9 zhx7Z3xdk{xeV9yk7!5c{V;yfdQsl9Jhcu?dW#Wrs!uqSF_ZK%C29oZ-cV2eFmNkxT zOWEMp$xbGxE~hJnaa-vhtfoOf+A5_tr>#frTtGL}kh( zW}qB(NdB1#H|67&zmR6Oap7YbnYyX{gC6z1Begpf*e5JJkuH%?j<>V=R3*>wZ$jJT zw3UNrPGL)mZ%d2>!($v~G$Tl*CZBXas|>S%Q->%J>>sN9y5A#^2c0mPtcL5S(u>A5 zf6bm$C2C)C+tH23VsV|@(mO}x%5r)9u}#x*a1D3@7o1&lJW|Hpa?#!1tA^AAe^!8g z;a%MN^|dC}dm4NA|FP}6Z5K{099wBP>rh9ACFcq*IbU zN*DGEmIQfZ|H&>V3LXf_ySk#jLG3MDW!_1OBL#7Q8~`e75>(`UV zu1a7zOQjUf@RL8&IY_jm^=?LCn~#70oNo*3 z(RB{lCgg#d*j4pdOt^oK4`jA*^F}+2a3W8nkk=QsT@&Qj#Juya5SO-$vUJgLN`8{w zYYmhurZiNYcEpuW^V$nd3J1L2xt4rt4bDPu4h(yGOV$J6(zmrlhB2YSuTc=oAG9ZQ zB{}a35PsR!5~gFi^;U7;tuV6QwbQtCfqJ9$vMvi*X%&n^QsB%W)xsM3@w!+3rB3i; zLqz8Jbp*18fs@`+iw*@$yaH5Zwn@8Rl}?tnPPqV(Ql`;}vKb0-{Qi%!US6NO*&&U&Tz?XBfsqcPk?J+S(M-Cgpb^(qw|HH@NsYoeo>nt8 zXE6Vr-rp<|e;?V$KlU-rKo>jbA3!lv|9#WI*L*T z-B^`~rQ#U7OqKU_jGO^-?y3phv`aWd^38_5#M{M^n)zT$x?F6Q<)r3X$!+T!qf^W1 zazAo`mkK42g&_!aK|}r)T$yykJq^Y)Ij+v0A@F+$R-)#pnFOMj0Q-l@2sgP+YB{=M zEPSx`lWJp0`{=7h>D0e})PvF3EnKQ0=iRli;!_<&HK?ae2Ok%VPW4xGc1YIM<+BF3 zaxmf(rbk(u_Z6?-erYF*5C3KaVed{#2wN!JC=q{yDT@X9*nYWvqL!oZ?4g!X7j;oX zw*=3VeXAV&tIqRQAgKnoP8FJzx-iFud->2Mi4g!ztqDK-tCHk*2(PaSgMThCde@jk zI0->^q6VubV(EId!VBiUJN$*k=G3(`BWtY)5^J=qTT^w6eZ%uUfd+3KKQ0i_AG2bD zhy-C(nZqv2lo%h$)tci(S41h)!S;MA^7;6Q$;>Bf(BbU1SxD-PnbG6^mDE$;n_sEL zazr9S($IgNnV&f5dw+l9FvESGe4;wx{p}Hs7mdRI34>j%AQ^J$72gUYjtCPd!h;gx zy>Q5Iwy%Z~rYl$=$UCuJ_Ha}SI7Y9v*j)l=)ee_kzlY7_$C~b@hqS-0n5DFB8l=Fs zMU5y%+MUIplb<$D`5ENw8Dew$?um)A1{NVoR5W&H<_*0FLQB7BrPI;z3y zCyJf|)-jJH#phN~uu!zeuH>cn_6b${8zx!RY4mrN<22oM{)0vYztLiL=Hk&BK(@wc zHOhx0dH^$b?i?raNae|T!^V%Zcy_zuXL5o*OW-KJ8q&Q8dbY7&@p?_AqAp|JBwEox zKM#5xJ(Qzi7$hT#xwuml>eqy^9Eq-2yD*Me0E>Hjm}4Mw7>LQ0BjP@o5d!Gw+CJW3 zx^Q~>vnG~h*;2B#@ZlWEt?>b$OYclN;tU-tT1Jrc&#oYgy zZrekiL})MUV?g$W=KlE2O`~}PGccrJEc_kaVx{&u|9cAZdOy(x&8&%Fz_9#vkFkQ% z`)SigIk0J7eN-(cm&g>C#cAFt@XS}@)5OvHZ4ty1)Y8i1-FrPjRlkg~f=OP~lmkwqdjZog{}&CgU+#}&OXPyQ zWGD=S>G6`T8W|>0_~oudfmDKSey!6Z`Ajt?c%n_T%SK0?*I@Hx2>yx(CB(PE9MFo8 z2k2bRlf?*US%yAOJq_hhGkV=Rx~5H!ySd9)`WSiSM&}8W^aR>HRw?xo(gkX%aW8 zE5X#*shAciVb4SFLnjyWDdtjKW3 ztf|nQglsG-mTWlKIR6@A0GPYqNrG$l0TQ>hjVpvxPl;5mq4}d-RP;$@MdsHnWiN?( z_@x@&!`&26UKC?g=w)xhQjsA$d}LOaCeR(cRVN&tI!nNmjg7@EK4)nCbMlE=BVH(Mm_!vRi#4kblqUK^dsTpj`wp#x9|_R#S$N!iA1lX}>vOeg>JIt})xRvuI5e zfgeLBSda8_T*a8+x40^7H;I#&EF5MZXtv~*%>_2*A3nMVrWoaN+~FiI^n z52y>(h{thjk;_7$U0j(~(S=dcT^=mNXm=O-o!?;>Q-UROs(ob9FXT{VVilA_%dmGy zb)R^aPdYgi{WkrltMO1xn2BFkvcGPc1vpGs$<%}qQ4VB_c`e{lhWjI96ivnehq73r z?sA&0LKO4SDo4`HC{l2LMN&eGl=(K9TYVG5gLLg;v2jP)a>_N4cs6BdxDJ?%lPEmc zfLi`bl^LpzmWrzs(!G6pji!)A$N;XO(r9*E*#jFBl=?P<1l#o>6r0Lqu#r(^-tCY_ zE3unl1~#u*^S$IH(&{zpDUsUkhavkEe{4;6en@amYoAp$lW|>H%IM9 z?MzdiSK>7-2~1OYXO&+v<;~J|YrFLZY_~=IbRdFESk$9(_WOdrH}g%GS&O-*H!QZB zPEd}BRrM`(SURJdWpw=h1IvI2)^FjKe0+6`oMiL1?-CwY z*~)WzD31B{lbKASfnn*edDs%txX=w*K=t~dd$2>uNR^j5ROES`5v7vC$s7w~b!oAJ zj%WE754tMPDAED7J#^L>;er<-DvWyNu~o5antN1W-Pnc&e055E=$V!=S(ue`CILDO z2mMVAq2pD1D`eFUY+}7#(gTSqlae;1$gzF@z%`HGsH)fHYUYk|Ad0Hkocw4Tul(O{ zR|T|>LonaG0kzfj=-2*IGT&Bhj<>{pCJ6KtJ8T|C6zlPZcXSl4Azhy`40eG|5+1w_ z<5NGB*Jl5?rlzm#KX2gk6@qfH$eIvj%*Cf1WJe8$Yl6H8NZSWD?v@35wQwQnZyohe zGu{TC4qg4_dQw=;qFbD+kYvn{eC*a@MVQ{Ua=!;;9tsWLyg#67n%=Fu3 z-HO@ScS7+BZ_|pSvNj$kZDkM#2oyIfgZgzRj6}7oGyiAY(+|0TfxPPP6xhxfREppj z$UZXkY>@iX*DB+;7mkH(o_cx%=Bb76o_X6a!D7NGC)PVw#dv4%Z&t84SaHL^YCf7h zY-&#kdQY>+XbrF9qrz#`w8bz>4Bu;m;AbZ8$V;PhpQk9@e9GOvuU`=Q-Q(E~(qrrt zh}g-D=QxWIv6hfKkkZZ2fDJKoIv~gJvl(arLB%k?%&|}3< zDI^BM@H2U*yAKu#eztrsm1dco$?MGca3Hk#E*E48^Qge5Q1~(!xOk1-_EWftxpa6H zuM#XLgkQ0kj+>e4_ls7C9Qj7Fz3 zk)Os{jSmT72wP3#qUTc(5@8R6Y(>>F4|iFIdZ(CvmWm#fb;8x$nkeA=5Rb%`s919o zio_w%zU=y#*Z9)*W{w&|E4UJM2ECuZmuk~8jEy@?LXv?LkH%yOco`ch5kfR8SzNYx z^ebmDfN!!O)ux}htp~TNO}?pMOcXvD8_nIakXb+o;X6V~n7xrp8E6epDRQ|&7kNK@ zaSLyg^>)|(T~u&;NrIJ;8;FXX>%6R9g<~O3wTWnV+~*f0gYc8ENP0TA3G>^W<)!8V zV!?=(5rkc@2W>`_`z~!{a_t2w39XshRBJ-{!wRgMk%%0H5%F6TbSP-O2+D(B#KtF1 z-fIIq^!(hNBXEi`x zNnxddg;CIjpiqYL(^DS1y|IbyzNI_JbB=bL1MtwlYtLN=Gpis7n5T?E#b$SZIFei! zDImd^+Prw1ml^D&>cQmaC?|Zvz7A27u{q+scK%qclMrNjQ}^TV#Gy6FEJKFM+ORgN zr?vW9{f%S^Nm^$fgw9X}=>#5Tn-wou@-vfEJHn)#_Ion?eeou`+SY|8PBYF#xYn>J zKrLZ__$HJX$;M-|OyaEWfb-Iuervs|M4`!Qq9MFsy`3C~Vp^uMuZt=Fu_r%2KqcX{ ze(Y`0sx8{Q1UnY6S6{+m;x3}rTxDBLoyH^wHLe1XL4J(+aNNd4g$z8}U z`hiM3-z3RQAAyCknL``~*$d_bLDid!-yj%<{G-PXO_xHu$g(f#c`FLh@Zs6C0`vp9 zBcl9uB&ZJdT!aSCf1`Q(!9O?gQfD8zEx*V!vv3N^=TenIknA8bS4bA+zJ=rtrT##UOLVIQ2<%8QC;=Y94@ryLmiAel)toZp=&t zCE*<&rf^!qYp~X&*?ep&H&A01A9nE##QYO;p0ycupwc=G>*-xa^6U>KG&a0`W4nj7 ztr%7;P{xtyfhwxNr)sV-GLNu=p<(1KX#-euY(Y?@jld0z+>Xi=*+M|}x?pH;!ETO*oButM+B|Ex z)Y@kV15eIQOHPCp^MfTRC4-;Q@7{VWfY-YS`J{TEws#J5!^VXC*moBf`P0-4{wv0b zDz;Y_>xM2Ekd_lwWlX84ka`RY2`dslFIf&(X6x`LhAV0LnV-rcli@TkIixW9tYr3) z&hX*_+^dP@cdjQS%Z5vWaM4fi3D=0v>4F^BVs|Q9hU^`){!Bv!4chKtKBk@@a)2K= zqvdR(l0vY}OJ)}9qMu^SR~4J^kzh;gME*_op@tT??PuKS9PT5- zStsTai_E~hiUMI>b^z=4)6@3vJ14hi(#k>0V)7Ixvjw+zUc3y0p^Avgsris!&c6ZT z(e+ddrXL#CWi_jPBLTZ&f*UsFalP#hrqMFpL}H=5>3_zfr(~JHs4#~w^MB7)jrsF} z5#I=GP(Zv$w96>m8&MzVCkYp{Io|nF$1y$4rBpc8JG*A)($|)lDTvtj&!!@~`n6V% zmRk)mC+pRt#Jcg#&Rf_owzzR$^;STyHN*~GGO@RuhVekcn$|Bv6ddmajCk*@7Fsh% zt1lwRMusk%be5OSySSKZ;BK|@8Yp7@dOl7yxR{V@^K-Ot)m`;K!#~4fw>Q1Zi2cO;5!%MF+v_7w? zN#8qdk-NzA;yjv9#Z%V;gUEx`?2RJjui8Ii_Gu-WA@+=_VpTobCUlkU^>tzVLu zIeAl<;%s}8=&385IMWl48Nz$*kW?gg2so^nhU*R0a?d1$g`Vwb4M#eBPjfjbIJC7Z zbAJaW4#OTAawBOWJ4l54OkxEgB#^Feci0QMl(Fz1eN#QE@A(rH(p3_i&Ye0NC{Gas zWsY!ZUQgt7vNZj|If&H3SkxduXOn{vB-k8!T-)ST^S%Do0|Y`1nkL;NCraiV>``zT zyaM^Z=f710TNov^F!2PYOu1}U!ZAl6D%#e@Zd&q=&%>@`W%({<4ke*ZELu#6w#f@09DJN@Q+_w1d%J% z7<|kqSUllO(}ByDorU50ea+~dhR>taSij*OaNb*d-%rWCn4!7!euW*Z>_MTELiZA_ zVls|6&gvKS9FG-jdh@w8*{!C_aFOG&GFOlcw>Sn;Lx-a*o zsMpBx-QrsHt+rAzSCL3vDhPjUZn{l+d_HgQb-OEfKA!(!=ITPQ9~I_sSNioswmipv zdZ~)=1J(>rIqqfN2#0-X$I2#9s4DJe>fUOv*X@^V`)mO1zkKAE{@S9Kbyo(lLy@lJ zV8aR#W^X1MM1w%Za zXr@KaQvjF&@q%7$Jf)=Lc9WTMrEM(an*1iw@3o&01yDl*1d`NSc7&{*&EQPt(C5yv zc8@biG;Ee=eS1+e%zwZf$k+w*6-l}xQ!13$n9}id8{+#@yqSKNVT1%wHHF2$q~ece z_l8bKO-+dvh29?*b@i)PG-xMnq@J+qf)w{$N=9Nra!vFl%(}pd##kKEt5YEj$0lL2 z^j)iJX;)93sSCD|c?VnvPU)giBIdo2sw9NS@ za?HCy@24&k*Im-FPv4XCIOKnxn_JF0uol<=+(#A6H^`2jn}d|d{b zbKCz#iYs5z3Ii(YFM^5D8L2kLR{TGKtr!Y3&K>fWHuYaH1U&-vK^}0l)hN%Q(+$lvEgDMMIoeqN)7%E~FoJJq`8NU^|2JQSA?+ zTJ%KVk|Gd}w51V=^{}FT_lc#cvW`f38qYlnTm@EBKJ1ANQGT5TdWpIJ2EK-8O{(oopUv5wH|E>pQ*d)#;WJUOB8I71#=V&5+j`v_) z1y!p&-f)hXK`rojsu=xQ8j-gRRuLHv&|#UTWt^=#)!!UH!k7`MBQege$^ODfn>_H` z99QCwIfQ%Y8Ii^XttFXQ>tDo&+ixJQ(PC1n7){jC(+(X7WF5(nbmiB?O( zJO4GbF?4q;Z+pF*z)4CjIC#oYqL>A9&hVu}4UBAE!Ag^}`F%bf(?ru7del5DO-opp zYxAQ2-4Np?V5r%kS^rvdiw8vLjXyYpgMogD;Gq~F#n!_E@9$1JIf)1Z42mH_a6HMc zL<9a`t6CCJQpp{dE?{_UjNRD16(B6!w<(IBW!FLu(OAA6{jd0Uk?4nW56G*cTxs?OFyfg{Ghw(XRdf9TgFvF-8X`cm`tIhQA*Jl7;uo6lpxn+S2oWz&m@wjb_ zPzUQ@4jYv6!5pI;^V=uS{_KdnE^pf{*vgc{02LIcqCdp81)|ya-hBcT3KO%qB8C93 z5>#F0_>qJ?8{`h+{AKjKpOVLEIJ83CWsD!ovb;D98k)V2EORGbLW#c?=P`W$#p0q= z%u~-{o^|-8P;`rpr89FgKdU)rQqR}S9{O#)PZ8yUsj?lqwMb-h_cao|@6Aj-bVA)9 zo1eBl*@8DQUM^jAZiR|i2GIU8%MQ{>(aI0dNHCbo2qc&nbmFVP@08fCB9E9A{78-v zWl9v6B$rd)3kk{9>euO=f_^7o;2kY$CarS_MyIAFY2nPwQHOhydU^tCm(pBv;1kPY z#h%qb|3yB3U()sp1=mJZBIWd$K}4B);&I5OdU7MPH-8$Ar*7h!cGakzw;Ipk*X>8s!!TVN zOf#a^jV2~oN03uGDoDc#zQAU2FPh z%Aoi-WCrJMZkw1k_=)9qt?RcM-R+L2(BF94E ziQ%uB1mTcfnO<%bv#fj{-EYkg78bmUG!e=mrKYWcJVPyrD6uP~M^zOLUy-6lIV}s+ zMJfXow3Y%5QbccGj!jf^U+w}=j0!M6Foyy5Y5WBw`sZ;a$kP(nMI~t%{s10q+R+GY z`!MF;GpWkpl{` z&WVU=`s^FXf9SS3L-KsKKg(Kv1uWL!#pX%Nqz-ELE*F{Gj*;+9L;sa2WsS3j(Ri7l zB+OEmW{du|K>l%2_YB?ZE+o!YomwLtO=pWTMpBB1C`v};C*y&7;*+KfEMfjE& ziHE#y?v^JmV`~Fi`)S)J8VLs@!f4I7-QWdTc4vuD;KIRR0XA$(lrsz>?k45T*lKw*_r6M# ziM>P)lfof#c$H~NFe}12&LLn7enRBqfha@?WnNnuqN0Qc^A0FgtA~6|O5S&^(kT2z z@|1u+AHFzr3Ag9@)JY>{I`z6sN=Q0Nu3xN~|2ImQz2WYZ7-?vVu*|k502B-5u2j1zfZ&vr259flFpN)P*`q)-QHsXzeMAC5G!ybs z*c^(LA>K%~X`J~Dlt}u$|L>vpARLLfQ+T-lY-nh@CwVMcKj!fNG{U5y8J@zo!T=gt z$;89IL14gw2&wtbk0Ky_V2L7_#l`>@VBuIECGjMHrdRpsS8u*>0N{sGLXpUBUZ>9A1hUs zDKP`(5{`|Y{IiN^u*0pLD#Qd?C50DqV0R!!p_vtSeRm0iD81t{X-0Hcip!PWUPlP` zs4|54PGn9xV1Yg3%?r+Bnc8s6I~F10l{QKI^3vx6Y5=&kf`M-mkvqwMM|skD{}OzR zz1W;w8m9C40|opoMkGEWI|g_YamcPZCqv(LY92=wDK*WM1Kln+6G{Fl;=}{(PyO0- zKt#-wxh+%ZxdCxP$KkbX&wTLbiuAS!ZDvZFjs^0ou84R$okS-lC2Yi^%cjJ@?5YQR zMC;l%Of5--<4wcioEcIUsO^;xNU6zF6&Ir&(Z8V?-X0D2Ilzy!j&))Cf591k{X0O- zn>sg#6p1CK=}v9Y9e#}1%sB=a#s`%})O|ZuV3dgx9n399GVv+lZ*O5mcDH?3xWIgs z1hgoguNfppZy^{tG1XL$Ft>-_blmpPu|Xg3yMe*HyRRfjB;;GVuqTa4{9)#^FXu!3QmDf;~1R zU&HzesjyWMm^zqB^1xN%2T(yxA^13avJ%&7=U$eTylne{u5wtS&LN}}pq|~XYV+mT zh7kiEy;`9of4T52aqLCqi(JIm;CoWN2qj1jYdB~<5-Q5K7n>kdCq^IsN?EWbwihDDtk2B2X$crJj*Fgd z1&ZdXpfobD9u8l>>7AHY{W#M**|_`+;qUK~KO*?o)?PM3^PHJy#}07$%U?obj&NI9 znx%=9q_6DOb1mAP7l>P+IY;VFjSha(_i1`dgt-~&*aUKw&)20=o;qEXQfWuY>?G2W+QmUyG>+}Z8+cOb{wh4r!0z> zKL|6-eLx+jCitt?ox1sfzWx&nFAta0pjl%c8B$#X^EksfiA-!yx|x_wH1CGZ0nD^m zAMnUrN3!H?3#edOI?3VEx`e4g(VPiRB;w=x#H8XV39c@f;e8-_rbGZN__oIczzX*<+bw!+|K`l3-Zku1v~2x3?27 z^r^I`wU(}tForl2^08fdo!|D-Y+iqRYX#e6I67@WD0%a3%4|C5f)5|7HYQF~ig_d* z4EJEM#byUxlKB&RZfMp3x^?mQ)_n8p3tMZz*%C&_vLO%%gjdEej3-)%@l{Cqgyf9; zoA6s2&R?@X`SO;A7E{-Kzt7MhydxLAL$K$i?-=JGa5^2mIM##O|#pA+e`^FE|H@f zR|sCo|7HQUg#4al(R(tVPxgEM`N}D0WvrB3Zh`-qm)Yif+|yqUZ2IKz1dbcLX^~Tm zj>SjU-sPZU5CK8GX;@l2i}>Vf?jDf2zbvP3kw+Js?dKxgx4k@8l>Lmd0Jf>(?_m(j zprR;Q8=#MGY zGaZzER=c9brV*5UZr9i2N6n9u87x#;{bReibrmJtZ_QxnR56R{aT5C$unve0(uMBnObMJ$(Zjz;FRqc zC3eLTT?>w0Xs5Ik!p3^X0Gle@Lq^~<2f1$gj+?g9Srq)j$yIaphB$if$WgjwBvLUo!+CFS)H|zj`c`U5th(EosVh&V3?&C~@4H0tz`hxsVP%&z2A`?M z8dvLxNYW=v(qc3~N6K;j?>kptsLkcToiVClgl^PXfFL4%9HLTBYk9qR#nX@}-mR@++7p@Jb2h`6|@S z!){S31P`y2_VJot{tkd2IbEC*#(_G%3L+dig-DEV1aa`MqeZ8PjF`E}J;#CQ@QMYI zpx!(K-uS0yQLA%!Sq@ztQ~~W7=;Mjs2GT zW0EzoJb@LMp!`q;;q}P87SRYTisI7kiaSxC_mb81-nfqZ)ar87ew*RFpf_S*zcyAt z2zu$E%QB-cYc*~`qbI&D`dM16O>f=oj<@J@=)v)rSW8H-O;EfQ6UDAULMNMcJs*S&N{pg{W6w?dxj=@EeP4C8ROlfDp&av_vt89sjb5W>keU zrZ_wCZ%xKQOTuOo2p05d+fFx%c*bgX;0JoZ_FH-r)WT%CZA-($#b3yF^JX$4$szgf z+c9a#EzVj!tN0fF#Y4a;AHtc4RFv^wG39-l3p1hgSt)qoXfq{2r|*2%Vw?87ko8lNap+dmxp5Y)=X7D7$!lgT1SsDKC%T*RfqO%h?*NZ_tmb`gPn$uvY!B zE~2{L#3UQI`&4hAzz<>ZeHQ=w8K${k5u@}fPIj(BO9$L9>#S*rp>mK=ig0&2sEC)a zkzsJQ7H0S52Bl=F}F>a5-w0PU$U^JzZEj?xrgMT7VQ?Xg0W5 zODos&XK~DhI?}{<9ejQ2;M4{%D2V_3*e!8SVC1R7&xN}xtaqTCCn!< zkx!|#(@DODEA=c+-!8_?l@=R_s}CgJhZ245`nLuUPG0@I%cVb%+02x8P~Zg!Gmj-? zzq)#P$|*BW7lD|%4d)I=A?fX8FC&1%UFL)jA?7i21U{J^X%SITb_avg3uiFnPpp%*^ z{-$+}*z^d;&uT3>lW<=EaMo>kJPR{9e_D8kk(j?2sahd6@he`~+vesxMM-{O8l;418yDx3!VXV&`e{c_?BqG*zm#A5xBpWPi6rHwaBk$ z@y(P+Est-l18YG&Xx*2l*;bH|gWN-sZP|h?1#=G;W>#?x0~N~x{?EAJ;XC$f!MAMZ#BDc)-f2;C4LBE=B>i17>?%&qLEZZ$G zLU3kS&*}pn7DygTzmFFyXgk~TKH>GG+WZkEW?VAnvPcqk4^0P*YI*(w`v z0k{mr@JzHFE|Q}56zND{Ql;!55SxZb3qa#~;ZZt|qWs9y07J@EPY}5-^y$qD3B%z@ z;+Y6xdcps5F{%2?Msblp0bk)^9L>fg9lQl9$;vn4&nz9?u57N%(p|!i$;@?pvWS1* zkW=ho`=3zZDEDTUMwYbPVPeP!mPX{`Z%4^1Ax`A?<-4LDLdogXk>i^$dE~ROnG3K) zVD9`$d2S(yiv+ffU<|~(ux_E<9IW%9;uZbi?f>Y#Z|sG7e5{asGU|kC!_{E7G@NOL zG`?TDgHT8$bEf|U{>2k)_c#Q%O|5LuEP_rz_|y&$@9&UKlP8JCQk#n5g0Kw( zPOEVk#ocToPab$@A;Aa{-ci!mgt!eESE9*YLFeZaRu$gY(gpV#9=>RAUt@fe zZb25R0m}iC6#1b9c#IoRKf_<8gG-(bA3Dpf4GtwDFc}ifKZxtszE$!v2a^#gPh`pm z%t6b0@g)a6gDcSH#W$JjQ#6XOb*72c_B3;ai_${CLuLmh?2U886YfZ9#N2=tsjSyG zt9uJuWwRLG+p9A@TK`SCNvvz!IKN>ur@J>_eGX7V0kiqv^AVTQp^^Lz`}p`F?K#gr z=MC1)Zx%Bp{awAqkvq8_pl8dx1@i1bh zfthesmBz4S{rtd=JM?pBhBf~ug&U$mM;-W^r5aw$qf$FNx${FBwIwhG_J(Mmo*C{f zx<&Eum=amaVC7`gtNyRuk>Ks1&=~T?Re}StzNs1u$w*hMt>UJ(H(=#eL{X%KgyOp9 zY^!ru)CLO!{QkTn-PpdsFVcUG z4kl2}i7}@0!9(ST`|5I!8^l%TpLUL>?T-#EKP5l}$cF^+b?_}RuFEA;^8T{P$`Mhm zqH0qNp=QG30_o+ZJ6qG<+C5b2u^FxdlfeS2-|B4la-$gni#CcTN~Ga9zW|}{`wB02 z4xQ1!N?tsYEwmz{wS^5~pc=O(3Zxa*DyJ|pdmfW;C@h2*tls_BX@bSLaKEf2xF!lH zYzjF~HhG@5ei8Rpc?(`J3gYmzcJ_a&BXMZ^%XqaRH=KUf-TXdnLiG%pckGWCuS|c* zj6tPwV0v3v^wddDgyRDV-0Lew=8!YgI3SZ+iS)}yA!)N3LibZwgdXi&_XFY|wY}AL z6p_Le6&$3K1CVG~3ZuD3HOz3OZTomP_|%ecO2_h}`T}Er148aF|CIWXfP>*8wpJjJ zx?{r9Y(LDmsbbD*KOUj`@3CNU{c^|yU>v>Wzi=EO)PQ8P5Dckr1PCDq^FZSQ;DA_C zt$)9b8@PRA>qk`*0U}H|?hh=f004|X6gL(c;^xqfD;&?fl^R_ z>R=lThk)B3NuG}#n6HEaRs>7i1e3>3S~hZ}o@t@QY(>5INpxsXEt=%x8kd?HTknLlg;UX@)ITe4_2o8g%1s-op_0j(@?Q z*nDE$ymW}yR0@I49v)3}rMU$G1gD({lYo{%)U7Ru?>mD}Mh`lNZH^$YX(RXH>lYA^ z-u?YD>4hjc`&zbx`w2;EsAD|mt*|33tQ59v(j~lZHG)%pS8-(;olabw1!{sJDXSgV zTk(?w?z8p8kdmOHHhj2TsU4)~VB?zYKgFpnePx&c4pJAk-{vB?EXN4&UjW?H|NRSK z74jLG<;nP2Se@e zBL#SHgnb_ixTw5q{bDaKNYjT;ZPHS&s9QQT$Y1ef5R;2u0Uf#41a?(S?nxD(uh1rHn8xJz(J zaEIXT&KchGt^50~yVm6w!wl6u)!o(ARZl%rRo~0?GTq$MPVmz9Z@XYI79b0#eebBI zAuWIsXF&Xs@9?VfHT1W9>I>e4|D*~ehY+VrKHV>6Pk4SwubgLYZPXb18%w{Ji^=)ry_%@M2Qw?o9)O9gE@5<#!9oFGt}ILpvyV0(?}kHFse^ z8W{ZfJ-$p-u_d_XxY_>nFVq!^F49%5Hh%ZK*^h1bOSU0Gk?c`RNA2gchnMS>eTPr) zg9;JZ1*|*yergN+u(B?`Ni{rw^2gCiy28C8{PYi$#&vRzznelu8EaQIpbA%# zL2~l5U&6IYhn2`?(Y(l&9x8_lk3`J;AE@JMry5(#ypIBYd%4-9?IOnLVv*mFhvI!1 zS4`@CDRTU$oS>iz5e+6Kw8ks@>BEc_^kL_EmX4KQm5L6^L7L8{PCEDl4m(OBR37r! zQ402kWa;tWi{##XnNqK!D2Hu=hG8-gG=E4BOaJW4e9nLrlsm*-IZwAeKsCy**)##K z#7H5P6eHxF9?(;!F?~olua!=hfte^XQeU87=llC#qp64)-$J+-_`6no$+${bN7it| z0XYlBmgYM3Xbw@4)#g`J5l<7NCO-VP#$vTuzF%W?YLvL(h;D+u;ipD_4+bnhB8^q} z_QBUO{Vwi)+a}=-0fZ5S8T}{Nghez&rk_@#vEP#J4ozV>yC6A;hKJkW8!AGoEZlOt zj$qx-LX*1U^3HWiQ{48C?2T(lQ<&{Be|{76qF5&iep_U82rolcU+{@_ciKN z(Z7m{Fhc%!INlNCABoRUbk$h<{q<+&%L(P$QEeYzzYldbU~e!>M5MV@VceUHR%Rph z>*2KHB&D>;g=My$-ai5CdsM@VgxM`TZTC9;1X7xhCG6~@lQZzD`|C`jtB`uj&f8lR zSq*C#?QqhBnf$WWOwgEayX&%e&3xmsZ>irOc7!esYgI54?L?y1qiak+V*j=3es$XM<3uM$X^LYYV6=-Qg} z0)b9`jif3zsci985OpG3m-o3)b}CB*%9*1tO6+Qkp1BfMyhpy#8scu=2W%1E8glLY zJn#KKnW-a7AHf4Gwx^$1;;ze8SANJ{h*ao`Xs&LWflv#!?BXdj&Q;}thpMqO?%T`g zem_2Eg+zs4-jI)}DoJWRZ&fJCf3)nmqL20KFX3C=XI2`Xa&X3!m#R}Aw`EMWBVBd~ zw^z>Xg?)BK=has8vR6fSb)@f*|A)lasvHYrhH|RH+K;T4|#f7|)*QaBjqL|mAbd5lpu zkhT-Y7#(&6{2x^Pzspk$xK{YH=u-Z||6~QSA$TJH2V4JtL}T^7FW887M&O4dg9SkC z@xRM)s!k!$|6dJ*J|;^YTmH#>7aPd@Gr9;9wwsz4xOQ zCmOmHY_?esl?Xf8lwPQ0H7|~%=AzuHQA;UE%X1^e86itNZ~)#3?u%i#5yJl2az}m* z$qdEMp!)E$6ID8VjlU10I{b;Tk6 zJ)LZK)=t-3C;bQ*K3Qh#`2xhOMRqiqqW*3-1HDZ~`fl1R7v5kTK)DEof58fm7=*(_g6lbpLaoj-R{{}psB_#y2Ur--;b3+zN)ZQ_n?cs%61pgH4!fPp72GZ{hsb zzRjEwo?-Pm4bGR>;O-RVENi2TZ$dPbo=~rex5(zh#1A4sw+Ef>r;Gk})X|H$i(xK# zgd;-n;wcrm?gW3iJD+T@yL_Ohqu*%7QQ}e09LBBDRdQFcOeMV>x^vN9P)@OW#bjWU z3Lw`AOU;6mqFsK(S^RL%AZVKhT&wd(NIk zk^Bs?fyxM`j`jyv3PRn<6la1i=Ud=dKy7du-K&K{@gK|2O~mda(PSdgvBB(4CKIqX zA0d5S&r+8k{{rdf7YQmtqlk4jJ#hqo9dy}!-GiFoO6dUIcG?^*GBMabXmx1uL!@88 zl_SW{=^s_`XHO2|Z@ll>DdyjJ7j|$clf91r#Cui!MHs#p4oc0u%y{u1fLlzwz=ag% z770e5jKddTi|&jKr28om98yWgBN|{>e!*fg+9l6eZDagRde}3c{Hz*t4`njn_IKwm zr1PLh8eo-mfNFEsgvjgwMj4`^8u+t!lQhSmLn@g9%F^MQB!ILA4bJYq4NI1P3)}Vz zEN>*+(VjOnOmlIT6C|`I)=`X3nc``fcuxw5diu_lWcG^BzPo~*UHYC0OM@zlw>4`8 zn115#_*TWGfrAR_mXMdPSh{^}anC)i{MO(4%ewlsesuqZ!snguhyW7)LRS4S1u{z4iA^lJ_RUqPp zlcy$c2j7xvvc=`OU88&r+oj-+2n-MuuBAc#B~bnI%U9Q*%y?{hhSKO+>(IH;Z{!G~ zb9z`eg_L!^S@)(1&wftGw<5>xJNU%FbPM?)FxDUXN3UbVBDRieuhLg2Y1(rMb=#bF zU;?PFg<6b$xFuBjrb81+{j&Gjiu2cdBU_7)>@uF~U)YT@;QwL>a0?4@ z4K+Hk7P>)|DrbyX?XDMN)UaGwVkHreYx6JzAb4P2>;^Ok)IDOCR-1CiV=`5Qqw!E4 z&h-o7V*T^!_MDS`kI)fM_Z&7H^G6N%*L4e%xcqTwU%4}|uZ221!IKnH%w=XN`+csh zepyM2?d9TWV9hSt^%f|L+q?5~9&lpoC+?_IA%#NyAl7YQ|BDVig*DhLib^x{(Gb(l zfB-wKT&+Oy0nv$t0U=Ntojf5AzE3b>GbvHmi=UIvthvR+SDXWZtys+qYmdLe)1$Z@D z0eb;6ZrCNRD}>`*--GaOvXq-5Xckwgt`W2Xo{Mrwnz9;N%j)wn9-myGpUr-pBsGfP zY8~3AoW?5;miD~%9>hq=Tohu#}pLF=ik~+im{>2Bz~Y>$n`uTPXw1X zsc|^=Tf`H@Qjh$%%cm8-7WNPmdT`SUpz?nwjbYtb)h&(lxrXONA08IXFpLicNgOJ| zgYO$+LfG(HkR{Yvt>F7O-dz6{9Vn?UkkB(_NL=6d&Y^=r5~p5j?=q#Hs@emeoVd4M zd=Fbuj$5A0Odkt2&W?ethAX?Y&UIHHCTi27?$MpM1nVWimFXRx#9 zeT+O`+hPZFT?=}lv$T?~n$3Po;${nk3sy`fK3|sz{8N@#mopgF|K8yA!vu+in$kQs zMmZQLK}7te>k?_{DrP)suO0AXp^Vu}9K9OPm(TDwr7?WrE0Y>RU;N#Aw#+k=%NOF?U>$V5CWOZ}A#c{2tns`k^ zK|Y7o-iZljO-eHQp}Y~Kot1)(661&woSBKNG5((`inn@i(5@NRR1*{~EjX%95gVN#AOdiglGMC#0u3Ku3{1!mSN~mx-a;l(rK3LOO z+T%^2u6Zb?0EOm@x5=CsqW`w<^5NWny>U%{JTYrf9m7{D84!MY&isE=?H8CefZ# zNa2JCbT&eUg4BK&2_83_N9Bq{S@(81GRJScyNy)Y(~@eL30ltpIo}OWBNWz=+Z-L& zB}#A^q7z~hoth8>31Z{Ura&#ey!Q`Hqz=@I2(tgDxYs3w9fdH%N8L^Isk2~1BdV-x zd7rOxNK2{5Aw#xPXu-CCn?T!qMd26RG=J` z+D_fn+U{n~S}^@a|FsvM?Qy!8U-_Eaaej*ImU-2GKIv*>UC$eis6^z~7I}n>el{A~ zUsPFgFX$}o_pmUB6UI=V88X)&E~$8YcX*jTUKYfZfe)Hn3&Y*@XpdAjVnV|Sa|bFW zoIho~IGOc&$AmzbHOF0{7sps^Sq{L2$VRYnhh-N*_ce?Z{f)69e-`zUfJ!yN9v5k^`szU|5#l?73@!^%824QOG;JT3>S-jS#kPU zRSvn>ugup`lgRyLlYW51%-B>(L}^DtkSx)z8hY?}ZiVn%S2TA$7X0&mTHs@j&soz` z{Di_^8&cNt8c9zlb)44d%;k!#;==m6a3=&GqWe4vZDGmfiFJCf6FhJ-I59VmK*)?u$(__}k*Sma81+}NR4>ra7orni#6t3vT) zmEEh~KVuUeq5mX>`|84uZoQu0RdwO$w-(<~F67O?zshe|$cBQysqwMVYaaC8EYl`C z9AW}+OoY|UHg~H{S>F}>mS|akwqQLBxi%bMALv3mbNw`FV$$W8LeDrs(p+a zBw;k*_-DT~oCq*>%tb}5vEs(a7pyKJjoY%C$BH;Vt-xa<9MUtmE*$gLXo#MOHe^Pzpm>iUNfYDjiO|Fh@44eV zuHdvBRNB^aEGbx?UjvPqiIXZS&*N%>+JkaQ~ocO?vN(joADoG z^QaHZv5RW0$M2XHXzlJHneM5z0)3VqEk}&GKrc!K&*HB>u5r=huUS05)6p@gXHD}G zed3a%PUkQhk|CaRTvA+YtAC=@5VGSPz6tlDTHYa#2;%l2uMa?wXo(c?!UGS?5Z$^* z`XoIR>rWTFu;|TccQ{EfWO0rzSpP=An|RLho?EOc=z#D?TN zyZ<){!Y!#HtSke?JBDQGe6Pn}s0di^P78Rt+D&KRQ;s=`2L#s!hrh*1O2@+=A_1d> zKtfXbTdtf=5#C1onFD1kxzyK@Sa!V?hw3-5YYnK4S@;`U1t3dR9G{LPgLH(-k9ji9CQu66|d2=A455zy7$ z6a(#G@c@m%OrnnGnG5O!9e2DAM;g2JNmTKU(y@xu(=Kqf4u7^wx9q0JI8ZqL45Z{x zM*#J`wzKI)JUNQGvGO5Xe7;=};O|9!j>tD}lLHnWPL8Q1^BAgu<+t_*B!oPi5WR6q zK#TDz1LU<*E2SmM+1njO`` zEva(#vMT;_E%~Q}8j)SRc`-8eeY7Y&w|#jlDt{1x+UL<2J zby$>}dspjY3?9(ilr)Bosp#IsFMK_mqMI8&z6ELM+ZcV+UEsaR` zwD;S_1h1Sb6^&dP=Ro%QbZPB6N0-t6Y?fwG2)p~GnBvTJ&72y}7hwZ=gc9wi2|z{c z|0%s=v?F%JY_}fw7w5Z`AcHUMO)t5z8t{l7Q}Q^w<#vt_mc;2K=t*a^41WvrVF$_e zGXZuFF65ln(d{gK5Xa4LxP$Y)4hO~%*#Op>Y;+R8 z*6{nj-@32!2zLC7rM1tf^FvQsLmXD{V_7-~cS|!2(sWKq8nxD;7s+NMlsES8>|&wI zu5CfwGJdmSeX^0~;~OM*YcUpdYtq9;$t7VX_qYv;;_#OjVt^Sz8Op5hV9dS?Mv>&F1w4z<}NVEK7V2jEumcvoIQ%V&E(oR1TyPNg!Swh;U;6p9bFDs;5 zL?*pJ+gk(;F9imt5B1^j(g;IVTNtj1g8=k!N{+DCj04rSGuWYBKUkF1zc7++x(SBxi4S8Rxr0~Of zBux{sF_@v~hY)J53K=^|FI~w;gd8P7ZK%u=r_!S0#4p$XDUgGY^Jb$}_~NIvQ$wM8 zUJF_)k7(_8!@lDl6mNOn<#-N!P2=s)!Mb)aXy+cN9it`l*sXrElV$%auDy-y95`aM z`#C0Lj@V@v=-i%X1Zo-^78pfZ2MTtG=ZDV{1iD+$f~TxEkEkk;3ilq;uyhwIiL;Tu zB~bULZwt6l1BX}xM;;|bA0CRH_&nvwhg*UrvuE}^CT@<2McC^;!)W!?+`~=SPKdBi z->wps)mOsDz=x8og2phnpmnU?Q>ULIhNa;iRP;rcZ^IDU;LX_hrCTPDHG~Co0WfO5 zHp96df#szVOv7*MHX#-h|LVJ) z9V_2{KdWBvp)lf4^Wko_$Do>}XQ^)cRfNDUb@Ep^>fj|(36~XKG;23e>kPfA$g98T`+UPD0IIQS;rb=o6;jWH4NU}&30}Fu4-eS!P zbg#@Ix*zK$d9U8~dm@D0$FhP&n3TYxf zemRa;Dq`B2B@U-ARhSfgGdsC9Fm*kqr z$YG4s{@Ri1v8CB%^^Jb>zj@~~Dc3!H^9z0qxQ z5VK58Sb`%^`Q~%_g0;2P{p9Mz+jP83eMt zgk1zQ=AivTAmo3i@2a96S3=!z^1ne)6ssu0He93iTuJXQGxie5_oHNsyD^u8JSKgq zy+xr`pGC9y_i}cE|HsMPNTFp|O$nIu4P>fvhqU#K|007WOCoiPAXlWjh4@%U3kMJP zW5OBkPOsesp~h|zj6Yj~$F3Mhgx25SZdW$N$iWO}<4PoR>}WHFiv-?FjV6sG*WS{jnaK&{&WWT|oE)1=AJvig2AO2s|MJcx7YvW=pbqj)+)5pJoS zINM9qw7eD$kg|XZvd#1{bhZ7&&QBO%T>8-d8?l_(s(k!oWY9_V0UAC#IEn8yI|c9T zZKBR444t_Xp>vc;^<2YVhxFkG-+>6t_?%1~crfp@6=|L;N9zS?z=&k>q3;{ME zhV|!jdp;%${_Qqpb4AYSjFTJ)&MBS^8ZqMoBw$z9kR~)UZIXJj||QaMlrENL%4eG$O=|6+_qgY03)nHlio< zwk$oLreYnj+7b9`!(n2$k9Ax?Kmsk@ELNC1$&G-e%Kbb1pcw`bN_PT}jEO+OFtk#6{Y%>cUYEMHxgLSyp%!N7fJECI$5P(u?yr@jSEP?m z{C9-@O+_FlL<{$La8G;sXZ(0!+XlRU4){bw7{{C2U!~c6y#+9JD%pn10!y5zef_{L z)MhcpGBW7Z>y~cK3O=#Xrz01ZvVdh=s5Pbe2e(8nd^POLubi7rLG^C*2i6y1_EhIh zMKd_b2Q*J?*@Bl&ELu`Y5+Cp&%cbltT-X5PO%NpdVo(UQIoFhNvC~bGTe8}1y|baL zH)-r$s~@+W!DrFmQ?&aj)Pq?`t!Qk+<#Iz&Jq=)HzR4omtRj|KyRc~%ztOY}!Ly8H zfBC}m8Wto`3m>L+=+N@dfY&3QAGL$rK{&+_g;G9R9ucsn8X`|@*i*RUySS%zH6)T0 zPH-hSFlNXHIDck_IGfNi0j@kMsB3mDnNWZ^D~{ubIE}7v(FC+dun*=awZx_R zR<@u`1mE+UqNSC~R#><7SKecttLUY;b-0{?nqN!Av&>R$pRrEFTj@BImh9Q9LQ##j zx|ESd`FVK6Eeb3j_m7f5s3MN{KhTx4C$KKm2OKTqG)*V-XB3mkd-AlAo&MBdlbdYk zQF1s(e0**l)OSaI{>xKi;arI)uk_mr0^N-qo1l~PIJbdglEXd2dfSQkgw?G4a?}`} zx3Gx=3$L#@!dIiq>YvHf$$E{K#W$X*?nQjRTkc_Zchlvw=zk7sd4-@k@6pGj|#LQgp z;b43XMt`vlI>)lp@bxr+br~ZI{zS~n+e}14-WoBZu2rxD1LWcgUqh0N&L86AmP*CK zKjauniYfTwtSrLC#i4-3*1Y>qfb63^0_Fv;ca{&`tLqzr(XoO7e%d8@Y%+>8CS>oe z+oglP>G|Lb7L0CrpuZ*TTHFu-a*X7!lCd;`e<(&4iV%6IbwuD0czF6U)Xz%0Qyhre-C)p|p7Od`J z2Hw<^AhDH950vM}ycH7Pis>Nw)Y$eT)vLT_t_HNb$O|`SO*(%oTdQ`PoWvG~%JJt_ zI~%yCt-24OJmr$oVv0y7S9Oi$zya^aWf+L4V5-&KY_~?almM-#N+KG0?>vv6wYu! z)^|Kqi-`X&J>dZ-#Zyu_O96mM0N@9*SKTVV&7%JAat-tKoiGKQ*aco&f_V*Khy1^9 znCcIyt>E}S0P`-S%Or4N6C8V=IH6`F^r#>);OBZ6zM`Kr;gqWmA>4FtZEez4cf;dS zMwIT{>;?LXzyS0_E&kzw_%TA7hHu@&q~bu#^mfX9?zgs*7x9h0&Ef;Slqs?)4|1#U zE5w84b-uAA3Xbm7ZepH3a)h>vHRF@d;2M%n;Q*!7J{5FX2CvT@pq%o6;uLBCniS~2 zVPDI$1x@H_Pn%=S&y`KKjYAheWrnZk6~lyNB+=RRd+8E@YJW6Rm_MIdM5X&byvPVu zXGzUSo`;~rOE2SYCdlOH59{doov5sEoJ)7g${snd{+7Tm0{=ikw2A+t9V))vNdHfV zfw-^lB_T6#g}SWQl{@}%DnZg`isVrH>z9=7+yKVFcNo8ahF2M;!e7X^hW&n1d1bz7 zB>?v!4(2EMra=7_ozn;Ebjv4NBbv1|C} zxQ}bzdj;1qOfP)&0Ex`> zMw{mSo?QLR4`FhEC;+F^?sE-bpB;}MeDV-x$*NCg&6%wm)gcRg1y33?H06 z+uwTYdrxe<<<8L8f#rcj0OU6AGF$?ia(7tlkrQc*gw6k91kw72?2%;))OB^lj71Pt9 zAk@FcD6YWnX9WrzmVYVCMnTL^7qJyuvDOO^4a`L383xY!m_a&lZJ|+(Dvz!RSXx#-U1(mRo3V)`c^z6(&!f#7=+0xO_|Q^6 zNc_k(tqz`OoTk#aJ!^=3#tWC+-P~krbJ5%l13u211zZZLS<$#WI&;@jw@;Juy}FbeotPS%`SAAZ3wC z86zJWMeaL^8y8+v_ohvlh?mfs)WF&*jepc|$-4wFySA7F9b@fI)KCst#J>`gnzLY=Gp^uQJ+&KcR`)# zg;C|dh3sQTH<2G>qhUYNg=|lRf7mf~H*<6m*$o~nt|eum?b+%1Q%a?~OF6)~yHR{9 zwm4=CO@;2cKC=S>_Vy533g>DUTJQRXx{LURaGBJX#_hc7W3lxWCgp3IPFbq!4I70$ z?1Gqkbbj)@6eK#?o@9y@mxj{DY~3>;Jc7`k3=p`40todrt*nPw6!8Pn1&&Mfs-!2I znkFXF^HRs_f@OeFq@3t$M0Z)KOJV`ld(kDLg+L@WNPHVX$r|q_Qu`)%Nl9={&)&Oz zsz&9Znm(6$EKN9GCSaYsT>uig#=^>*yG||=lJ_2tk=-`jTbq8+-3q_R*o9eAqUhZ2 zt0)_uUdB5DBRO-mI((v)`cIlxY!Gi7jV;`g)My zw){@W(yL|;(2&kU(5z( z!F#V2l%-idT6i@J#)UhcX`Gnu%oCR$;OVbY-6K^NR^4*TCS4KgcwS|7pku8hqB^;M zhxvx^3Qn3aTE$$pEcGBMU~MPjLcGI;^HVZR!A5t_UU;NeE^&Qk@a0-vsEaw~aF3*w z%x+H|qs7D|Xm4p?`9ZuJVs&`~#Ety|R!zd)<{2KiV-)HAbCAmb>JIb?HdKZKkR=#y z_PWW~AUlWg+qOfS;?V@zWg6=8G*}S`>scG`Y4v6ltRso&q_}#qt zjLvgJPFic`dujk>X8XL4_2 zk*PQ}RS$=AjwVm@4`X8>9|bp!r5bz~N`jR5#BNd=ksR=~11Zc5xq@3`PMN+^ln>vu z;~7#Aa1j$=)|rWF^>wTA_ixzj-&e9;rm+e?L_H3808GU;X;g9n{ z){xp4o(n?@IeLEXozQ5PsZmN>*xq$Aq%wc_^aV1G4ZAqKp;aQk0}FO;HEv-C%)(cctJNQ~%rD+MXj}fT&xNkWk6W zsFY#=SLV3>lN5hEzb(zX>B?)T-u&vliy7Jl7VAw2CQ&7u`$%E1!LNVc%_wBPUv+Lg zlDD;l5)9uzgy}90BUl`!J;_FVzxwTJObX|Zw`CV@MN5f-QO54ENe#MsWNRbaBTW42 zli9OVJ3TwH7teo4@)jElaV(WnBlr3ql5+XRY3{G~B{nXij8p8W40AS`BBp=PRXu+t zhA>;Isu5bl?}88FSa0FTfUlt`S|MzG`b+RVw3_{7<311mAb2X z%UNAE#SiI+F~L0)SU-GbZ&Ocv_vr`zcE&G7FR3ye7J3k_FSd^38<-U4zRdL9;BUVy z{0X9w44fv@6xvBov8_z|=eu9U*q3h#qqt4-Tb>9uMqK`8g=i^d)_8x=I!p9tq!oU5 zGusTJ&U0Hd`gH1jWUZo#{)@e2k4-}P1(Pc;S{Ib?Z4?sh10Lzu z75h*_lnaZE8Xl^O!`e%}M*L*DU$;0D??Gby(r#fm+PjdjuT?8RL4sr^wR|Dgl@_+4 zMb){@u9vFXNfRb=cDR(U-qSGGMyaPmdFI{IbAz7=X)-w5U<<-!GQ0cm6ef$@ByWcF z#4%yI*I*vi;?QUB@ebMV(v>tW_eIF<<#*l_jCNeQomlyqxO_@{GvlvY5GdOR$<}Xo z$%u4$c9hiKLTJ=+C}=)A26ycioKlrvHT9QTKS}Tn`2fgO(IAEv9j=S-qdU?(F#$0{ zKAYk(S>W4_l|fxG@Whg&-2KDe>6=Eh*po%SQTm0b*qvN-lM;`Uy;UcVT?A!(E7(gLxr5Uo~Am46%v1X6pwAKAwwR<$}MPz69Lwc?-lv1mItfR7R*&?4wbFX9q6b z&}C+z1G9&2<(vlb94(}Ggi3i8C9yqQ4%ba?$OWWtzBCv66MQ*#VZ@3}K(MBdU~lOl zVTC279#H6i6DGqUj#3@Ubn`34+xMeiR%oI1Cqc%HhI5#)x>B+T2IC^lEK?B{27UcM z#c->>jLm)xn{RImx_PJy`XeFKF-g|yf8Rw~a5;YU8(FQ_$2&p zqi3J(>SW%_#gDU121>pa6~#;LMoAOIKC}uV@9&v#Hj(=NZbj%stAr;WL;FGM($P-a z1w1xcWF9#dLb;E>2KJ8GLP;#?r6&%m8if9WK&tfp(?Qxj}dL*s|F(z;{P z+S=mL-mCtRNtoyQI>ko1Dqe_?*IExXCzpei_HDxZ%BsCX#DS~o;=0OX{Nv@%P>+!o z>UsLvNhCLp9uZcRCrpj}QDG`z?2#wj0eg*C#fa2j%1M`($|1%I&LraBhIv`IM`ud;*RbU{Q91MmX z8ti{}$zcURLYmC~69Wr~fit#LM?rbLdx$a!o~!-z@&AfJp*rR0JenuPa3v~-0e(J8 LD@#>L7zh0yh&`2n literal 0 HcmV?d00001 diff --git a/packages/registry/docs/_static/copy.svg b/packages/registry/docs/_static/copy.svg new file mode 100644 index 0000000000..3e9fd912ba --- /dev/null +++ b/packages/registry/docs/_static/copy.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/registry/docs/_static/cut.svg b/packages/registry/docs/_static/cut.svg new file mode 100644 index 0000000000..6a3b112b14 --- /dev/null +++ b/packages/registry/docs/_static/cut.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/registry/docs/_static/favicon.ico b/packages/registry/docs/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ae7bb0cd8b1f4f2e6d657bc7a253472819d77819 GIT binary patch literal 5430 zcmdUzv1$}y6or39*mCm*nMy3Tv=B0n;3G_FX)6dj5Wz;UwcFM%Z{S2y*ogZGGvWhm z#3q7dJYSp}Zf^dW-Bm#h9Pa(^J?Gr{Z)P`;lCe04v=C~c5&99(gR}V4d8U_FC9H+9nPYV{kIiS_C~YBdpE3Vk>v$CV z%oqQ$uo;<6{X}5T9@M5ZJMBUq>^pZi?on>L2OI%z&ZC;rOT)dRu2t=NF1cX$9(~US z9@^-a-Gg|A5ck)pc3{t}d?iG`+WIk~74YyHW)vB2Pb;nr@L;rhn2Sno0`a^%NO%p=Cq^TlH~kz?oDb9c@6@!W^|AI<+;`~Mg3 zU*q0~cH7Ry<*vNLpz?g3OM&+bl)h)V>hbdj*WN#3R)=1_b>PCk^%=8SG2$Kc%>Jj} zza#f5GtbVx{hoYYM}9nK?tb<62cCLW@NRXEe}7kjv-5ns1U$Vaes?#a-}pPI$9KEe z`+VWKF_&)A8@@P&;NMOB(OvIB`AVSfQN?+7Ml0aq`OGMcH&q- zV?-mM^|Q%u>^-BaG8%M^P)2?{!x^}7F6S!pb7%3`L;PKsU#lLU1>1o|YV?ffBVLpD zj?W=}Av7a?-g{e)M-lyZJxf~&>)&_|G}W$iw(;a;@7>f+=<)lQ{5<+ueK+g-`c8kD z((*@2M;C_|hv&~O=Uum)z3-OCXQxMJFHes?zwDNu=iTyr{{7qeygwaKKb`krIMWjc zE*i9WbX`Bg6K`fPiOdT`*PLHiTJtBB+P literal 0 HcmV?d00001 diff --git a/packages/registry/docs/_static/logo.png b/packages/registry/docs/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ad5af00bf163bf8e5896ab1e3627a03eabb300bd GIT binary patch literal 12314 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 A*Z=?k literal 0 HcmV?d00001 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/logo_2x.png b/packages/registry/docs/_static/logo_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9e303f7cedb8c0292848930560c210e6dba9506b GIT binary patch literal 3243 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 + + + + + \ No newline at end of file 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/_static/searchtools.js b/packages/registry/docs/_static/searchtools.js new file mode 100644 index 0000000000..23ed8bf5a2 --- /dev/null +++ b/packages/registry/docs/_static/searchtools.js @@ -0,0 +1,553 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + + +if (!Scorer) { + /** + * Simple result scoring code. + */ + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + /* + score: function(result) { + return result[4]; + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: {0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5}, // used to be unimportantResults + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2 + }; +} + +if (!splitQuery) { + function splitQuery(query) { + return query.split(/\s+/); + } +} + +/** + * Search Module + */ +var Search = { + + _index : null, + _queued_query : null, + _pulse_status : -1, + + htmlToText : function(htmlString) { + var virtualDocument = document.implementation.createHTMLDocument('virtual'); + var htmlElement = $(htmlString, virtualDocument); + htmlElement.find('.headerlink').remove(); + docContent = htmlElement.find('[role=main]')[0]; + if(docContent === undefined) { + console.warn("Content block not found. Sphinx search tries to obtain it " + + "via '[role=main]'. Could you check your theme or template."); + return ""; + } + return docContent.textContent || docContent.innerText; + }, + + init : function() { + var params = $.getQueryParameters(); + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + $('input[name="q"]')[1].value = query; + if (params.doc_section) { + var doc_section = params.doc_section[0]; + $('select[name="doc_section"]')[0].value = doc_section; + } + this.performSearch(query, doc_section); + } + }, + + loadIndex : function(url) { + $.ajax({type: "GET", url: url, data: null, + dataType: "script", cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + }}); + }, + + setIndex : function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex : function() { + return this._index !== null; + }, + + deferQuery : function(query) { + this._queued_query = query; + }, + + stopPulse : function() { + this._pulse_status = 0; + }, + + startPulse : function() { + if (this._pulse_status >= 0) + return; + function pulse() { + var i; + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + } + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch : function(query, doc_section) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

 

').appendTo(this.out); + this.output = $('