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;
}