/** * Copyright 2021 Google LLC. * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview taken from {@link https://github.com/GoogleChromeLabs/chromium-bidi/blob/main/rollup.config.mjs | chromium-bidi} * and modified to specific requirement. */ import fs from 'node:fs'; import path from 'node:path'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import {nodeResolve} from '@rollup/plugin-node-resolve'; import cleanup from 'rollup-plugin-cleanup'; import license from 'rollup-plugin-license'; const isProduction = process.env.NODE_ENV === 'production'; const allowedLicenses = [ 'MIT', 'Apache 2.0', 'Apache-2.0', 'BSD-3-Clause', 'BSD-2-Clause', 'ISC', '0BSD', ]; const thirdPartyDir = './build/src/third_party'; const {devDependencies = {}} = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'), ); // special case for puppeteer, from which we only bundle puppeteer-core devDependencies['puppeteer-core'] = devDependencies['puppeteer']; const packageLock = JSON.parse( fs.readFileSync(path.join(process.cwd(), 'package-lock.json'), 'utf-8'), ); function getResolvedVersion(packageName) { const packageEntry = packageLock.packages?.[`node_modules/${packageName}`]; return packageEntry?.version || null; } const aggregatedStats = { bundlesProcessed: 0, totalBundles: 0, bundledPackages: new Set(), }; const projectNodeModulesPath = path.join(process.cwd(), 'node_modules') + path.sep; function getPackageName(modulePath) { // Handle rollup's virtual module paths (paths starting with 0x00) const absolutePathStart = modulePath.indexOf(projectNodeModulesPath); if (absolutePathStart < 0) { return null; } const relativePath = modulePath.slice( projectNodeModulesPath.length + absolutePathStart, ); const segments = relativePath.split(path.sep); // handle scoped packages if (segments[0].startsWith('@') && segments[1]) { return `${segments[0]}/${segments[1]}`; } return segments[0]; } /** * @returns {import('rollup').Plugin} */ function listBundledDeps() { aggregatedStats.totalBundles++; return { name: 'gather-bundled-dependencies', generateBundle(options, bundle) { for (const chunk of Object.values(bundle)) { if (chunk.type === 'chunk' && chunk.modules) { // chunk.modules is an object where keys are the absolute file paths Object.keys(chunk.modules).forEach(modulePath => { const packageName = getPackageName(modulePath); if (packageName) { aggregatedStats.bundledPackages.add(packageName); } }); } } aggregatedStats.bundlesProcessed++; // Only write the file when the last bundle is finished if (aggregatedStats.bundlesProcessed === aggregatedStats.totalBundles) { const outputPath = path.join(thirdPartyDir, 'bundled-packages.json'); const bundledDevDeps = {}; for (const [name, versionRange] of Object.entries(devDependencies)) { if ( aggregatedStats.bundledPackages.has(name) || name === 'chrome-devtools-frontend' || name === 'lighthouse' ) { const resolvedVersion = getResolvedVersion(name); bundledDevDeps[name] = resolvedVersion || versionRange; } } fs.writeFileSync(outputPath, JSON.stringify(bundledDevDeps, null, 2)); } }, }; } const seenDependencies = new Map(); /** * @param {string} wrapperIndexName * @param {import('rollup').OutputOptions} [extraOutputOptions={}] * @param {import('rollup').ExternalOption} [external=[]] * @returns {import('rollup').RollupOptions} */ const bundleDependency = ( wrapperIndexName, extraOutputOptions = {}, external = [], ) => ({ input: path.join(thirdPartyDir, wrapperIndexName), output: { ...extraOutputOptions, file: path.join(thirdPartyDir, wrapperIndexName), sourcemap: !isProduction, format: 'esm', }, plugins: [ cleanup({ // Keep license comments. Other comments are removed due to // http://b/390559299 and // https://github.com/microsoft/TypeScript/issues/60811. comments: [/Copyright/i], }), license({ thirdParty: { allow: { test: dependency => { return allowedLicenses.includes(dependency.license); }, failOnUnlicensed: true, failOnViolation: true, }, output: { file: path.join(thirdPartyDir, 'THIRD_PARTY_NOTICES'), template(dependencies) { for (const dependency of dependencies) { const key = `${dependency.name}:${dependency.version}`; seenDependencies.set(key, dependency); } const stringifiedDependencies = Array.from( seenDependencies.values(), ).map(dependency => { let arr = []; arr.push(`Name: ${dependency.name ?? 'N/A'}`); let url = dependency.homepage ?? dependency.repository; if (url !== null && typeof url !== 'string') { url = url.url; } arr.push(`URL: ${url ?? 'N/A'}`); arr.push(`Version: ${dependency.version ?? 'N/A'}`); arr.push(`License: ${dependency.license ?? 'N/A'}`); if (dependency.licenseText !== null) { arr.push(''); arr.push(dependency.licenseText.replaceAll('\r', '')); } return arr.join('\n'); }); // Manual license handling for chrome-devtools-frontend third_party const tsConfig = JSON.parse( fs.readFileSync( path.join(process.cwd(), 'tsconfig.json'), 'utf-8', ), ); const thirdPartyDirectories = tsConfig.include.filter(location => location.includes( 'node_modules/chrome-devtools-frontend/front_end/third_party', ), ); const manualLicenses = []; // Add chrome-devtools-frontend main license const cdtfLicensePath = path.join( process.cwd(), 'node_modules/chrome-devtools-frontend/LICENSE', ); if (fs.existsSync(cdtfLicensePath)) { manualLicenses.push( [ 'Name: chrome-devtools-frontend', 'License: Apache-2.0', '', fs.readFileSync(cdtfLicensePath, 'utf-8'), ].join('\n'), ); } // Add chrome-devtools-frontend main license const lighthouseLicensePath = path.join( process.cwd(), 'node_modules/lighthouse/LICENSE', ); if (fs.existsSync(lighthouseLicensePath)) { manualLicenses.push( [ 'Name: lighthouse', 'License: Apache-2.0', '', fs.readFileSync(lighthouseLicensePath, 'utf-8'), ].join('\n'), ); } for (const thirdPartyDir of thirdPartyDirectories) { const fullPath = path.join(process.cwd(), thirdPartyDir); const licenseFile = path.join(fullPath, 'LICENSE'); if (fs.existsSync(licenseFile)) { const name = path.basename(thirdPartyDir); manualLicenses.push( [ `Name: ${name}`, `License:`, '', fs.readFileSync(licenseFile, 'utf-8').replaceAll('\r', ''), ].join('\n'), ); } } if (manualLicenses.length > 0) { stringifiedDependencies.push(...manualLicenses); } const divider = '\n\n-------------------- DEPENDENCY DIVIDER --------------------\n\n'; return stringifiedDependencies.join(divider); }, }, }, }), listBundledDeps(), commonjs(), json(), nodeResolve(), ], external, }); export default [ bundleDependency( 'index.js', { inlineDynamicImports: true, }, (source, importer, _isResolved) => { if ( source === 'yargs' && importer && importer.includes('puppeteer-core') ) { return true; } if ( source === '@toon-format/toon' || source.startsWith('@toon-format/toon/') ) { return true; } if ( source === '@blackwell-systems/gcf' || source.startsWith('@blackwell-systems/gcf/') ) { return true; } const existingExternals = [ './bidi.js', '../bidi/bidi.js', './lighthouse-devtools-mcp-bundle.js', ]; if (existingExternals.includes(source)) { return true; } return false; }, ), bundleDependency( 'devtools-formatter-worker.js', { inlineDynamicImports: true, }, (_source, _importer, _isResolved) => false, ), bundleDependency( 'devtools-heap-snapshot-worker.js', { inlineDynamicImports: true, }, (_source, _importer, _isResolved) => false, ), ];