UNPKG

@openshift-console/dynamic-plugin-sdk-webpack

Version:

Provides webpack ConsoleRemotePlugin used to build all dynamic plugin assets.

114 lines (113 loc) 5.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExtensionValidator = exports.findWebpackModules = exports.collectCodeRefData = exports.guessModuleFilePath = void 0; const fs = require("fs"); const path = require("path"); const _ = require("lodash"); const coderef_resolver_1 = require("../coderefs/coderef-resolver"); const object_1 = require("../utils/object"); const BaseValidator_1 = require("./BaseValidator"); /** * Guess the file path of the module (e.g., full path with extension, * or relevant barrel file) based on the given base path. * * Returns the base path if no relevant module file is found. * * @param basePath The base file path to start guessing from. * @param msgCallback Optional callback to log messages. */ const guessModuleFilePath = (basePath, msgCallback = _.noop) => { // Path already contains a file extension (no extra guessing needed) if (path.extname(basePath)) { return basePath; } // Check if the path refers to an barrel file (base path only specified the directory) const barrelFile = ['index.ts', 'index.js', 'index.tsx', 'index.jsx'] .map((i) => path.resolve(basePath, i)) .find(fs.existsSync); if (barrelFile) { msgCallback(`The module ${basePath} refers to an barrel file ${barrelFile}. Barrel files are not recommended as they may cause unnecessary code to be loaded. Consider specifying the module file path directly.`); return barrelFile; } // Check if the base path neglected to include a file extension const moduleFile = ['.tsx', '.ts', '.jsx', '.js'] .map((ext) => `${basePath}${ext}`) .find(fs.existsSync); if (moduleFile) { msgCallback(`The module ${basePath} refers to file ${moduleFile}, but a file extension was not specified.`); return moduleFile; } // No relevant file could be found, return the original base path. return basePath; }; exports.guessModuleFilePath = guessModuleFilePath; const collectCodeRefData = (extensions) => extensions.reduce((acc, e, index) => { const data = { index, propToCodeRefValue: {} }; (0, object_1.deepForOwn)(e.properties, coderef_resolver_1.isEncodedCodeRef, (ref, key) => { data.propToCodeRefValue[key] = ref.$codeRef; }); if (!_.isEmpty(data.propToCodeRefValue)) { acc.push(data); } return acc; }, []); exports.collectCodeRefData = collectCodeRefData; const findWebpackModules = (compilation, exposedModules, pluginBasePath) => { const webpackModules = Array.from(compilation.modules); return Object.keys(exposedModules).reduce((acc, moduleName) => { const absolutePath = pluginBasePath && (0, exports.guessModuleFilePath)(path.resolve(pluginBasePath, exposedModules[moduleName])); acc[moduleName] = webpackModules.find((m) => { // @ts-expect-error rootModule is internal to webpack's ModuleConcatenationPlugin const rootModule = m?.rootModule; /* first strategy: check if the module name matches the rawRequest */ const rawRequest = m?.rawRequest || rootModule?.rawRequest; const matchesRawRequest = rawRequest && rawRequest === exposedModules[moduleName]; /* second strategy: check if the absolute path matches the resource */ const resource = m?.resource || rootModule?.resource; const matchesAbsolutePath = resource && resource === absolutePath; return matchesRawRequest || matchesAbsolutePath; }); return acc; }, {}); }; exports.findWebpackModules = findWebpackModules; class ExtensionValidator extends BaseValidator_1.BaseValidator { validate(compilation, extensions, exposedModules, pluginBasePath) { const codeRefs = (0, exports.collectCodeRefData)(extensions); const webpackModules = (0, exports.findWebpackModules)(compilation, exposedModules, pluginBasePath); // Each exposed module must have at least one code reference Object.keys(exposedModules).forEach((moduleName) => { const moduleReferenced = codeRefs.some((data) => Object.values(data.propToCodeRefValue).some((value) => { const [parsedModuleName] = (0, coderef_resolver_1.parseEncodedCodeRefValue)(value); return parsedModuleName && moduleName === parsedModuleName; })); if (!moduleReferenced) { this.result.addError(`Exposed module '${moduleName}' is not referenced by any extension`); } }); // Each code reference must point to a valid webpack module export codeRefs.forEach((data) => { Object.entries(data.propToCodeRefValue).forEach(([propName, codeRefValue]) => { const [moduleName, exportName] = (0, coderef_resolver_1.parseEncodedCodeRefValue)(codeRefValue); const errorTrace = `in extension [${data.index}] property '${propName}'`; if (!moduleName || !exportName) { this.result.addError(`Invalid code reference '${codeRefValue}' ${errorTrace}`); return; } const foundModule = webpackModules[moduleName]; if (!foundModule) { this.result.addError(`Invalid module '${moduleName}' ${errorTrace}`); return; } const moduleExports = compilation.moduleGraph.getProvidedExports(foundModule); const exportValid = Array.isArray(moduleExports) && moduleExports.includes(exportName); if (!exportValid) { this.result.addError(`Invalid module export '${exportName}' ${errorTrace}`); } }); }); return this.result; } } exports.ExtensionValidator = ExtensionValidator;