@titanium/module-copier
Version:
⭐ Axway Amplify module for copying dependencies to Appcelerator Titanium Resources SDK
333 lines (273 loc) • 12.3 kB
JavaScript
/* eslint-disable max-depth */
const copier = {};
module.exports = copier;
copier.nativeModulePaths = [];
copier.widgetManifests = [];
copier.widgetDirectories = [];
copier.turboDirectories = [];
copier.nativeModulePlatformPaths = [];
copier.package_registry = [];
copier.excludedDirectories = [ `.git`, `.svn` ];
const fs = require(`fs-extra`);
const path = require(`path`);
const _ = require(`lodash`);
const logger = console;
const NODE_MODULES = `node_modules`;
const THE_ROOT_MODULE = `__THE_ROOT_MODULE__`;
/**
* @description Copy all dependencies to target directory.
* @param {string} projectPath - Absolute filepath for project root directory.
* @param {string} targetPath - Absolute filepath for target directory to copy node_modules into.
* @param {object} [options] - Options object.
* @param {boolean} [options.includeOptional=true] - Whether to include optional dependencies when gathering.
* @returns {void} A Promise that resolves on completion.
*/
copier.executeSync = ({ projectPath, targetPath, includeOptional = false, includePeers = false }) => {
if (projectPath === null || projectPath === undefined) {
throw new Error(`projectPath must be defined.`);
}
if (targetPath === null || targetPath === undefined) {
throw new Error(`targetPath must be defined.`);
}
// resolve path names for file copying
projectPath = path.resolve(projectPath);
targetPath = path.resolve(targetPath);
// recursively gather the full set of dependencies/directories we need to copy
const root = new Dependency({ name: THE_ROOT_MODULE, directory: projectPath, root: projectPath });
const directoriesToBeCopied = root.getDirectoriesToCopy(includeOptional, includePeers);
const dirSet = new Set(directoriesToBeCopied); // de-duplicate
// back to Array so we can #map()
const deDuplicated = Array.from(dirSet);
// logger.debug(`🦠 deDuplicated: ${JSON.stringify(deDuplicated, null, 2)}`);
// logger.debug(`🦠 copier.turboDirectories: ${JSON.stringify(copier.turboDirectories, null, 2)}`);
// Then copy them over
deDuplicated.map(directory => {
let relativePath = directory.substring(projectPath.length);
let overwrite = true;
for (const dir of copier.turboDirectories) {
if (directory.startsWith(dir) && directory.startsWith(path.join(dir, `node_modules`))) {
relativePath = directory.substring(dir.length);
overwrite = false;
break;
}
}
// const relativePath = directory.substring(projectPath.length);
// logger.debug(`🦠 relativePath: ${JSON.stringify(relativePath, null, 2)}`);
const destPath = path.join(targetPath, relativePath);
// logger.debug(`🦠 destPath: ${JSON.stringify(destPath, null, 2)}`);
return fs.copySync(directory, destPath, {
overwrite,
dereference: true,
// Make sure we are not copying unwanted dependencies or directories marked for skipping
// filter: src =>
// !src.endsWith(NODE_MODULES)
// && copier.nativeModulePlatformPaths.every(item => !src.startsWith(item))
// && copier.excludedDirectories.every(item => !src.endsWith(item))
// && copier.widgetManifests.every(item => !src.startsWith(item.dir)),
filter: src => {
return !src.endsWith(NODE_MODULES)
&& copier.nativeModulePlatformPaths.every(item => !src.startsWith(item))
&& copier.excludedDirectories.every(item => !src.endsWith(item))
&& copier.widgetManifests.every(item => !src.startsWith(item.dir))
&& copier.turboDirectories.every(dir => {
if (src !== dir && src.startsWith(dir)) {
return src.startsWith(path.join(dir, `turbo`))
|| src.startsWith(path.join(dir, `node_modules`))
|| src === path.join(dir, `package.json`);
} else {
return true;
}
});
},
});
});
// console.debug(`this.widgetManifests: ${JSON.stringify(copier.widgetManifests, null, 2)}`);
copier.package_registry = _.sortBy(copier.package_registry, [ `name`, `alias` ]);
fs.writeJsonSync(path.join(projectPath, `build`, `widgets.json`), copier.widgetManifests, { spaces: `\t` });
fs.writeJsonSync(path.join(targetPath, `__package_registry.json`), copier.package_registry, { spaces: `\t` });
};
const findMain = ({ directory, main, root }) => {
if (! main) {
return;
}
root = root || directory;
let main_file;
let result;
// logger.debug(`🦠 directory: ${JSON.stringify(directory, null, 2)}`);
// logger.debug(`🦠 main: ${JSON.stringify(main, null, 2)}`);
// logger.debug(`🦠 root: ${JSON.stringify(root, null, 2)}`);
main_file = path.join(directory, main);
// logger.debug(`🦠 main_file: ${JSON.stringify(main_file, null, 2)}`);
if ((main_file = path.join(directory, main)) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
} else if ((main_file = path.join(directory, `${main}.js`)) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
} else if ((main_file = path.join(directory, `${main}.json`)) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
} else if ((main_file = path.join(directory, main, `index.js`)) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
} else if (main_file = path.join(directory, main, `index.json`) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
} else if ((main_file = path.join(directory, `index.js`)) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
} else if ((main_file = path.join(directory, `index.json`)) && fs.existsSync(main_file) && fs.lstatSync(main_file).isFile()) {
result = main_file.substring(root.length);
}
// logger.debug(`🦠 result: ${JSON.stringify(result, null, 2)}`);
return result;
};
class Dependency {
constructor({ parent, name, directory, root }) {
this.name = name;
this.parent = parent;
this.directory = directory;
this.root = root;
}
/**
* Get directories that need to be copied to target.
* @param {boolean} [includeOptional=true] - Include optional dependencies?
* @param {boolean} [includePeers=true] - Include peer dependencies?
* @returns {Promise<string[]>} Full set of directories to copy.
*/
getDirectoriesToCopy({ includeOptional = false, includePeers = false, parentRoot }) {
// const childrenNames = this.gatherChildren({ includeOptional, includePeers });
const results = this.gatherChildren({ includeOptional, includePeers, parentRoot });
// if (!childrenNames) {
// return []; // Ignore this directory
// }
// if (results.dependencies.length === 0) {
// if (this.name !== THE_ROOT_MODULE) {
// return [ this.directory ]; // just need our own directory!
// } else {
// return [];
// }
// }
const children = results.dependencies.map(name => this.resolve(name));
const allDirs = children.map(child => child.getDirectoriesToCopy({ includeOptional, includePeers, parentRoot: results.parentRoot }));
// flatten allDirs down to single Array
const flattened = allDirs.reduce((acc, val) => acc.concat(val), []); // TODO: replace with flat() call once Node 11+
if (results.includeParent && (this.name !== THE_ROOT_MODULE)) {
flattened.push(this.directory); // We need to include our own directory
}
return flattened;
}
/**
* Gather a list of all child dependencies.
* @param {boolean} [includeOptional] - Include optional dependencies?
* @param {boolean} [includePeers] - Include peer dependencies?
* @returns {Promise<string[]>} Set of dependency names.
*/
gatherChildren({ includeOptional = false, includePeers = false, parentRoot = this.root }) {
const packageJson = fs.readJsonSync(path.join(this.directory, `package.json`));
const ignore = _.get(packageJson, `titanium.ignore`, false);
const result = {
parentRoot,
includeParent: !ignore,
dependencies: [],
};
if (ignore) {
return result;
}
const module_type = !_.get(packageJson, `titanium.type`, `package`);
const titaniumDependencies = _.get(packageJson, `titanium.dependencies`);
logger.debug(`🦠 package: ${JSON.stringify(this.directory, null, 2)}`);
if (!this.directory.startsWith(parentRoot)) {
parentRoot = this.root;
}
// logger.debug(`🦠 parentRoot: ${JSON.stringify(parentRoot, null, 2)}`);
if (result.includeParent && (this.name !== THE_ROOT_MODULE)) {
if (!_.find(copier.package_registry, { name: packageJson.name })) {
const main = findMain({ directory: this.directory, main: packageJson.main, root: parentRoot });
copier.package_registry.push({
name: packageJson.name,
version: packageJson.version,
// directory: this.directory,
directory: this.directory.substring(parentRoot.length),
main,
});
}
}
const aliases = _.get(packageJson, `titanium.aliases`);
if (_.isObject(aliases)) {
for (const alias in aliases) {
let main = aliases[alias];
if (!main.startsWith(`/`)) {
// main = path.join(this.directory, main).substring(this.root.length);
main = findMain({ directory: this.directory, main, root: parentRoot });
}
copier.package_registry.push({
alias,
version: packageJson.version,
// directory: this.directory,
directory: this.directory.substring(parentRoot.length),
main,
});
}
}
const dependencies = [];
if (titaniumDependencies) {
dependencies.push(...Object.keys(packageJson[titaniumDependencies] || {}));
result.parentRoot = this.directory;
} else {
dependencies.push(...Object.keys(packageJson.dependencies || {}));
// include optional dependencies too?
if (includeOptional && packageJson.optionalDependencies) {
dependencies.push(...Object.keys(packageJson.optionalDependencies));
}
if (includePeers && packageJson.peerDependencies) {
dependencies.push(...Object.keys(packageJson.peerDependencies));
}
}
if (packageJson.titanium) {
// logger.debug(`🦠 packageJson.titanium.type: ${JSON.stringify(packageJson.titanium.type, null, 2)}`);
if (packageJson.titanium.type === `turbo`) {
copier.turboDirectories.push(this.directory);
} else if (packageJson.titanium.type === `native-module`) {
copier.nativeModulePaths.push(this.directory);
// Just add ios and android if type: native-module
copier.nativeModulePlatformPaths.push(path.join(this.directory, `ios`));
copier.nativeModulePlatformPaths.push(path.join(this.directory, `android`));
} else if (packageJson.titanium.type === `widget`) {
const widgetDir = path.join(this.directory, packageJson.titanium.widgetDir || `.`);
copier.widgetDirectories.push(widgetDir);
const widgetManifest = {
dir: widgetDir,
manifest: {
id: packageJson.titanium.widgetId || packageJson.id,
platforms: packageJson.titanium.platforms || `ios,android`,
},
};
copier.widgetManifests.push(widgetManifest);
}
}
result.dependencies = dependencies;
// logger.debug(`🦠 copier.package_registry: ${JSON.stringify(copier.package_registry, null, 2)}`);
// logger.debug(`🦠 result: ${JSON.stringify(result, null, 2)}`);
return result;
}
/**
* Attempts to resolve a given module by id to the correct.
* @param {string} subModule - Id of a module that is it's dependency.
* @returns {Promise<Dependency>} The resolved dependency.
*/
resolve(subModule) {
try {
// First try underneath the current module
const source_directory = path.join(this.directory, NODE_MODULES, subModule);
// const target_directory = path.join(this.target, NODE_MODULES, subModule);
const packageJsonExists = fs.existsSync(path.join(source_directory, `package.json`));
if (packageJsonExists) {
return new Dependency({ parent: this, name: subModule, directory: source_directory, root: this.root });
}
} catch (err) {
// this is the root and we still didn't find it, fail!
if (this.parent === null) {
throw err;
}
}
if (_.isNil(this.parent)) {
throw new Error(`Could not find dependency: ${subModule}`);
}
return this.parent.resolve(subModule); // Try the parent (recursively)
}
}