UNPKG

@h4ad/node-modules-packer

Version:

<h1 align="center"> 🚀 Node Modules Packer </h1>

353 lines • 16.4 kB
"use strict"; //#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