From 83c1bf904f744842060e0dfdb7449406800df157 Mon Sep 17 00:00:00 2001 From: Arthur Andrade Date: Wed, 22 Jan 2025 16:25:13 -0300 Subject: [PATCH] feat: Adds Plugins feature (#2563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What's the purpose of this pull request? > [!TIP] > PT-BR: Para melhor compreensão da feature recomenda-se assisti a [apresentação feita ao time da Faststore Experience](https://drive.google.com/file/d/13K9Us2jYjNLbrv6-5jD4QGGa614lakFY/view), explicando as motivações da feature e uma demo do seu funcionamento. > [!NOTE] > This PR might seem extensive, but the majority of the changes are focused on the `hCMS.ts` and `plugins.ts` files. The other files are mostly support files for the features and will have their content overwritten during the `generate` process. ### This PR adds the plugin feature to Faststore. The Faststore Plugins feature enables the addition of new functionalities to a store without requiring direct integration into the Faststore core or the store's source code. This approach not only maintains source code encapsulation but also extends the extensibility options already available in Faststore, such as Component Override, theming, and CMS customization, while introducing new capabilities. Plugins operate as an intermediary layer between the Faststore core and the store code. This means that modifications made by a plugin override core functionality but can still be overridden by the store's customizations, ensuring flexibility and adaptability. One notable enhancement brought by this feature is the ability to create new pages, something previously unsupported directly within the store. For instance, the Buyer Portal — a B2B solution — requires creating a management page and modifying existing Faststore components. With Plugins, such functionality can be implemented in a modular and scalable way, empowering developers to build solutions without impacting the core or existing store customizations. _The plugins feature is in the early stages of development and and plugins can only be developed by VTEX._ Screenshot 2024-11-14 at 17 54 49 ### Implemented Features - **New Pages**: Plugins enable the creation of new pages in a store. - **Plugin Override** Components: New sections can be created via plugins. These sections override core sections but can, in turn, be overridden by store-specific sections. - **hCMS Merge**: The content-types.json and sections.json files are merged across core, plugin, and store files, with the store files taking precedence. - **Theme Merge**: Plugins can add theme overrides and customizations. These plugin-level theme changes can be further overridden by store-specific themes. ## How it works? ### Configuring a New Plugin To add custom functionalities to your Faststore store, you can create a new plugin. This plugin can include custom pages, UI components, themes, and CMS configurations, all in a modular way. #### Plugin Package Structure The plugin should follow this folder structure: ```plaintext @faststore/new-plugin/ ├── src/ │ ├── pages/ # Custom pages │ ├── components/ # UI components │ │ └── index.tsx # Example UI component │ ├── theme/ # Custom themes and styles │ │ └── index.scss # Styles file ├── cms/ │ ├── faststore/ │ │ ├── content-types.json # CMS content types │ │ └── sections.json # Custom CMS sections ├── package.json # Plugin metadata and dependencies ├── yarn.lock # Yarn dependencies and versions ├── plugin.config.js # Plugin configuration ├── tsconfig.json # TypeScript configuration ``` - `src/`: Contains the plugin source code, such as custom pages (`pages/`), UI components (`components/index.tsx`), and themes (`theme/index.scss`). All content within src/ will be copied to the plugins/ folder inside `.faststore` when the plugin is integrated into the store. - `cms/`: Contains configuration files for CMS integration, as `content-types.json` and `sections.json`. - `plugin.config.js`: The plugin configuration file where page paths are defined. ### Dependencies You can add dependencies to your plugin, such as Faststore packages to access core and UI functionalities, as well as specific packages for the framework, such as Next.js 13, if required. These dependencies should be added to the plugin's package.json, enabling advanced features within the plugin code. ### Creating a New Page To create a new page in your plugin, you need to define its configuration in the `plugin.config.js` file. The configuration structure should look similar to the example below: ```js module.exports = { name: "poc-plugin", pages: { "my-account": { path: "/my-account", appLayout: false, }, }, }; ``` #### Key Properties: - Page Name: In this example, `"my-account"` is the name of the page, and this should match the name of the file in the pages/ folder (e.g., `my-account.tsx`). ```plaintext @faststore/new-plugin/ ├── src/ │ ├── pages/ # Custom pages │ │ └── my-account.ts # Example page ``` - `path`: This defines the route where the page will be registered. It follows the same pattern as the Next.js page router, meaning the page will be accessible via the defined path (e.g., `/my-account`). - `appLayout`: This property defines whether the global components (like the header and footer) will be displayed on the page. Set it to false to disable the default layout. #### Page Structure Each page should have at least the following structure. The loader function will be executed server-side: ```tsx // loader function to fetch data export async function loader() { const result = await fetch("http://..."); return await result.json(); } // Page component to render data export default function MyAccount(data: any) { return ( <> ); } ``` ### Creating New Sections and Overriding Existing Sections To create new sections or override existing ones, you should follow a structure similar to the one used in Faststore stores. The sections should be placed inside the /components folder and the index.tsx file must export all your sections as an object, with each section as a property of that object #### Section Structure The new sections will be made available, and any sections with the same name as the core sections will override those sections. ```plaintext @faststore/new-plugin/ ├── src/ │ ├── components/ # UI components │ │ └── ProductDetails.tsx # Example override section │ │ └── ProductInfo.tsx # Example new section │ │ └── index.tsx # File to exports components ``` Here's an example of how to define sections in an index.tsx file: ```tsx import PersonalInfo from "./PersonalInfo"; import ProductDetails from "./ProductDetails"; const sections = { ProductDetails, PersonalInfo, }; export default sections; ``` In this example: - PersonalInfo: is a new section that has been created. - ProductDetails: is an override of the default component from Faststore. ### Customizing CMS via Plugin To customize the CMS via a plugin, you need to create two files: sections.json and content-types.json. These files should be placed under the `/cms/faststore/` folder in your plugin. File Structure: ```plaintext @faststore/new-plugin/ ├── cms/ │ ├── faststore/ │ │ ├── sections.json │ │ └── content-types.json ``` - `sections.json`: Defines the sections available in the plugin. - `content-types.json`: Defines the content types handled by the plugin. #### Merging Files To merge your plugin’s CMS files with the store’s files, use the `cms-sync` command. This will merge the plugin’s `sections.json` and `content-types.json` with the store’s, giving priority to the store's content. ```bash cms-sync ``` This command ensures that: - New sections and content types from the plugin are added. - Sections/content types with the same name as the core will override the default ones. - The store's customizations overrides all. ### Creating a Theme To create a theme for your plugin, the process is similar to creating a theme for a store. The theme should be defined in the `src/themes/index.scss` file, where all the CSS for the plugin will be written. File Structure: ```plaintext @faststore/new-plugin/ ├── src/ │ ├── themes/ │ │ └── index.scss ``` `index.scss`: This is where all the styles for the plugin should be defined. #### CSS Loading Order The CSS from the plugin's `index.scss` will be loaded after the global styles and before the store's CSS, allowing it to override the store's styles while respecting the global styles. ### Adding a Plugin to the Store To add a plugin to your store, you need to follow these steps: #### 1. Install the Plugin Package First, install the plugin package in the store’s codebase. For example, if you are adding the @faststore/plugin-test, run: ```bash yarn add @faststore/plugin-test ``` #### 2. Update discovery.config.js In the discovery.config.js file, add the plugin name to the plugins property. This will register the plugin for use in the store. ```js Copy code module.exports = { ... plugins: [ "@faststore/plugin-test", ], ... } ``` #### 3. Configure Plugin Page Paths (Optional) You can also configure the path for the plugin's pages in the discovery.config.js file. For example, to change the default path for the my-account page, use the following structure: ```js module.exports = { ... plugins: [ { "@faststore/plugin-test": { pages: { "my-account": { path: "/other-path" } }, }, }, ], ... } ``` This allows you to specify custom paths for any pages defined by the plugin. ## How to test it? ### Testing Locally To test your plugin locally, you need to link the core package, CLI, and the plugin to a starter store. Here's how you can do it: #### 1. Link the Core, CLI, and Plugin Packages First, you will need to link the core, CLI, and the plugin to your local starter store. You can do this using yarn link for each package to the starter from [this PR](https://github.com/vtex-sites/starter.store/pull/616). Link the core and CLI from the current PR Link the plugin from [this repository](https://github.com/vtex/poc-faststore-plugin). #### 2. Run the Starter Store Once linked, navigate to the starter store's directory and run the commands as you normally would in a store: ```bash yarn install yarn dev ``` 3. Server Restart for Changes Every time you make a change to the plugin, you will need to restart the server for the changes to take effect. For changes made to the core and CLI, ensure they are rebuilt before testing: ```bash yarn build ``` ### Starters Deploy Preview ### Testing on Preview To test your plugin on the preview environment, follow these steps: #### 1. Preview the Starter Store with Plugins There is an open PR that allows you to preview the store with plugins running. You can access the preview environment using the following link: [Preview Store](https://sfj-c18f478--starter.preview.vtex.app/) This preview is based on the starter store from the PR [starter.store/pull/616](https://github.com/vtex-sites/starter.store/pull/616). #### 2. Check the Main Color Override On the homepage, you will notice that the main color has been overridden by red, which is done via the plugin. This demonstrates that the plugin is successfully applying styles to the store. Screenshot 2024-11-19 at 10 56 19 #### 3. Test the my-account Page You can access the my-account page by navigating to /my-account in the preview store. On this page, you will see the result of the useSession hook displayed in blue. [My Account](https://sfj-c18f478--starter.preview.vtex.app/my-account) Screenshot 2024-11-19 at 10 56 51 #### 4. Test the Product Detail Page (PDP) On the Product Detail Page (PDP), you will see the overridden component from the plugin. This component displays: The result of the useSession hook. A Next.js link to navigate back to the homepage. You can test this by visiting a PDP, such as: [Product Detail Page](https://sfj-c18f478--starter.preview.vtex.app/4k-philips-monitor-99988213/p) Screenshot 2024-11-19 at 10 57 46 ## References [B2BTEAM-1951](https://vtex-dev.atlassian.net/browse/B2BTEAM-1951) [starter.store/pull/616](https://github.com/vtex-sites/starter.store/pull/616). [Preview Store](https://sfj-c18f478--starter.preview.vtex.app/) [POC-Plugin](https://github.com/vtex/poc-faststore-plugin/tree/feat/core-usage) ## Checklist You may erase this after checking them all :wink: **PR Title and Commit Messages** - [x] PR title and commit messages follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification - Available prefixes: `feat`, `fix`, `chore`, `docs`, `style`, `refactor` and `test` **PR Description** - [x] Added a label according to the PR goal - `breaking change`, `bug`, `contributing`, `performance`, `documentation`.. **Dependencies** - [x] Committed the `yarn.lock` file when there were changes to the packages [B2BTEAM-1951]: https://vtex-dev.atlassian.net/browse/B2BTEAM-1951?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- packages/cli/src/utils/directory.ts | 39 ++- packages/cli/src/utils/generate.ts | 3 + packages/cli/src/utils/hcms.ts | 31 +- packages/cli/src/utils/plugins.ts | 296 ++++++++++++++++++ .../src/components/cms/global/Components.ts | 2 + .../src/components/cms/home/Components.ts | 2 + .../core/src/components/cms/plp/Components.ts | 2 + .../src/components/cms/search/Components.ts | 2 + .../sections/Alert/OverriddenDefaultAlert.ts | 2 + .../BannerText/OverriddenDefaultBannerText.ts | 2 + .../Breadcrumb/OverriddenDefaultBreadcrumb.ts | 2 + .../OverriddenDefaultCrossSellingShelf.ts | 2 + .../EmptyState/OverriddenDefaultEmptyState.ts | 2 + .../sections/Hero/OverriddenDefaultHero.ts | 2 + .../Navbar/OverriddenDefaultNavbar.ts | 2 + .../Newsletter/OverriddenDefaultNewsletter.ts | 2 + .../OverriddenDefaultProductDetails.ts | 2 + .../OverriddenDefaultProductGallery.ts | 2 + .../OverriddenDefaultProductShelf.ts | 2 + .../RegionBar/OverriddenDefaultRegionBar.ts | 2 + .../templates/LandingPage/LandingPage.tsx | 2 + .../customizations/src/GlobalOverrides.tsx | 6 + packages/core/src/pages/404.tsx | 2 + packages/core/src/pages/500.tsx | 2 + packages/core/src/pages/[slug]/p.tsx | 2 + packages/core/src/pages/_app.tsx | 1 + packages/core/src/pages/login.tsx | 2 + packages/core/src/plugins/.gitkeep | 0 packages/core/src/plugins/index.scss | 3 + packages/core/src/plugins/index.ts | 2 + packages/core/src/plugins/overrides/Alert.tsx | 3 + .../core/src/plugins/overrides/BannerText.tsx | 3 + .../core/src/plugins/overrides/Breadcrumb.tsx | 3 + .../plugins/overrides/CrossSellingShelf.tsx | 3 + .../core/src/plugins/overrides/EmptyState.tsx | 3 + packages/core/src/plugins/overrides/Hero.tsx | 3 + .../core/src/plugins/overrides/Navbar.tsx | 3 + .../core/src/plugins/overrides/Newsletter.tsx | 3 + .../src/plugins/overrides/ProductDetails.tsx | 3 + .../src/plugins/overrides/ProductGallery.tsx | 3 + .../src/plugins/overrides/ProductShelf.tsx | 3 + .../core/src/plugins/overrides/RegionBar.tsx | 3 + .../plugins/overrides/ThirdPartyScripts.tsx | 3 + .../core/src/plugins/overrides/WebFonts.tsx | 3 + 44 files changed, 450 insertions(+), 15 deletions(-) create mode 100644 packages/cli/src/utils/plugins.ts create mode 100644 packages/core/src/plugins/.gitkeep create mode 100644 packages/core/src/plugins/index.scss create mode 100644 packages/core/src/plugins/index.ts create mode 100644 packages/core/src/plugins/overrides/Alert.tsx create mode 100644 packages/core/src/plugins/overrides/BannerText.tsx create mode 100644 packages/core/src/plugins/overrides/Breadcrumb.tsx create mode 100644 packages/core/src/plugins/overrides/CrossSellingShelf.tsx create mode 100644 packages/core/src/plugins/overrides/EmptyState.tsx create mode 100644 packages/core/src/plugins/overrides/Hero.tsx create mode 100644 packages/core/src/plugins/overrides/Navbar.tsx create mode 100644 packages/core/src/plugins/overrides/Newsletter.tsx create mode 100644 packages/core/src/plugins/overrides/ProductDetails.tsx create mode 100644 packages/core/src/plugins/overrides/ProductGallery.tsx create mode 100644 packages/core/src/plugins/overrides/ProductShelf.tsx create mode 100644 packages/core/src/plugins/overrides/RegionBar.tsx create mode 100644 packages/core/src/plugins/overrides/ThirdPartyScripts.tsx create mode 100644 packages/core/src/plugins/overrides/WebFonts.tsx diff --git a/packages/cli/src/utils/directory.ts b/packages/cli/src/utils/directory.ts index 521354c540..1c3241c142 100644 --- a/packages/cli/src/utils/directory.ts +++ b/packages/cli/src/utils/directory.ts @@ -18,50 +18,75 @@ export const withBasePath = (basepath: string) => { return path.resolve(process.cwd(), basepath) } - /* + /* * This will loop from the basepath until the process.cwd() looking for node_modules/@faststore/core - * + * * If it reaches process.cwd() (or /, as a safeguard), without finding it, it will throw an exception */ const getCorePackagePath = () => { - const coreFromNodeModules = path.join('node_modules', '@faststore', 'core') + const packageFromNodeModules = path.join( + 'node_modules', + '@faststore', + 'core' + ) const resolvedCwd = path.resolve(process.cwd()) const parents: string[] = [] let attemptedPath do { - attemptedPath = path.join(resolvedCwd, basepath, ...parents, coreFromNodeModules) + attemptedPath = path.join( + resolvedCwd, + basepath, + ...parents, + packageFromNodeModules + ) if (fs.existsSync(attemptedPath)) { return attemptedPath } parents.push('..') - } while (path.resolve(attemptedPath) !== resolvedCwd || path.resolve(attemptedPath) !== '/') + } while ( + path.resolve(attemptedPath) !== resolvedCwd || + path.resolve(attemptedPath) !== '/' + ) throw `Could not find @node_modules on ${basepath} or any of its parents until ${attemptedPath}` } const tmpDir = path.join(getRoot(), tmpFolderName) const userSrcDir = path.join(getRoot(), 'src') + const getPackagePath = (...packagePath: string[]) => + path.join(getRoot(), 'node_modules', ...packagePath) return { getRoot, + getPackagePath, userDir: getRoot(), userSrcDir, userThemesFileDir: path.join(userSrcDir, 'themes'), userCMSDir: path.join(getRoot(), 'cms', 'faststore'), userLegacyStoreConfigFile: path.join(getRoot(), 'faststore.config.js'), userStoreConfigFile: path.join(getRoot(), 'discovery.config.js'), - + tmpSeoConfig: path.join(tmpDir, 'next-seo.config.ts'), tmpFolderName, tmpDir, tmpCustomizationsSrcDir: path.join(tmpDir, 'src', 'customizations', 'src'), - tmpThemesCustomizationsFile: path.join(tmpDir, 'src', 'customizations', 'src', 'themes', 'index.scss'), + tmpThemesCustomizationsFile: path.join( + tmpDir, + 'src', + 'customizations', + 'src', + 'themes', + 'index.scss' + ), + tmpThemesPluginsFile: path.join(tmpDir, 'src', 'plugins', 'index.scss'), tmpCMSDir: path.join(tmpDir, 'cms', 'faststore'), tmpCMSWebhookUrlsFile: path.join(tmpDir, 'cms-webhook-urls.json'), + tmpPagesDir: path.join(tmpDir, 'src', 'pages'), + tmpPluginsDir: path.join(tmpDir, 'src', 'plugins'), tmpStoreConfigFile: path.join( tmpDir, 'src', diff --git a/packages/cli/src/utils/generate.ts b/packages/cli/src/utils/generate.ts index b1104949d4..16ca0bf065 100644 --- a/packages/cli/src/utils/generate.ts +++ b/packages/cli/src/utils/generate.ts @@ -17,6 +17,7 @@ import ora from 'ora' import { withBasePath } from './directory' import { installDependencies } from './dependencies' import { logger } from './logger' +import { installPlugins } from './plugins' interface GenerateOptions { setup?: boolean @@ -513,5 +514,7 @@ export async function generate(options: GenerateOptions) { createCmsWebhookUrlsJsonFile(basePath), updateNextConfig(basePath), enableRedirectsMiddleware(basePath), + + installPlugins(basePath), ]) } diff --git a/packages/cli/src/utils/hcms.ts b/packages/cli/src/utils/hcms.ts index 469c8aef5d..4dac87bb31 100644 --- a/packages/cli/src/utils/hcms.ts +++ b/packages/cli/src/utils/hcms.ts @@ -4,6 +4,7 @@ import { CliUx } from '@oclif/core' import { readFileSync, existsSync, writeFileSync } from 'fs-extra' import { withBasePath } from './directory' +import { getPluginName, getPluginsList } from './plugins' export interface ContentTypeOrSectionDefinition { id?: string @@ -96,7 +97,8 @@ async function confirmUserChoice( fileName: string ) { const goAhead = await CliUx.ux.confirm( - `You are about to override default ${fileName.split('.')[0] + `You are about to override default ${ + fileName.split('.')[0] }:\n\n${duplicates .map((definition) => definition.id || definition.name) .join('\n')}\n\nAre you sure? [yes/no]` @@ -110,7 +112,8 @@ async function confirmUserChoice( } export async function mergeCMSFile(fileName: string, basePath: string) { - const { coreCMSDir, userCMSDir, tmpCMSDir } = withBasePath(basePath) + const { coreCMSDir, userCMSDir, tmpCMSDir, getPackagePath } = + withBasePath(basePath) const coreFilePath = path.join(coreCMSDir, fileName) const customFilePath = path.join(userCMSDir, fileName) @@ -123,32 +126,44 @@ export async function mergeCMSFile(fileName: string, basePath: string) { let output: ContentTypeOrSectionDefinition[] = coreDefinitions + const plugins = await getPluginsList(basePath) + + const pluginCMSFilePaths = plugins.map((plugin) => + getPackagePath(getPluginName(plugin), 'cms', 'faststore', fileName) + ) + + const customizations = [...pluginCMSFilePaths, customFilePath].filter( + (pluginCMSFilePath) => existsSync(pluginCMSFilePath) + ) + // TODO: create a validation when the CMS files exist but don't have a component for them - if (existsSync(customFilePath)) { - const customFile = readFileSync(customFilePath, 'utf8') + for (const newFilePath of customizations) { + const customFile = readFileSync(newFilePath, 'utf8') try { const customDefinitions = JSON.parse(customFile) const { duplicates, newDefinitions } = splitCustomDefinitions( - coreDefinitions, + output, customDefinitions, primaryIdentifierForDefinitions ) if (duplicates.length) { - await confirmUserChoice(duplicates, fileName) + if (newFilePath === customFilePath) { + await confirmUserChoice(duplicates, fileName) + } output = [ ...dedupeAndMergeDefinitions( - coreDefinitions, + output, duplicates, primaryIdentifierForDefinitions ), ...newDefinitions, ] } else { - output = [...coreDefinitions, ...newDefinitions] + output = [...output, ...newDefinitions] } } catch (err) { if (err instanceof SyntaxError) { diff --git a/packages/cli/src/utils/plugins.ts b/packages/cli/src/utils/plugins.ts new file mode 100644 index 0000000000..f2e3254260 --- /dev/null +++ b/packages/cli/src/utils/plugins.ts @@ -0,0 +1,296 @@ +import { + copySync, + existsSync, + mkdirSync, + readdirSync, + writeFileSync, +} from 'fs-extra' +import { withBasePath } from './directory' +import path from 'path' +import { logger } from './logger' + +export type PageConfig = { + path: string + appLayout: boolean + name: string +} + +export type PluginConfig = { + pages?: { [pageName: string]: Partial } +} + +export type Plugin = + | string + | { + [pluginName: string]: PluginConfig + } + +const PLUGIN_CONFIG_FILE = 'plugin.config.js' + +const sanitizePluginName = (pluginName: string, pascalCase = false) => { + const sanitized = pluginName.split('/')[1] + + if (pascalCase) { + return sanitized + .toLowerCase() + .split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join('') + } + + return sanitized +} + +export const getPluginName = (plugin: Plugin) => { + if (typeof plugin === 'string') { + return plugin + } + + return Object.keys(plugin)[0] +} + +const getPluginCustomConfig = (plugin: Plugin) => { + if (typeof plugin === 'string') { + return {} + } + + return plugin[getPluginName(plugin)] +} + +const getPluginSrcPath = async (basePath: string, pluginName: string) => { + const { getPackagePath } = withBasePath(basePath) + return getPackagePath(pluginName, 'src') +} + +export const getPluginsList = async (basePath: string): Promise => { + const { tmpStoreConfigFile } = withBasePath(basePath) + + try { + const { plugins = [] } = await import(tmpStoreConfigFile) + return plugins + } catch (error) { + logger.error(`Could not load plugins from store config`) + } + + return [] +} + +const copyPluginsSrc = async (basePath: string, plugins: Plugin[]) => { + const { tmpPluginsDir } = withBasePath(basePath) + + logger.log('Copying plugins files') + + plugins.forEach(async (plugin) => { + const pluginName = getPluginName(plugin) + const pluginSrcPath = await getPluginSrcPath( + basePath, + getPluginName(pluginName) + ) + const pluginDestPath = path.join( + tmpPluginsDir, + sanitizePluginName(pluginName) + ) + + copySync(pluginSrcPath, pluginDestPath) + logger.log(`Copied ${pluginName} files`) + }) +} + +const copyPluginPublicFiles = async (basePath: string, plugins: Plugin[]) => { + const { tmpDir, getPackagePath } = withBasePath(basePath) + + logger.log('Copying plugin public files') + + plugins.forEach(async (plugin) => { + const pluginName = getPluginName(plugin) + const pluginPath = getPackagePath(getPluginName(pluginName)) + + try { + if (existsSync(`${pluginPath}/public`)) { + copySync(`${pluginPath}/public`, `${tmpDir}/public`, { + dereference: true, + overwrite: true, + }) + logger.log(`Plugin public files copied`) + } + } catch (e) { + logger.error(e) + } + }) +} + +const getPluginPageFileContent = ( + pluginName: string, + pageName: string, + appLayout: boolean +) => ` +// GENERATED FILE +// @ts-nocheck +import * as page from 'src/plugins/${pluginName}/pages/${pageName}'; +${appLayout ? `import { getGlobalSectionsData } from 'src/components/cms/GlobalSections'` : ``} +${appLayout ? `import RenderSections from 'src/components/cms/RenderSections'` : ``} + +export async function getServerSideProps(${appLayout ? '{ previewData, ...otherProps }' : 'otherProps'}) { + const noop = async function() {} + const loaderData = await (page.loader || noop)(otherProps) +${appLayout ? `const { sections = [] } = await getGlobalSectionsData(previewData)` : ``} + + return { + props: { + data: loaderData, + ${appLayout ? 'globalSections: sections' : ``} + } + } +} +export default function Page(props) { + ${ + appLayout + ? `return + {page.default(props.data)} + ` + : `return page.default(props.data)` + } +} + ` + +const generatePluginPages = async (basePath: string, plugins: Plugin[]) => { + const { tmpPagesDir, getPackagePath } = withBasePath(basePath) + + logger.log('Generating plugin pages') + + plugins.forEach(async (plugin) => { + const pluginName = getPluginName(plugin) + const pluginConfigPath = getPackagePath(pluginName, PLUGIN_CONFIG_FILE) + + const pluginConfig = await import(pluginConfigPath) + + const { pages: pagesCustom } = getPluginCustomConfig(plugin) + + const pagesConfig: Record = { + ...(pluginConfig.pages ?? {}), + ...pagesCustom, + } + + const pages = Object.keys(pagesConfig) + + pages.forEach(async (pageName) => { + const paths = pagesConfig[pageName].path.split('/') + + const pageFile = paths.pop() + const pagePaths = paths + + const pagePath = path.join(tmpPagesDir, ...pagePaths, pageFile + '.tsx') + + const fileContent = getPluginPageFileContent( + sanitizePluginName(pluginName), + pageName, + pagesConfig[pageName].appLayout + ) + + mkdirSync(path.dirname(pagePath), { recursive: true }) + writeFileSync(pagePath, fileContent) + }) + }) +} + +export async function addPluginsSections(basePath: string, plugins: Plugin[]) { + const { tmpPluginsDir, getPackagePath } = withBasePath(basePath) + + logger.log('Adding plugin sections') + + const indexPluginsOverrides = plugins + .filter((plugin) => + existsSync( + getPackagePath(getPluginName(plugin), 'src', 'components', 'index.ts') + ) + ) + .map((plugin) => { + const pluginReference = + sanitizePluginName(getPluginName(plugin), true) + 'Components' + + return { + import: `import { default as ${pluginReference} } from 'src/plugins/${sanitizePluginName(getPluginName(plugin))}/components'`, + pluginReference, + } + }) + + const pluginsImportFileContent = ` + ${indexPluginsOverrides.map((plugin) => plugin.import).join('\n')} + + export default { + ${indexPluginsOverrides.map((plugin) => `...${plugin.pluginReference}`).join(',\n')} + } + ` + + const sectionPath = path.join(tmpPluginsDir, 'index.ts') + writeFileSync(sectionPath, pluginsImportFileContent) + logger.log('Writing plugins overrides') + logger.log(sectionPath) + logger.log(pluginsImportFileContent) +} + +export async function addPluginsOverrides(basePath: string, plugins: Plugin[]) { + const { tmpPluginsDir, getPackagePath } = withBasePath(basePath) + + logger.log('Adding plugin overrides') + + plugins + .map((plugin) => ({ + pluginName: getPluginName(plugin), + pluginOverridesPath: getPackagePath( + getPluginName(plugin), + 'src', + 'components', + 'overrides' + ), + })) + .filter(({ pluginOverridesPath }) => existsSync(pluginOverridesPath)) + .reverse() + .forEach(({ pluginName, pluginOverridesPath }) => { + const overrideFilesAlreadyCopied: string[] = [] + + const sanitizedPluginName = sanitizePluginName(pluginName) + + const overrideFiles = readdirSync(pluginOverridesPath) + + overrideFiles + .filter((file) => !overrideFilesAlreadyCopied.includes(file)) + .forEach((overrideFileName) => { + const overrideFileContent = `export { override } from 'src/plugins/${sanitizedPluginName}/components/overrides/${overrideFileName.split('.')[0]}'` + + writeFileSync( + path.join(tmpPluginsDir, 'overrides', overrideFileName), + overrideFileContent + ) + overrideFilesAlreadyCopied.push(overrideFileName) + }) + }) +} + +const addPluginsTheme = async (basePath: string, plugins: Plugin[]) => { + const { getPackagePath, tmpThemesPluginsFile } = withBasePath(basePath) + + const pluginImportsContent = plugins + .filter((plugin) => + existsSync( + getPackagePath(getPluginName(plugin), 'src', 'themes', 'index.scss') + ) + ) + .map( + (plugin) => `@import "${getPluginName(plugin)}/src/themes/index.scss";` + ) + .join('\n') + + writeFileSync(tmpThemesPluginsFile, pluginImportsContent) +} + +export const installPlugins = async (basePath: string) => { + const plugins = await getPluginsList(basePath) + + copyPluginsSrc(basePath, plugins) + copyPluginPublicFiles(basePath, plugins) + generatePluginPages(basePath, plugins) + addPluginsSections(basePath, plugins) + addPluginsOverrides(basePath, plugins) + addPluginsTheme(basePath, plugins) +} diff --git a/packages/core/src/components/cms/global/Components.ts b/packages/core/src/components/cms/global/Components.ts index e3f07cc731..b9a579edd6 100644 --- a/packages/core/src/components/cms/global/Components.ts +++ b/packages/core/src/components/cms/global/Components.ts @@ -6,6 +6,7 @@ import { OverriddenDefaultNavbar as Navbar } from 'src/components/sections/Navba import { OverriddenDefaultRegionBar as RegionBar } from 'src/components/sections/RegionBar/OverriddenDefaultRegionBar' import CUSTOM_COMPONENTS from 'src/customizations/src/components' +import PLUGINS_COMPONENTS from 'src/plugins' const CartSidebar = dynamic( () => @@ -34,6 +35,7 @@ const COMPONENTS: Record> = { CartSidebar, // out of viewport RegionModal, // out of viewport Footer, // out of viewport + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/components/cms/home/Components.ts b/packages/core/src/components/cms/home/Components.ts index b559b3bb4a..c73f04b585 100644 --- a/packages/core/src/components/cms/home/Components.ts +++ b/packages/core/src/components/cms/home/Components.ts @@ -6,6 +6,7 @@ import Incentives from 'src/components/sections/Incentives' import { default as GLOBAL_COMPONENTS } from '../global/Components' import CUSTOM_COMPONENTS from 'src/customizations/src/components' +import PLUGINS_COMPONENTS from 'src/plugins' const BannerText = dynamic( () => @@ -48,6 +49,7 @@ const COMPONENTS: Record> = { Newsletter, ProductShelf, ProductTiles, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/components/cms/plp/Components.ts b/packages/core/src/components/cms/plp/Components.ts index 0ebf2bdc70..1d8347a44d 100644 --- a/packages/core/src/components/cms/plp/Components.ts +++ b/packages/core/src/components/cms/plp/Components.ts @@ -5,6 +5,7 @@ import { OverriddenDefaultBreadcrumb as Breadcrumb } from 'src/components/sectio import { OverriddenDefaultHero as Hero } from 'src/components/sections/Hero/OverriddenDefaultHero' import { OverriddenDefaultProductGallery as ProductGallery } from 'src/components/sections/ProductGallery/OverriddenDefaultProductGallery' import CUSTOM_COMPONENTS from 'src/customizations/src/components' +import PLUGINS_COMPONENTS from 'src/plugins' import { default as GLOBAL_COMPONENTS } from '../global/Components' const BannerText = dynamic( @@ -53,6 +54,7 @@ const COMPONENTS: Record> = { Newsletter, ProductShelf, ProductTiles, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/components/cms/search/Components.ts b/packages/core/src/components/cms/search/Components.ts index 1a6e0deb28..c3b52ea809 100644 --- a/packages/core/src/components/cms/search/Components.ts +++ b/packages/core/src/components/cms/search/Components.ts @@ -5,6 +5,7 @@ import { OverriddenDefaultBreadcrumb as Breadcrumb } from 'src/components/sectio import { OverriddenDefaultHero as Hero } from 'src/components/sections/Hero/OverriddenDefaultHero' import { OverriddenDefaultProductGallery as ProductGallery } from 'src/components/sections/ProductGallery/OverriddenDefaultProductGallery' import CUSTOM_COMPONENTS from 'src/customizations/src/components' +import PLUGINS_COMPONENTS from 'src/plugins' import { default as GLOBAL_COMPONENTS } from '../global/Components' const BannerText = dynamic( @@ -62,6 +63,7 @@ const COMPONENTS: Record> = { Newsletter, ProductShelf, ProductTiles, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/components/sections/Alert/OverriddenDefaultAlert.ts b/packages/core/src/components/sections/Alert/OverriddenDefaultAlert.ts index 801671febb..f130993bb7 100644 --- a/packages/core/src/components/sections/Alert/OverriddenDefaultAlert.ts +++ b/packages/core/src/components/sections/Alert/OverriddenDefaultAlert.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/Alert' +import { override as overridePlugin } from 'src/plugins/overrides/Alert' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import Alert from '.' @@ -10,6 +11,7 @@ import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinitio * This allows users to override the default Alert section present in the Headless CMS */ export const OverriddenDefaultAlert = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'Alert'>), ...(override as SectionOverrideDefinitionV1<'Alert'>), Section: Alert, }) diff --git a/packages/core/src/components/sections/BannerText/OverriddenDefaultBannerText.ts b/packages/core/src/components/sections/BannerText/OverriddenDefaultBannerText.ts index 35ba287938..dc7c6f0bb7 100644 --- a/packages/core/src/components/sections/BannerText/OverriddenDefaultBannerText.ts +++ b/packages/core/src/components/sections/BannerText/OverriddenDefaultBannerText.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/BannerText' +import { override as overridePlugin } from 'src/plugins/overrides/BannerText' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import BannerText from '.' @@ -10,6 +11,7 @@ import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinitio * This allows users to override the default BannerText section present in the Headless CMS */ export const OverriddenDefaultBannerText = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'BannerText'>), ...(override as SectionOverrideDefinitionV1<'BannerText'>), Section: BannerText, }) diff --git a/packages/core/src/components/sections/Breadcrumb/OverriddenDefaultBreadcrumb.ts b/packages/core/src/components/sections/Breadcrumb/OverriddenDefaultBreadcrumb.ts index 21af53e079..f4f07d1327 100644 --- a/packages/core/src/components/sections/Breadcrumb/OverriddenDefaultBreadcrumb.ts +++ b/packages/core/src/components/sections/Breadcrumb/OverriddenDefaultBreadcrumb.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/Breadcrumb' +import { override as overridePlugin } from 'src/plugins/overrides/Breadcrumb' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import Breadcrumb from '.' @@ -10,6 +11,7 @@ import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinitio * This allows users to override the default Breadcrumb section present in the Headless CMS */ export const OverriddenDefaultBreadcrumb = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'Breadcrumb'>), ...(override as SectionOverrideDefinitionV1<'Breadcrumb'>), Section: Breadcrumb, }) diff --git a/packages/core/src/components/sections/CrossSellingShelf/OverriddenDefaultCrossSellingShelf.ts b/packages/core/src/components/sections/CrossSellingShelf/OverriddenDefaultCrossSellingShelf.ts index 89509e655a..8cc3270102 100644 --- a/packages/core/src/components/sections/CrossSellingShelf/OverriddenDefaultCrossSellingShelf.ts +++ b/packages/core/src/components/sections/CrossSellingShelf/OverriddenDefaultCrossSellingShelf.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/CrossSellingShelf' +import { override as overridePlugin } from 'src/plugins/overrides/CrossSellingShelf' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinition' import CrossSellingShelf from '.' @@ -9,6 +10,7 @@ import CrossSellingShelf from '.' * This allows users to override the default CrossSellingShelf section present in the Headless CMS */ export const OverriddenDefaultCrossSellingShelf = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'CrossSellingShelf'>), ...(override as SectionOverrideDefinitionV1<'CrossSellingShelf'>), Section: CrossSellingShelf, }) diff --git a/packages/core/src/components/sections/EmptyState/OverriddenDefaultEmptyState.ts b/packages/core/src/components/sections/EmptyState/OverriddenDefaultEmptyState.ts index cc43f67ff9..255a107b14 100644 --- a/packages/core/src/components/sections/EmptyState/OverriddenDefaultEmptyState.ts +++ b/packages/core/src/components/sections/EmptyState/OverriddenDefaultEmptyState.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/EmptyState' +import { override as overridePlugin } from 'src/plugins/overrides/EmptyState' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinition' @@ -10,6 +11,7 @@ import EmptyState from './EmptyState' * This allows users to override the default EmptyState section present in the Headless CMS */ export const OverriddenDefaultEmptyState = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'EmptyState'>), ...(override as SectionOverrideDefinitionV1<'EmptyState'>), Section: EmptyState, }) diff --git a/packages/core/src/components/sections/Hero/OverriddenDefaultHero.ts b/packages/core/src/components/sections/Hero/OverriddenDefaultHero.ts index 48754cfd28..378504ad25 100644 --- a/packages/core/src/components/sections/Hero/OverriddenDefaultHero.ts +++ b/packages/core/src/components/sections/Hero/OverriddenDefaultHero.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/Hero' +import { override as overridePlugin } from 'src/plugins/overrides/Hero' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import Hero from '.' @@ -10,6 +11,7 @@ import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinitio * This allows users to override the default Hero section present in the Headless CMS */ export const OverriddenDefaultHero = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'Hero'>), ...(override as SectionOverrideDefinitionV1<'Hero'>), Section: Hero, }) diff --git a/packages/core/src/components/sections/Navbar/OverriddenDefaultNavbar.ts b/packages/core/src/components/sections/Navbar/OverriddenDefaultNavbar.ts index c5c4075fd6..35e4b3ae10 100644 --- a/packages/core/src/components/sections/Navbar/OverriddenDefaultNavbar.ts +++ b/packages/core/src/components/sections/Navbar/OverriddenDefaultNavbar.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/Navbar' +import { override as overridePlugin } from 'src/plugins/overrides/Navbar' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinition' import Navbar from './Navbar' @@ -9,6 +10,7 @@ import Navbar from './Navbar' * This allows users to override the default Navbar section present in the Headless CMS */ export const OverriddenDefaultNavbar = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'Navbar'>), ...(override as SectionOverrideDefinitionV1<'Navbar'>), Section: Navbar, }) diff --git a/packages/core/src/components/sections/Newsletter/OverriddenDefaultNewsletter.ts b/packages/core/src/components/sections/Newsletter/OverriddenDefaultNewsletter.ts index 79ee9dc12d..ad9ac497c5 100644 --- a/packages/core/src/components/sections/Newsletter/OverriddenDefaultNewsletter.ts +++ b/packages/core/src/components/sections/Newsletter/OverriddenDefaultNewsletter.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/Newsletter' +import { override as overridePlugin } from 'src/plugins/overrides/Newsletter' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinition' import Newsletter from './Newsletter' @@ -9,6 +10,7 @@ import Newsletter from './Newsletter' * This allows users to override the default Newsletter section present in the Headless CMS */ export const OverriddenDefaultNewsletter = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'Newsletter'>), ...(override as SectionOverrideDefinitionV1<'Newsletter'>), Section: Newsletter, }) diff --git a/packages/core/src/components/sections/ProductDetails/OverriddenDefaultProductDetails.ts b/packages/core/src/components/sections/ProductDetails/OverriddenDefaultProductDetails.ts index fee8029b1f..03e4ec0cdd 100644 --- a/packages/core/src/components/sections/ProductDetails/OverriddenDefaultProductDetails.ts +++ b/packages/core/src/components/sections/ProductDetails/OverriddenDefaultProductDetails.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/ProductDetails' +import { override as overridePlugin } from 'src/plugins/overrides/ProductDetails' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import ProductDetails from './ProductDetails' @@ -10,6 +11,7 @@ import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinitio * This allows users to override the default ProductDetails section present in the Headless CMS */ export const OverriddenDefaultProductDetails = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'ProductDetails'>), ...(override as SectionOverrideDefinitionV1<'ProductDetails'>), Section: ProductDetails, }) diff --git a/packages/core/src/components/sections/ProductGallery/OverriddenDefaultProductGallery.ts b/packages/core/src/components/sections/ProductGallery/OverriddenDefaultProductGallery.ts index 2358cc96a1..17ee0a3a37 100644 --- a/packages/core/src/components/sections/ProductGallery/OverriddenDefaultProductGallery.ts +++ b/packages/core/src/components/sections/ProductGallery/OverriddenDefaultProductGallery.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/ProductGallery' +import { override as overridePlugin } from 'src/plugins/overrides/ProductGallery' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinition' import ProductGallery from '.' @@ -9,6 +10,7 @@ import ProductGallery from '.' * This allows users to override the default ProductGallery section present in the Headless CMS */ export const OverriddenDefaultProductGallery = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'ProductGallery'>), ...(override as SectionOverrideDefinitionV1<'ProductGallery'>), Section: ProductGallery, }) diff --git a/packages/core/src/components/sections/ProductShelf/OverriddenDefaultProductShelf.ts b/packages/core/src/components/sections/ProductShelf/OverriddenDefaultProductShelf.ts index 0303b79a2e..e5e435969c 100644 --- a/packages/core/src/components/sections/ProductShelf/OverriddenDefaultProductShelf.ts +++ b/packages/core/src/components/sections/ProductShelf/OverriddenDefaultProductShelf.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/ProductShelf' +import { override as overridePlugin } from 'src/plugins/overrides/ProductShelf' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import ProductShelf from '.' @@ -10,6 +11,7 @@ import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinitio * This allows users to override the default ProductShelf section present in the Headless CMS */ export const OverriddenDefaultProductShelf = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'ProductShelf'>), ...(override as SectionOverrideDefinitionV1<'ProductShelf'>), Section: ProductShelf, }) diff --git a/packages/core/src/components/sections/RegionBar/OverriddenDefaultRegionBar.ts b/packages/core/src/components/sections/RegionBar/OverriddenDefaultRegionBar.ts index b937467d5d..102c3ba405 100644 --- a/packages/core/src/components/sections/RegionBar/OverriddenDefaultRegionBar.ts +++ b/packages/core/src/components/sections/RegionBar/OverriddenDefaultRegionBar.ts @@ -1,4 +1,5 @@ import { override } from 'src/customizations/src/components/overrides/RegionBar' +import { override as overridePlugin } from 'src/plugins/overrides/RegionBar' import { getOverriddenSection } from 'src/sdk/overrides/getOverriddenSection' import type { SectionOverrideDefinitionV1 } from 'src/typings/overridesDefinition' import RegionBar from '.' @@ -9,6 +10,7 @@ import RegionBar from '.' * This allows users to override the default RegionBar section present in the Headless CMS */ export const OverriddenDefaultRegionBar = getOverriddenSection({ + ...(overridePlugin as SectionOverrideDefinitionV1<'RegionBar'>), ...(override as SectionOverrideDefinitionV1<'RegionBar'>), Section: RegionBar, }) diff --git a/packages/core/src/components/templates/LandingPage/LandingPage.tsx b/packages/core/src/components/templates/LandingPage/LandingPage.tsx index dbf9841a1f..ee89e72cf3 100644 --- a/packages/core/src/components/templates/LandingPage/LandingPage.tsx +++ b/packages/core/src/components/templates/LandingPage/LandingPage.tsx @@ -11,6 +11,7 @@ import Incentives from 'src/components/sections/Incentives' import { OverriddenDefaultNewsletter as Newsletter } from 'src/components/sections/Newsletter/OverriddenDefaultNewsletter' import { OverriddenDefaultProductShelf as ProductShelf } from 'src/components/sections/ProductShelf/OverriddenDefaultProductShelf' import ProductTiles from 'src/components/sections/ProductTiles' +import PLUGINS_COMPONENTS from 'src/plugins' import CUSTOM_COMPONENTS from 'src/customizations/src/components' import { default as GLOBAL_COMPONENTS } from 'src/components/cms/global/Components' import MissingContentError from 'src/sdk/error/MissingContentError/MissingContentError' @@ -31,6 +32,7 @@ const COMPONENTS: Record> = { Newsletter, ProductShelf, ProductTiles, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/customizations/src/GlobalOverrides.tsx b/packages/core/src/customizations/src/GlobalOverrides.tsx index 77ed094088..4872265ab8 100644 --- a/packages/core/src/customizations/src/GlobalOverrides.tsx +++ b/packages/core/src/customizations/src/GlobalOverrides.tsx @@ -1,9 +1,15 @@ import WebFontsOverrides from 'src/customizations/src/components/overrides/WebFonts' import { default as CoreWebFonts } from 'src/fonts/WebFonts' import ThirdPartyScriptsOverrides from 'src/customizations/src/components/overrides/ThirdPartyScripts' +import ThirdPartyScriptsPluginsOverrides from 'src/plugins/overrides/ThirdPartyScripts' +import WebFontsOverridesPlugins from 'src/plugins/overrides/WebFonts' const Components = { WebFonts: CoreWebFonts, + + ...ThirdPartyScriptsPluginsOverrides.components, + ...WebFontsOverridesPlugins.components, + ...WebFontsOverrides.components, ...ThirdPartyScriptsOverrides.components, } diff --git a/packages/core/src/pages/404.tsx b/packages/core/src/pages/404.tsx index 49d477b5c3..baaebfd175 100644 --- a/packages/core/src/pages/404.tsx +++ b/packages/core/src/pages/404.tsx @@ -10,6 +10,7 @@ import { import { default as GLOBAL_COMPONENTS } from 'src/components/cms/global/Components' import RenderSections from 'src/components/cms/RenderSections' import { OverriddenDefaultEmptyState as EmptyState } from 'src/components/sections/EmptyState/OverriddenDefaultEmptyState' +import PLUGINS_COMPONENTS from 'src/plugins' import CUSTOM_COMPONENTS from 'src/customizations/src/components' import { PageContentType, getPage } from 'src/server/cms' @@ -17,6 +18,7 @@ import { PageContentType, getPage } from 'src/server/cms' const COMPONENTS: Record> = { ...GLOBAL_COMPONENTS, EmptyState, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/pages/500.tsx b/packages/core/src/pages/500.tsx index afa0771dbb..32bb7332db 100644 --- a/packages/core/src/pages/500.tsx +++ b/packages/core/src/pages/500.tsx @@ -10,6 +10,7 @@ import { import { default as GLOBAL_COMPONENTS } from 'src/components/cms/global/Components' import RenderSections from 'src/components/cms/RenderSections' import { OverriddenDefaultEmptyState as EmptyState } from 'src/components/sections/EmptyState/OverriddenDefaultEmptyState' +import PLUGINS_COMPONENTS from 'src/plugins' import CUSTOM_COMPONENTS from 'src/customizations/src/components' import { PageContentType, getPage } from 'src/server/cms' @@ -17,6 +18,7 @@ import { PageContentType, getPage } from 'src/server/cms' const COMPONENTS: Record> = { ...GLOBAL_COMPONENTS, EmptyState, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/pages/[slug]/p.tsx b/packages/core/src/pages/[slug]/p.tsx index e7791cb9e3..ba0c16c135 100644 --- a/packages/core/src/pages/[slug]/p.tsx +++ b/packages/core/src/pages/[slug]/p.tsx @@ -21,6 +21,7 @@ import { OverriddenDefaultNewsletter as Newsletter } from 'src/components/sectio import { OverriddenDefaultProductDetails as ProductDetails } from 'src/components/sections/ProductDetails/OverriddenDefaultProductDetails' import { OverriddenDefaultProductShelf as ProductShelf } from 'src/components/sections/ProductShelf/OverriddenDefaultProductShelf' import ProductTiles from 'src/components/sections/ProductTiles' +import PLUGINS_COMPONENTS from 'src/plugins' import CUSTOM_COMPONENTS from 'src/customizations/src/components' import { useSession } from 'src/sdk/session' import { execute } from 'src/server' @@ -49,6 +50,7 @@ const COMPONENTS: Record> = { ProductShelf, ProductTiles, CrossSellingShelf, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/pages/_app.tsx b/packages/core/src/pages/_app.tsx index 44aca4b429..d8a3bb9bc0 100644 --- a/packages/core/src/pages/_app.tsx +++ b/packages/core/src/pages/_app.tsx @@ -7,6 +7,7 @@ import SEO from '../../next-seo.config' // FastStore UI's base styles import '../styles/global/index.scss' +import '../plugins/index.scss' import '../customizations/src/themes/index.scss' import { DefaultSeo } from 'next-seo' diff --git a/packages/core/src/pages/login.tsx b/packages/core/src/pages/login.tsx index 4a7451a981..99dcda76f4 100644 --- a/packages/core/src/pages/login.tsx +++ b/packages/core/src/pages/login.tsx @@ -11,6 +11,7 @@ import { } from 'src/components/cms/GlobalSections' import RenderSections from 'src/components/cms/RenderSections' import { OverriddenDefaultEmptyState as EmptyState } from 'src/components/sections/EmptyState/OverriddenDefaultEmptyState' +import PLUGINS_COMPONENTS from 'src/plugins' import CUSTOM_COMPONENTS from 'src/customizations/src/components' import { PageContentType, getPage } from 'src/server/cms' import storeConfig from '../../discovery.config' @@ -19,6 +20,7 @@ import storeConfig from '../../discovery.config' const COMPONENTS: Record> = { ...GLOBAL_COMPONENTS, EmptyState, + ...PLUGINS_COMPONENTS, ...CUSTOM_COMPONENTS, } diff --git a/packages/core/src/plugins/.gitkeep b/packages/core/src/plugins/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/plugins/index.scss b/packages/core/src/plugins/index.scss new file mode 100644 index 0000000000..59811cc1a2 --- /dev/null +++ b/packages/core/src/plugins/index.scss @@ -0,0 +1,3 @@ +// ---------------------------------------------------------- +// Plugins' themes will override this file +// ---------------------------------------------------------- diff --git a/packages/core/src/plugins/index.ts b/packages/core/src/plugins/index.ts new file mode 100644 index 0000000000..f51d770c70 --- /dev/null +++ b/packages/core/src/plugins/index.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line import/no-anonymous-default-export +export default {} diff --git a/packages/core/src/plugins/overrides/Alert.tsx b/packages/core/src/plugins/overrides/Alert.tsx new file mode 100644 index 0000000000..b4ea57ef7e --- /dev/null +++ b/packages/core/src/plugins/overrides/Alert.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/Alert' diff --git a/packages/core/src/plugins/overrides/BannerText.tsx b/packages/core/src/plugins/overrides/BannerText.tsx new file mode 100644 index 0000000000..2994ef1a3b --- /dev/null +++ b/packages/core/src/plugins/overrides/BannerText.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/BannerText' diff --git a/packages/core/src/plugins/overrides/Breadcrumb.tsx b/packages/core/src/plugins/overrides/Breadcrumb.tsx new file mode 100644 index 0000000000..63c97eb129 --- /dev/null +++ b/packages/core/src/plugins/overrides/Breadcrumb.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/Breadcrumb' diff --git a/packages/core/src/plugins/overrides/CrossSellingShelf.tsx b/packages/core/src/plugins/overrides/CrossSellingShelf.tsx new file mode 100644 index 0000000000..d400dc09e8 --- /dev/null +++ b/packages/core/src/plugins/overrides/CrossSellingShelf.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/CrossSellingShelf' diff --git a/packages/core/src/plugins/overrides/EmptyState.tsx b/packages/core/src/plugins/overrides/EmptyState.tsx new file mode 100644 index 0000000000..0f2a072f80 --- /dev/null +++ b/packages/core/src/plugins/overrides/EmptyState.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/EmptyState' diff --git a/packages/core/src/plugins/overrides/Hero.tsx b/packages/core/src/plugins/overrides/Hero.tsx new file mode 100644 index 0000000000..225bac3493 --- /dev/null +++ b/packages/core/src/plugins/overrides/Hero.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/Hero' diff --git a/packages/core/src/plugins/overrides/Navbar.tsx b/packages/core/src/plugins/overrides/Navbar.tsx new file mode 100644 index 0000000000..3d7dcbc801 --- /dev/null +++ b/packages/core/src/plugins/overrides/Navbar.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/Navbar' diff --git a/packages/core/src/plugins/overrides/Newsletter.tsx b/packages/core/src/plugins/overrides/Newsletter.tsx new file mode 100644 index 0000000000..0da945ad41 --- /dev/null +++ b/packages/core/src/plugins/overrides/Newsletter.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/Newsletter' diff --git a/packages/core/src/plugins/overrides/ProductDetails.tsx b/packages/core/src/plugins/overrides/ProductDetails.tsx new file mode 100644 index 0000000000..277e1fde77 --- /dev/null +++ b/packages/core/src/plugins/overrides/ProductDetails.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/ProductDetails' diff --git a/packages/core/src/plugins/overrides/ProductGallery.tsx b/packages/core/src/plugins/overrides/ProductGallery.tsx new file mode 100644 index 0000000000..bc9feca0e3 --- /dev/null +++ b/packages/core/src/plugins/overrides/ProductGallery.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/ProductGallery' diff --git a/packages/core/src/plugins/overrides/ProductShelf.tsx b/packages/core/src/plugins/overrides/ProductShelf.tsx new file mode 100644 index 0000000000..1ebae07955 --- /dev/null +++ b/packages/core/src/plugins/overrides/ProductShelf.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/ProductShelf' diff --git a/packages/core/src/plugins/overrides/RegionBar.tsx b/packages/core/src/plugins/overrides/RegionBar.tsx new file mode 100644 index 0000000000..f4c6bc43de --- /dev/null +++ b/packages/core/src/plugins/overrides/RegionBar.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { override } from 'src/customizations/src/components/overrides/RegionBar' diff --git a/packages/core/src/plugins/overrides/ThirdPartyScripts.tsx b/packages/core/src/plugins/overrides/ThirdPartyScripts.tsx new file mode 100644 index 0000000000..e32f63c144 --- /dev/null +++ b/packages/core/src/plugins/overrides/ThirdPartyScripts.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { default } from 'src/customizations/src/components/overrides/ThirdPartyScripts' diff --git a/packages/core/src/plugins/overrides/WebFonts.tsx b/packages/core/src/plugins/overrides/WebFonts.tsx new file mode 100644 index 0000000000..c192792437 --- /dev/null +++ b/packages/core/src/plugins/overrides/WebFonts.tsx @@ -0,0 +1,3 @@ +// This is an example of how it can be used on the plugins. + +export { default } from 'src/customizations/src/components/overrides/WebFonts'