UNPKG

@loopback/build

Version:

A set of common scripts and default configurations to build LoopBack 4 or other TypeScript modules

286 lines (248 loc) 8.08 kB
#!/usr/bin/env node // Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved. // Node module: @loopback/build // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT /* ======== Usage: node ./bin/compile-package <target> Where <target> is one of es2015, es2017 or es2018. ======== */ 'use strict'; const debug = require('debug')('loopback:build'); const utils = require('./utils'); const path = require('path'); const fs = require('fs'); const {globSync} = require('glob'); const fse = require('fs-extra'); const {buildOpts: buildOptions} = require('typescript'); function run(argv, options) { if (options === true) { options = {dryRun: true}; } else { options = options || {}; } const packageDir = utils.getPackageDir(); const runnerName = argv[1]; const compilerOpts = argv.slice(2); const runnerIsLbttsc = runnerName.includes('lb-ttsc'); const isUseTtscSet = utils.isOptionSet(compilerOpts, '--use-ttypescript'); const useTtsc = runnerIsLbttsc || isUseTtscSet; const isTargetSet = utils.isOptionSet(compilerOpts, '--target'); const isOutDirSet = utils.isOptionSet(compilerOpts, '--outDir'); const isProjectSet = utils.isOptionSet(compilerOpts, '-p', '--project'); const isCopyResourcesSet = utils.isOptionSet( compilerOpts, '--copy-resources', ); let TSC_CLI = 'typescript/lib/tsc'; if (useTtsc) { try { require.resolve('ttypescript'); TSC_CLI = 'ttypescript/lib/tsc'; } catch (e) { if (isUseTtscSet) { console.error( 'Error using the --use-ttypescript option - ttypescript is not installed', ); } else { console.error('Error using lb-ttsc - ttypescript is not installed'); } process.exit(1); } } debug(`Using ${TSC_CLI} to compile package`); // --copy-resources and --use-ttypescript are not a TS Compiler options, // so we remove them from the list of compiler options to avoid compiler // errors. if (isCopyResourcesSet) { compilerOpts.splice(compilerOpts.indexOf('--copy-resources'), 1); } if (isUseTtscSet) { compilerOpts.splice(compilerOpts.indexOf('--use-ttypescript'), 1); } let target; if (isTargetSet) { const targetIx = compilerOpts.indexOf('--target'); target = compilerOpts[targetIx + 1]; compilerOpts.splice(targetIx, 2); } let outDir; if (isOutDirSet) { const outDirIx = compilerOpts.indexOf('--outDir'); outDir = path.resolve(process.cwd(), compilerOpts[outDirIx + 1]); compilerOpts.splice(outDirIx, 2); } let tsConfigFile; let rootDir; if (!isProjectSet) { rootDir = utils.getRootDir(); tsConfigFile = utils.getConfigFile('tsconfig.build.json', 'tsconfig.json'); if (tsConfigFile === path.join(rootDir, 'config/tsconfig.build.json')) { // No local tsconfig.build.json or tsconfig.json found let baseConfigFile = path.join(rootDir, 'config/tsconfig.common.json'); baseConfigFile = path.relative(packageDir, baseConfigFile); if (baseConfigFile.indexOf('..' + path.sep) !== 0) { // tsconfig only supports relative or rooted path baseConfigFile = '.' + path.sep + baseConfigFile; } baseConfigFile = baseConfigFile.replace(/\\/g, '/'); // Create tsconfig.json under the package as it's required to parse // include/exclude correctly tsConfigFile = path.join(packageDir, 'tsconfig.json'); fs.writeFileSync( tsConfigFile, JSON.stringify( { extends: baseConfigFile, compilerOptions: { outDir: 'dist', rootDir: 'src', }, include: ['src'], }, null, ' ', ), ); } } const args = []; let cwd = process.env.LERNA_ROOT_PATH || process.cwd(); if (tsConfigFile && fs.existsSync(tsConfigFile)) { const tsconfig = require(tsConfigFile); if (tsconfig.references) { args.unshift('-b'); // Reset the cwd for a composite project cwd = process.cwd(); } else { // Make the config file relative the current directory args.push('-p', path.relative(cwd, tsConfigFile)); } } if (outDir) { args.push('--outDir', path.relative(cwd, outDir)); } if (target) { args.push('--target', target); } if (isCopyResourcesSet) { // Since outDir is set, ts files are compiled into that directory. // If copy-resources flag is passed, copy resources (non-ts files) // to the same outDir as well. copyResources(rootDir, packageDir, tsConfigFile, outDir, options); } if (target) { args.push('--target', target); } args.push(...compilerOpts); const validArgs = validArgsForBuild(args); return utils.runCLI(TSC_CLI, validArgs, {cwd, ...options}); } /** * `tsc -b` only accepts valid arguments. `npm run build -- --<other-arg>` may * pass in extra arguments. We need to remove such arguments. * @param {string[]} args An array of arguments */ function validArgsForBuild(args) { const validBooleanOptions = []; const validValueOptions = []; // See https://github.com/microsoft/TypeScript/blob/v3.8.3/src/compiler/commandLineParser.ts#L122 buildOptions.forEach(opt => { /** * name: "help", * shortName: "h", * type: "boolean", */ const options = opt.type === 'boolean' ? validBooleanOptions : validValueOptions; options.push(`--${opt.name}`); if (opt.shortName) { validBooleanOptions.push(`-${opt.shortName}`); } }); let validArgs = args; if (args.includes('-b') || args.includes('--build')) { validArgs = filterArgs(args, (arg, next) => { if (validBooleanOptions.includes(arg)) { return next === 'false' || next === 'true' ? 2 : 1; } if (validValueOptions.includes(arg)) return 2; return 0; }); // `-b` has to be the first argument validArgs.unshift('-b'); } debug('Valid args for tsc -b', validArgs); return validArgs; } /** * Filter arguments by name from the args * @param {string[]} args - Array of args * @param {function} filter - (arg: string) => 0, 1, 2 */ function filterArgs(args, filter) { const validArgs = []; let i = 0; while (i < args.length) { const length = filter(args[i], args[i + 1]); if (length === 0) { i++; } else if (length === 1) { validArgs.push(args[i]); i++; } else if (length === 2) { validArgs.push(args[i], args[i + 1]); i += 2; } } return validArgs; } module.exports = run; if (require.main === module) run(process.argv); function copyResources(rootDir, packageDir, tsConfigFile, outDir, options) { if (!rootDir) { console.warn('Ignoring --copy-resources option - rootDir was not set.'); return; } if (!tsConfigFile) { console.warn( 'Ignoring --copy-resources option - no tsconfig file was found.', ); return; } const tsConfig = require(tsConfigFile); if (!outDir) { outDir = tsConfig.compilerOptions && tsConfig.compilerOptions.outDir; if (!outDir) { console.warn( 'Ignoring --copy-resources option - outDir was not configured.', ); return; } } const dirs = tsConfig.include ? tsConfig.include.join('|') : ['src', 'test'].join('|'); const compilerRootDir = (tsConfig.compilerOptions && tsConfig.compilerOptions.rootDir) || ''; const pattern = `@(${dirs})/**/!(*.ts)`; const files = globSync(pattern, {root: packageDir, nodir: true}); for (const file of files) { /** * Trim path that matches tsConfig.compilerOptions.rootDir */ let targetFile = file; if (compilerRootDir && file.startsWith(compilerRootDir + '/')) { targetFile = file.substring(compilerRootDir.length + 1); } const copyFrom = path.join(packageDir, file); const copyTo = path.join(outDir, targetFile); debug(' copy %j to %j', copyFrom, copyTo); if (!options.dryRun) { fse.copySync(copyFrom, copyTo); } } }