import { createError } from '@cirrusct/error'; import { mvReplaceDir, rm } from '@cirrusct/fs'; import { Logger } from '@cirrusct/logging'; import { shellExec } from '@cirrusct/shell-exec'; import { exec } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import * as tmp from 'tmp'; import { CompilerOptions } from 'typescript'; import { TsConfig } from './types'; export const shellTsc = async ( tsConfig: TsConfig, rootPath: string, outDir?: string, logger?: Logger, preserveTmpFiles: boolean = false ) => { if (!tsConfig.compilerOptions) { throw createError('tsc: Cannot compile. Invalid tsConfig'); } outDir = outDir || tsConfig.compilerOptions.outDir; const debug = (o: object, msg: string) => { if (logger) { logger.debug(o, msg); } }; debug({ tsConfig }, 'starting tsc'); if (!outDir) { throw createError(`Argument error in tsc. No outDir specified`); } let tmpOutDir: tmp.SynchrounousResult; try { tmpOutDir = tmp.dirSync(); } catch (e) { throw createError('[tsc] Could not create tmp output directory'); } let tmpProjectFilePath: string; try { // create a tmp file in the root with the tsconfig.json tmpProjectFilePath = tsConfigToTmpFilePath(tsConfig, rootPath); // build the tsc command const tscShellCmd = `tsc -p ${tmpProjectFilePath} --outDir ${tmpOutDir.name}`; debug({ tscShellCmd }, 'Shelling out to tsc'); // shell out to execute tsc const result = await shellExec(tscShellCmd); debug({ result: result.stdout }, `tsc returned code: ${result.exitCode} `); if (result.exitCode === 0) { const resolvedOutDir = path.resolve(outDir); // move output from tmp dir to final destination debug({ tmpDir: tmpOutDir.name, dest: resolvedOutDir }, 'moving output files from tmp to dest'); await mvReplaceDir(tmpOutDir.name, resolvedOutDir); debug({}, `tsc completed sucessfully`); } else { debug({}, `tsc failed`); throw createError( `tsc shell exec failed with error code ${result.exitCode}`, undefined, 'ShellExecFailure', result.stderr ); } } catch (e) { debug({}, `tsc failed`); throw createError('tsc failed', e); } finally { if (tmpProjectFilePath && !preserveTmpFiles) { try { // delete tmp tsconfig.json debug({}, `deleting tmp tsconfig file`); rm(tmpProjectFilePath); } catch {} } else { debug({}, `NOT deleting tmp tsconfig file`); } } }; export const getTscVersion = async (cwd = process.cwd): Promise => { return new Promise((resolve, reject) => { exec('tsc --version', { cwd: __dirname }, (err, tscversionstdout, tscversionstderr) => { if (err) { return reject(`tsc --version failed: ${err}`); } return resolve( tscversionstdout .toLowerCase() .replace('version', '') .trim() ); }); }); }; const tsConfigToTmpFilePath = (tsConfig: TsConfig, rootPath: string) => { const txt = JSON.stringify(tsConfig); const tmpFileName = tmp.tmpNameSync({ dir: rootPath }); fs.writeFileSync(tmpFileName, txt); return tmpFileName; };