diff --git a/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md b/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md index 648e87d0994..b26b41a29ac 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md +++ b/frontend/packages/console-dynamic-plugin-sdk/CHANGELOG-webpack.md @@ -12,6 +12,9 @@ table in [Console dynamic plugins README](./README.md). ## 4.23.0-prerelease.6 - TBD +- Add `ConsoleRemotePlugin` option `baseDir` to support multi-plugin builds ([OCPBUGS-111520], [#17002]) +- Expand PatternFly CSS package list to include `@patternfly/patternfly` ([#17029]) +- Warn when the plugin has a direct dependency on PatternFly CSS packages ([#17029]) - Minimum compatible version of `@rspack/core` peer dependency increased to `2.1.10` ([OCPBUGS-109592], [#16978]) ## 4.23.0-prerelease.5 - 2026-08-04 @@ -169,6 +172,7 @@ table in [Console dynamic plugins README](./README.md). [OCPBUGS-84338]: https://issues.redhat.com/browse/OCPBUGS-84338 [OCPBUGS-88319]: https://issues.redhat.com/browse/OCPBUGS-88319 [OCPBUGS-109592]: https://issues.redhat.com/browse/OCPBUGS-109592 +[OCPBUGS-111520]: https://issues.redhat.com/browse/OCPBUGS-111520 [#13188]: https://github.com/openshift/console/pull/13188 [#13388]: https://github.com/openshift/console/pull/13388 [#13521]: https://github.com/openshift/console/pull/13521 @@ -200,3 +204,5 @@ table in [Console dynamic plugins README](./README.md). [#16752]: https://github.com/openshift/console/pull/16752 [#16115]: https://github.com/openshift/console/pull/16115 [#16978]: https://github.com/openshift/console/pull/16978 +[#17002]: https://github.com/openshift/console/pull/17002 +[#17029]: https://github.com/openshift/console/pull/17029 diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts b/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts index 4acd90988b0..32fe61d0650 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/webpack/ConsoleRemotePlugin.ts @@ -25,9 +25,9 @@ import { ValidationResult } from '../validation/ValidationResult'; import type { DynamicModulePackageSpecs } from './DynamicModuleImportPlugin'; import { DynamicModuleImportPlugin, resolveDynamicModuleMaps } from './DynamicModuleImportPlugin'; -const loadPluginPackageJSON = () => readPkg.sync({ normalize: false }) as ConsolePluginPackageJSON; - -// Resolve from cwd, not this file's real path, so symlinked SDK installations work +// Resolve from process.cwd(), not this file's real path, so symlinked SDK installations work. +// It should not be necessary to customize the process.cwd() resolution base path since module +// bundlers typically use a hoisted top-level node_modules hierarchy. const loadVendorPackageJSON = (moduleName: string) => // eslint-disable-next-line @typescript-eslint/no-require-imports require( @@ -55,8 +55,12 @@ const hasPackageDependency = (pkg: readPkg.PackageJson, depName: string) => const getPluginSDKPackagePeerDependencies = () => loadVendorPackageJSON('@openshift-console/dynamic-plugin-sdk').peerDependencies; -const getPatternFlyStyles = (baseDir: string) => - glob.sync(`${baseDir}/node_modules/@patternfly/react-styles/**/*.css`); +const patternFlyStylePackages = ['@patternfly/patternfly', '@patternfly/react-styles']; + +const getPatternFlyCSSFiles = (baseDir: string): string[] => + patternFlyStylePackages + .map((moduleName) => glob.sync(`${baseDir}/node_modules/${moduleName}/**/*.css`)) + .flat(); /** * Get webpack shared module configuration to use by Console plugins. @@ -140,7 +144,7 @@ export const validateConsoleExtensionsFileSchema = ( return new SchemaValidator(description).validate(schema, extensions); }; -const getCompileTimeSharedModuleWarnings = (pkg: ConsolePluginPackageJSON): string[] => { +const getCompileTimeModuleWarnings = (pkg: ConsolePluginPackageJSON): string[] => { const warnings: string[] = []; sharedPluginModules.forEach((moduleName) => { @@ -155,6 +159,14 @@ const getCompileTimeSharedModuleWarnings = (pkg: ConsolePluginPackageJSON): stri } }); + patternFlyStylePackages.forEach((moduleName) => { + if (hasPackageDependency(pkg, moduleName)) { + warnings.push( + `[WARNING] Detected direct dependency on ${moduleName}, its modules are ignored to avoid breaking Console provided PatternFly CSS`, + ); + } + }); + return warnings; }; @@ -215,6 +227,15 @@ export const dynamicModuleImportTransformFilter = (moduleRequest: string) => { }; export type ConsoleRemotePluginOptions = Partial<{ + /** + * Base directory for resolving relative paths when processing plugin assets. + * + * Must be an absolute path. + * + * If not specified, `process.cwd()` will be used as the base directory. + */ + baseDir: string; + /** * Console dynamic plugin metadata. * @@ -316,7 +337,7 @@ export type ConsoleRemotePluginOptions = Partial<{ * * If not specified, the list will contain a single entry: * ```ts - * path.resolve(process.cwd(), 'node_modules') + * path.resolve(baseDir, 'node_modules') * ``` */ modulePaths: string[]; @@ -352,25 +373,28 @@ export type ConsoleRemotePluginOptions = Partial<{ /** * Generates Console dynamic plugin remote container and related assets. * - * Refer to `console-dynamic-plugin-sdk/src/shared-modules.ts` for details on Console provided - * shared modules and their configuration. - * - * @see {@link sharedPluginModules} - * @see {@link getSharedModuleMetadata} + * Refer to {@link sharedPluginModules} for details on Console provided shared modules and their configuration. */ export class ConsoleRemotePlugin implements WebpackPluginInstance { private readonly adaptedOptions: Required; - private readonly baseDir = process.cwd(); - - private readonly pkg = loadPluginPackageJSON(); + private readonly pkg: ConsolePluginPackageJSON; private readonly dynamicModuleMaps: Record; constructor(options: ConsoleRemotePluginOptions = {}) { + const baseDir = options.baseDir ?? process.cwd(); + + if (!path.isAbsolute(baseDir)) { + throw new Error(`baseDir must be an absolute path: ${baseDir}`); + } + + this.pkg = readPkg.sync({ cwd: baseDir, normalize: false }); + this.adaptedOptions = { + baseDir, pluginMetadata: options.pluginMetadata ?? this.pkg.consolePlugin, - extensions: options.extensions ?? parseJSONC(path.resolve(this.baseDir, extensionsFile)), + extensions: options.extensions ?? parseJSONC(path.resolve(baseDir, extensionsFile)), validateExtensionSchema: options.validateExtensionSchema ?? true, validateExtensionIntegrity: options.validateExtensionIntegrity ?? true, validateSharedModules: options.validateSharedModules ?? true, @@ -401,7 +425,7 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance { } const resolvedModulePaths = this.adaptedOptions.sharedDynamicModuleSettings.modulePaths ?? [ - path.resolve(process.cwd(), 'node_modules'), + path.resolve(baseDir, 'node_modules'), ]; this.dynamicModuleMaps = resolveDynamicModuleMaps( @@ -412,8 +436,13 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance { } apply(compiler: Compiler) { - const { pluginMetadata, extensions, validateExtensionIntegrity, sharedDynamicModuleSettings } = - this.adaptedOptions; + const { + baseDir, + pluginMetadata, + extensions, + validateExtensionIntegrity, + sharedDynamicModuleSettings, + } = this.adaptedOptions; const { name, @@ -441,8 +470,9 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance { compiler.options.resolve = compiler.options.resolve ?? {}; compiler.options.resolve.alias = compiler.options.resolve.alias ?? {}; - // Prevent PatternFly styles from being included in the compilation - getPatternFlyStyles(this.baseDir).forEach((cssFile) => { + // Prevent PatternFly CSS files from being included in the webpack compilation. + // Console is responsible for loading all supported PatternFly CSS at runtime. + getPatternFlyCSSFiles(baseDir).forEach((cssFile) => { if (Array.isArray(compiler.options.resolve.alias)) { compiler.options.resolve.alias.push({ name: cssFile, alias: false }); } else { @@ -487,7 +517,7 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance { compilation, extensions, exposedModules ?? {}, - path.dirname(path.resolve(this.baseDir, extensionsFile)), + path.dirname(path.resolve(baseDir, extensionsFile)), ); if (result.hasErrors()) { @@ -498,7 +528,7 @@ export class ConsoleRemotePlugin implements WebpackPluginInstance { } } - getCompileTimeSharedModuleWarnings(this.pkg).forEach((message) => { + getCompileTimeModuleWarnings(this.pkg).forEach((message) => { compilation.warnings.push(new compiler.webpack.WebpackError(message)); }); });