@h4ad/node-modules-packer
Version:
<h1 align="center"> 🚀 Node Modules Packer </h1>
353 lines • 16.4 kB
JavaScript
;
//#region Imports
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const path_1 = require("path");
const dependency_extractor_1 = require("@h4ad/dependency-extractor");
const core_1 = require("@oclif/core");
const esbuild_1 = tslib_1.__importDefault(require("esbuild"));
const rimraf_1 = tslib_1.__importDefault(require("rimraf"));
const custom_command_1 = tslib_1.__importDefault(require("../../common/custom-command"));
const custom_error_1 = tslib_1.__importDefault(require("../../common/custom-error"));
const extensions_1 = require("../../common/extensions");
const fs_1 = require("../../common/fs");
const zip_1 = require("../../common/zip");
//#endregion
class Run extends custom_command_1.default {
//#endregion
//#region Static Methods
static async headless(options, loadOptions) {
const args = [options.dir];
const pushFlagWithArguments = (flag, options) => {
for (const option of options) {
args.push(flag);
args.push(option);
}
};
const pushFlagBoolean = (flag, cond) => {
const prefix = cond ? '--' : '--no-';
args.push(`${prefix}${flag}`);
};
if (Array.isArray(options.include))
pushFlagWithArguments('-i', options.include);
if (Array.isArray(options.ignoreFileExt))
pushFlagWithArguments('-e', options.ignoreFileExt);
if (options.disableDefaultIgnoreFileExt !== undefined) {
pushFlagBoolean('disable-default-ignore-file-ext', options.disableDefaultIgnoreFileExt);
}
if (Array.isArray(options.includeNodePath))
pushFlagWithArguments('--include-node-path', options.includeNodePath);
if (Array.isArray(options.ignoreNodePath))
pushFlagWithArguments('--ignore-node-path', options.ignoreNodePath);
if (options.prod !== undefined)
pushFlagBoolean('prod', options.prod);
if (options.dev !== undefined)
pushFlagBoolean('dev', options.dev);
if (options.peer !== undefined)
pushFlagBoolean('peer', options.peer);
if (options.optional !== undefined)
pushFlagBoolean('optional', options.optional);
if (options.outputPath !== undefined)
args.push('--output-path', options.outputPath);
if (options.outputFile !== undefined)
args.push('--output-file', options.outputFile);
if (options.minify !== undefined)
pushFlagBoolean('minify', options.minify);
if (options.minifyKeepNames !== undefined)
pushFlagBoolean('minify-keep-names', options.minifyKeepNames);
return await this.run(args, loadOptions);
}
//#endregion
//#region Public Methods
async run() {
const { args, flags } = await this.parse(Run);
const dir = args['dir'];
const outputPath = flags['output-path'];
const outputFile = flags['output-file'];
this.checkForNodeModules(flags, dir);
this.checkForNoOutputFile(flags, dir, outputPath, outputFile);
const ignoredFileExtensions = this.getIgnoredFileExtensions(flags);
const ignoredNodePaths = this.getIgnoredNodePaths(flags);
const includedNodePaths = this.getIncludedNodePaths(flags);
const selectedDependencies = this.getSelectedDependencies(flags, dir);
const shouldIgnoreNodeFile = this.getShouldIgnoreNodeFileCallback(dir, ignoredFileExtensions, ignoredNodePaths, includedNodePaths, selectedDependencies);
const zipArtifacts = this.getZipArtifacts(dir, flags, shouldIgnoreNodeFile);
const outputFilePath = (0, path_1.resolve)(dir, outputPath, outputFile);
await this.zipDirectory(flags, dir, zipArtifacts, outputFilePath);
const size = (0, fs_1.safeStatSync)(outputFilePath).size;
return { size, file: outputFile, path: outputPath };
}
//#endregion
//#region Protected Methods
checkForNodeModules(flags, dir) {
this.logMessage(flags, 'log', 'Checking node folder');
const nodeModulesFolderPath = (0, path_1.join)(dir, 'node_modules');
if ((0, fs_1.safeExistsSync)(nodeModulesFolderPath)) {
this.logMessage(flags, 'log', 'Checking node folder... found');
return;
}
throw new custom_error_1.default(`Invalid Node Modules: folder ${nodeModulesFolderPath} does not exist`, {
code: 'ERR_NODE_MODULES_NOT_FOUND',
suggestions: [`Maybe you forgot to run "npm i" on ${dir}?`],
});
}
checkForNoOutputFile(flags, dir, outputPath, outputFile) {
this.logMessage(flags, 'log', 'Removing old output file');
const resolvedOutputPath = (0, path_1.resolve)(dir, outputPath);
const outputFilePath = (0, path_1.join)(resolvedOutputPath, outputFile);
if (!(0, fs_1.safeExistsSync)(resolvedOutputPath)) {
this.logMessage(flags, 'log', `Not found folder in ${outputPath}, creating new one...`);
(0, fs_1.safeMkdirSync)(resolvedOutputPath, { recursive: true });
}
if (!(0, fs_1.safeExistsSync)(outputFilePath)) {
this.logMessage(flags, 'debug', `Not found ${outputFilePath} in output folder, it's not necessary remove.`);
this.logMessage(flags, 'log', 'Removing old output file... done');
return;
}
this.logMessage(flags, 'log', `File found at ${outputFilePath}, removing...`);
rimraf_1.default.sync(outputFilePath);
this.logMessage(flags, 'log', `File found at ${outputFilePath}, removed.`);
this.logMessage(flags, 'log', 'Removing old output file... done');
}
getSelectedDependencies(flags, dir) {
this.logMessage(flags, 'log', 'Getting selected dependencies');
const dependenciesContainer = this.getExtractedDependenciesFromLockFile(flags, dir);
const allDependencies = dependenciesContainer.getAllDependencies();
const hasFlag = (type, flag) => (type & flag) > 0;
const selectedDependencies = allDependencies.filter(dependency => {
if (flags.prod &&
hasFlag(dependency.type, dependency_extractor_1.DependencyType.PRODUCTION) &&
!hasFlag(dependency.type, dependency_extractor_1.DependencyType.PEER))
return true;
if (flags.dev && hasFlag(dependency.type, dependency_extractor_1.DependencyType.DEVELOPMENT))
return true;
if (flags.peer && hasFlag(dependency.type, dependency_extractor_1.DependencyType.PEER))
return true;
if (flags.optional && hasFlag(dependency.type, dependency_extractor_1.DependencyType.OPTIONAL))
return true;
return false;
});
this.logMessage(flags, 'log', `Total dependencies found: ${allDependencies.length}.`);
this.logMessage(flags, 'log', `Selected dependencies: ${selectedDependencies.length}.`);
return selectedDependencies;
}
getExtractedDependenciesFromLockFile(flags, dir) {
const packageLockFilePath = (0, path_1.join)(dir, 'package-lock.json');
if ((0, fs_1.safeExistsSync)(packageLockFilePath)) {
this.logMessage(flags, 'debug', `Found lock file in ${packageLockFilePath}.`);
this.logMessage(flags, 'debug', 'Reading and parsing lock file with NpmExtractor.');
return new dependency_extractor_1.NpmExtractor().parse((0, fs_1.safeReadFileSync)(packageLockFilePath).toString('utf-8'));
}
throw new custom_error_1.default(`Invalid package-lock.json: file ${packageLockFilePath} does not exist`, {
code: 'ERR_LOCK_FILE',
suggestions: [
'Currently we only support package-lock.json to detect production dependencies.',
],
});
}
getIgnoredFileExtensions(flags) {
const ignoredFileExtensions = [];
if (!flags['disable-default-ignore-file-ext'])
ignoredFileExtensions.push(...extensions_1.defaultIgnoredFileExtensions);
if (Array.isArray(flags['ignore-file-ext']))
ignoredFileExtensions.push(...flags['ignore-file-ext']);
this.logMessage(flags, 'debug', 'Using following ignored file extensions:');
this.logMessage(flags, 'debug', JSON.stringify(ignoredFileExtensions));
return ignoredFileExtensions;
}
getIgnoredNodePaths(flags) {
const ignoredNodePaths = [];
if (Array.isArray(flags['ignore-node-path']))
ignoredNodePaths.push(...flags['ignore-node-path']);
this.logMessage(flags, 'debug', 'Using following ignored node paths:');
this.logMessage(flags, 'debug', JSON.stringify(ignoredNodePaths));
return ignoredNodePaths.map(this.fixPath.bind(this));
}
getIncludedNodePaths(flags) {
const includedNodePaths = [];
if (Array.isArray(flags['include-node-path']))
includedNodePaths.push(...flags['include-node-path']);
this.logMessage(flags, 'debug', 'Using following included node paths:');
this.logMessage(flags, 'debug', JSON.stringify(includedNodePaths));
return includedNodePaths.map(this.fixPath.bind(this));
}
getShouldIgnoreNodeFileCallback(dir, ignoredFileExtensions, ignoredNodePaths, includedNodePaths, selectedDependencies) {
return filename => {
if (ignoredFileExtensions.some(ext => filename.endsWith(ext)))
return true;
const filenameNodePath = this.fixPath((0, path_1.relative)((0, path_1.resolve)(dir, 'node_modules'), filename));
if (includedNodePaths.some(path => filenameNodePath.startsWith(path)))
return false;
if (ignoredNodePaths.some(path => filenameNodePath.startsWith(path)))
return true;
const isSelectedDependency = selectedDependencies.some(dependency => filenameNodePath.startsWith(dependency.name));
return !isSelectedDependency;
};
}
getZipArtifacts(dir, flags, shouldIgnoreNodeFile) {
this.logMessage(flags, 'log', 'Getting artifacts to zip');
const isJsFileRegex = /(\.js|\.cjs|\.mjs)$/;
const keepNames = !!flags['minify-keep-names'];
const transformAsyncCode = (code) => esbuild_1.default
.transform(code, { minify: true, keepNames })
.then(result => result.code);
const transformerFunction = (filePath, metadataPath) => {
const isJsFile = isJsFileRegex.test(filePath) || isJsFileRegex.test(metadataPath);
if (!isJsFile)
return undefined;
return transformAsyncCode;
};
const transformer = flags.minify ? transformerFunction : undefined;
const artifacts = [
{
path: (0, path_1.join)(dir, 'node_modules'),
name: 'node_modules',
type: 'directory',
transformer,
shouldIgnore: shouldIgnoreNodeFile,
},
];
const includeFiles = Array.isArray(flags.include) ? flags.include : [];
for (const includeFile of includeFiles) {
const [relativePath, pathMappedTo] = includeFile.split(':');
const includeFilePath = (0, path_1.join)(dir, relativePath);
const stats = (0, fs_1.safeStatSync)(includeFilePath);
const metadataPath = pathMappedTo
? (0, path_1.resolve)('/', pathMappedTo).slice(1)
: undefined;
const type = stats.isDirectory() ? 'directory' : 'file';
artifacts.push({
path: this.fixPath(includeFilePath),
name: this.fixPath(includeFile),
metadataPath: metadataPath ? this.fixPath(metadataPath) : metadataPath,
transformer,
type,
});
}
this.logMessage(flags, 'log', `Getting artifacts to zip... ${artifacts.length} selected`);
return artifacts;
}
async zipDirectory(flags, dir, zipArtifacts, outputPath) {
this.logMessage(flags, 'log', 'Creating the output file');
if (!outputPath.endsWith('.zip')) {
throw new custom_error_1.default('Invalid output file extension.', {
code: 'ERR_OUTPUT_FILE',
suggestions: [
'You should specific an --output-file with .zip extension.',
],
});
}
const rootPath = (0, path_1.resolve)(process.cwd(), dir);
const fasterZip = new zip_1.FasterZip();
await fasterZip.run(rootPath, outputPath, zipArtifacts);
this.logMessage(flags, 'log', 'Creating the output file... created');
}
logMessage(flags, type, message, ...args) {
if (flags.quiet)
return;
this[type](message, args);
}
fixPath(path) {
return path.replace(/\\\\/g, '/').replace(/\\/g, '/');
}
}
exports.default = Run;
//#region Static Properties
Run.description = 'Pack files and node dependencies to zip file.';
Run.examples = [
'<%= config.bin %> <%= command.id %> /project/path -i dist',
];
Run.args = [
{
name: 'dir',
description: 'Project root directory',
required: false,
default: './',
},
];
Run.flags = {
include: core_1.Flags.string({
char: 'i',
description: 'Include more files during packing (eg: -i dist).',
helpValue: 'package.json',
multiple: true,
required: false,
}),
'ignore-file-ext': core_1.Flags.string({
char: 'e',
description: 'Force ignore specific file extension.',
multiple: true,
required: false,
}),
'disable-default-ignore-file-ext': core_1.Flags.boolean({
description: 'Disable including default ignored extensions that we consider as useless.',
required: false,
default: false,
allowNo: true,
}),
'include-node-path': core_1.Flags.string({
description: 'Force include folders starting with the specified path (eg --include-node-path "dev-dependency" will include node_modules/dev-dependency), but you need to MANUALLY add your sub-dependencies if dev-dependency has production dependencies.',
helpValue: 'dev-dependency',
multiple: true,
required: false,
}),
'ignore-node-path': core_1.Flags.string({
description: 'Force exclude folders starting with specified path (eg: -n "typeorm/browser" will exclude node_modules/typeorm/browser).',
helpValue: 'typeorm/browser',
multiple: true,
required: false,
}),
prod: core_1.Flags.boolean({
description: 'Include production dependencies when pack node dependencies.',
default: true,
required: false,
allowNo: true,
}),
peer: core_1.Flags.boolean({
description: 'Include peer dependencies when pack node dependencies.',
default: false,
required: false,
allowNo: true,
}),
dev: core_1.Flags.boolean({
description: 'Include development dependencies when pack node dependencies.',
default: false,
required: false,
allowNo: true,
}),
optional: core_1.Flags.boolean({
description: 'Include optional dependencies when pack node dependencies.',
default: false,
required: false,
allowNo: true,
}),
'output-path': core_1.Flags.string({
description: 'Specify output path for the zip file.',
default: './',
required: false,
}),
'output-file': core_1.Flags.string({
description: 'Specify output file name for the zip file.',
default: 'deploy.zip',
required: false,
}),
minify: core_1.Flags.boolean({
description: 'Minify each .js file with esbuild.',
default: false,
required: false,
allowNo: true,
}),
'minify-keep-names': core_1.Flags.boolean({
description: 'Keep the names during minification.',
default: false,
required: false,
allowNo: true,
}),
quiet: core_1.Flags.boolean({
char: 'q',
description: 'Run without logging.',
default: false,
required: false,
}),
};
//# sourceMappingURL=index.js.map