desktop/scripts/copy-package-with-dependencies.tsx (106 lines of code) (raw):

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ import fs from 'fs-extra'; import path from 'path'; import ignore from 'ignore'; const DEFAULT_BUILD_IGNORES = [ '/node_modules', 'README*', 'LICENSE*', '*.ts', '*.ls', '*.flow', '*.tsbuildinfo', '*.map', 'Gruntfile*', ]; /** * This function copies package into the specified target dir with all its dependencies: * 1) Both direct and transitive dependencies are copied. * 2) Symlinks are dereferenced and copied to the target dir as normal dirs. * 3) Hoisting is supported, so the function scans node_modules up the file tree until dependency is resolved. * 4) All the dependencies keep their scopes, e.g. dependency from <packageDir>/node_modules/package1/node_modules/package2 * is copied to <targetDir>/node_modules/package1/node_modules/package2. * 5) Prints informative error and fails fast if a dependency is not resolved. */ export default async function copyPackageWithDependencies( packageDir: string, targetDir: string, ) { await fs.remove(targetDir); await copyPackageWithDependenciesRecursive(packageDir, targetDir, targetDir); } async function copyPackageWithDependenciesRecursive( packageDir: string, targetDir: string, rootTargetDir: string, ) { if (await fs.pathExists(targetDir)) { return; } await fs.mkdirp(targetDir); if ((await fs.stat(packageDir)).isSymbolicLink()) { packageDir = await fs.readlink(packageDir); } const ignores = await fs .readFile(path.join(packageDir, '.buildignore'), 'utf-8') .then((l) => l.split('\n')) .catch((_e) => []) .then((l: Array<string>) => ignore().add(DEFAULT_BUILD_IGNORES.concat(l))); await fs.copy(packageDir, targetDir, { dereference: true, recursive: true, filter: (src) => { const relativePath = path.relative(packageDir, src); return relativePath === '' || !ignores.ignores(relativePath); }, }); const pkg = await fs.readJson(path.join(packageDir, 'package.json')); const dependencies = (pkg.dependencies ?? {}) as {[key: string]: string}; let unresolvedCount = Object.keys(dependencies).length; let curPackageDir = packageDir; let curTargetDir = targetDir; while (unresolvedCount > 0) { const curPackageModulesDir = path.join(curPackageDir, 'node_modules'); if (await fs.pathExists(curPackageModulesDir)) { for (const moduleName of Object.keys(dependencies)) { const curModulePath = path.join( curPackageModulesDir, ...moduleName.split('/'), ); const targetModulePath = path.join( curTargetDir, 'node_modules', ...moduleName.split('/'), ); if (await fs.pathExists(curModulePath)) { await copyPackageWithDependenciesRecursive( curModulePath, targetModulePath, rootTargetDir, ); delete dependencies[moduleName]; unresolvedCount--; } } } const parentPackageDir = getParentPackageDir(curPackageDir); if ( !parentPackageDir || parentPackageDir === '' || parentPackageDir === curPackageDir ) { break; } curPackageDir = parentPackageDir; curTargetDir = getParentPackageDir(curTargetDir); if (!curTargetDir || curTargetDir.length < rootTargetDir.length) { curTargetDir = rootTargetDir; } } if (unresolvedCount > 0) { for (const unresolvedDependency of Object.keys(dependencies)) { console.error(`Cannot resolve ${unresolvedDependency} in ${packageDir}`); } process.exit(1); } } function getParentPackageDir(packageDir: string) { packageDir = path.dirname(packageDir); while ( path.basename(packageDir) === 'node_modules' || path.basename(packageDir).startsWith('@') ) { packageDir = path.dirname(packageDir); } return packageDir; }