/// /// import path = require('path'); import fs = require('fs'); import _ = require('lodash'); import utils = require('./utils'); import cache = require('./cacheUtils'); var Promise = require('es6-promise').Promise; export var grunt: IGrunt = require('grunt'); export interface ICompileResult { code: number; output: string; fileCount?: number; } /////////////////////////// // Helper /////////////////////////// function executeNode(args: string[]): Promise { return new Promise((resolve, reject) => { grunt.util.spawn({ cmd: process.execPath, args: args }, (error, result, code) => { var ret: ICompileResult = { code: code, // New TypeScript compiler uses stdout for user code errors. Old one used stderr. output: result.stdout || result.stderr }; resolve(ret); }); }); } ///////////////////////////////////////////////////////////////// // Fast Compilation ///////////////////////////////////////////////////////////////// // Map to store if the cache was cleared after the gruntfile was parsed var cacheClearedOnce: { [targetName: string]: boolean } = {}; function getChangedFiles(files, targetName: string) { files = cache.getNewFilesForTarget(files, targetName); _.forEach(files, (file) => { grunt.log.writeln(('### Fast Compile >>' + file).cyan); }); return files; } function resetChangedFiles(files, targetName: string) { cache.compileSuccessfull(files, targetName); } function clearCache(targetName: string) { cache.clearCache(targetName); cacheClearedOnce[targetName] = true; } ///////////////////////////////////////////////////////////////////// // tsc handling //////////////////////////////////////////////////////////////////// function resolveTypeScriptBinPath(): string { var ownRoot = path.resolve(path.dirname((module).filename), '../..'); var userRoot = path.resolve(ownRoot, '..', '..'); var binSub = path.join('node_modules', 'typescript', 'bin'); if (fs.existsSync(path.join(userRoot, binSub))) { // Using project override return path.join(userRoot, binSub); } return path.join(ownRoot, binSub); } function getTsc(binPath: string): string { var pkg = JSON.parse(fs.readFileSync(path.resolve(binPath, '..', 'package.json')).toString()); grunt.log.writeln('Using tsc v' + pkg.version); return path.join(binPath, 'tsc'); } export function compileAllFiles(targetFiles: string[], target: ITargetOptions, task: ITaskOptions, targetName: string, outFile: string): Promise { // Make a local copy so we can modify files without having external side effects var files = _.map(targetFiles, (file) => file); var newFiles: string[] = files; if (task.fast === 'watch') { // if we only do fast compile if target is watched // if this is the first time its running after this file was loaded if (cacheClearedOnce[grunt.task.current.target] === undefined) { // Then clear the cache for this target clearCache(targetName); } } if (task.fast !== 'never') { if (target.out) { grunt.log.writeln('Fast compile will not work when --out is specified. Ignoring fast compilation'.cyan); } else { newFiles = getChangedFiles(files, targetName); if (newFiles.length !== 0) { files = newFiles; // If outDir is specified but no baseDir is specified we need to determine one if (target.outDir && !target.baseDir) { target.baseDir = utils.findCommonPath(files, '/'); } } else { grunt.log.writeln('No file changes were detected. Skipping Compile'.green); return new Promise((resolve) => { var ret: ICompileResult = { code: 0, fileCount: 0, output: 'No files compiled as no change detected' }; resolve(ret); }); } } } // Transform files as needed. Currently all of this logic in is one module // transformers.transformFiles(newFiles, targetFiles, target, task); // If baseDir is specified create a temp tsc file to make sure that `--outDir` works fine // see https://github.com/grunt-ts/grunt-ts/issues/77 var baseDirFile: string = '.baseDir.ts'; var baseDirFilePath: string; if (target.outDir && target.baseDir && files.length > 0) { baseDirFilePath = path.join(target.baseDir, baseDirFile); if (!fs.existsSync(baseDirFilePath)) { grunt.file.write(baseDirFilePath, '// Ignore this file. See https://github.com/grunt-ts/grunt-ts/issues/77'); } files.push(baseDirFilePath); } // If reference and out are both specified. // Then only compile the updated reference file as that contains the correct order if (target.reference && target.out) { var referenceFile = path.resolve(target.reference); files = [referenceFile]; } // Quote the files to compile. Needed for command line parsing by tsc files = _.map(files, (item) => `"${path.resolve(item)}"`); if (outFile) { outFile = `"${path.resolve(outFile)}"`; } var args: string[] = files.slice(0); // boolean options if (task.sourceMap) { args.push('--sourcemap'); } if (task.declaration) { args.push('--declaration'); } if (task.removeComments) { args.push('--removeComments'); } if (task.noImplicitAny) { args.push('--noImplicitAny'); } if (task.noResolve) { args.push('--noResolve'); } if (task.noEmitOnError) { args.push('--noEmitOnError'); } if (task.preserveConstEnums) { args.push('--preserveConstEnums'); } if (task.suppressImplicitAnyIndexErrors) { args.push('--suppressImplicitAnyIndexErrors'); } // string options args.push('--target', task.target.toUpperCase()); // check the module compile option if (task.module) { var moduleOptionString: string = task.module.toLowerCase(); if (moduleOptionString === 'amd' || moduleOptionString === 'commonjs') { args.push('--module', moduleOptionString); } else { console.warn('WARNING: Option "module" does only support "amd" | "commonjs"'.magenta); } } var theOutDir : string = null; if (target.outDir) { if (target.out) { console.warn('WARNING: Option "out" and "outDir" should not be used together'.magenta); } theOutDir = `"${path.resolve(target.outDir)}"`; args.push('--outDir', theOutDir); } // Target options: if (outFile) { if (utils.isJavaScriptFile(outFile)) { args.push('--out', outFile); } else { if (!theOutDir) { args.push('--outDir', outFile); } } } else if (target.out) { args.push('--out', target.out); } if (target.dest && (!target.out) && (!target.outDir)) { if (utils.isJavaScriptFile(target.dest)) { args.push('--out', target.dest); } else { if (target.dest === 'src') { console.warn(('WARNING: Destination for target "' + targetName + '" is "src", which is the default. If you have' + ' forgotten to specify a "dest" parameter, please add it. If this is correct, you may wish' + ' to change the "dest" parameter to "src/" or just ignore this warning.').magenta); } if (Array.isArray(target.dest)) { if ((target.dest).length === 0) { // ignore it and do nothing. } else if ((target.dest).length > 0) { console.warn((('WARNING: "dest" for target "' + targetName + '" is an array. This is not supported by the' + ' TypeScript compiler or grunt-ts.' + (((target.dest).length > 1) ? ' Only the first "dest" will be used. The' + ' remaining items will be truncated.' : ''))).magenta); args.push('--outDir', (target.dest)[0]); } } else { args.push('--outDir', target.dest); } } } if (task.sourceRoot) { args.push('--sourceRoot', task.sourceRoot); } if (task.mapRoot) { args.push('--mapRoot', task.mapRoot); } // Locate a compiler var tsc: string; if (task.compiler) { // Custom compiler (task.compiler) grunt.log.writeln('Using the custom compiler : ' + task.compiler); tsc = task.compiler; } else { // the bundled OR npm module based compiler tsc = getTsc(resolveTypeScriptBinPath()); } // To debug the tsc command if (task.verbose) { console.log(args.join(' ').yellow); } else { grunt.log.verbose.writeln(args.join(' ').yellow); } // Create a temp last command file and use that to guide tsc. // Reason: passing all the files on the command line causes TSC to go in an infinite loop. var tempfilename = utils.getTempFile('tscommand'); if (!tempfilename) { throw (new Error('cannot create temp file')); } fs.writeFileSync(tempfilename, args.join(' ')); // Execute command return executeNode([tsc, '@' + tempfilename]).then((result: ICompileResult) => { if (task.fast !== 'never' && result.code === 0) { resetChangedFiles(newFiles, targetName); } result.fileCount = files.length; fs.unlinkSync(tempfilename); grunt.log.writeln(result.output); return Promise.cast(result); }, (err) => { fs.unlinkSync(tempfilename); throw err; }); }