@rushstack/heft
Advanced tools
Comparing version 0.35.1 to 0.36.0
# Change Log - @rushstack/heft | ||
This log was last generated on Wed, 11 Aug 2021 00:07:21 GMT and should not be manually modified. | ||
This log was last generated on Wed, 11 Aug 2021 23:14:17 GMT and should not be manually modified. | ||
## 0.36.0 | ||
Wed, 11 Aug 2021 23:14:17 GMT | ||
### Minor changes | ||
- Add support to TypeScriptPlugin for composite TypeScript projects, with behavior analogous to "tsc --build". | ||
- Retired the use of the .heft/build-cache folder for persisting build state across the "heft clean" or "--clean" invocation. Incremental TypeScript compilation is now performed either by running "heft build" (without "--clean"), or using watch mode, and requires the tsconfig to manually opt in. The feature reduced performance of cold builds and introduced bugs due to stale caches that confused users. | ||
## 0.35.1 | ||
@@ -6,0 +14,0 @@ Wed, 11 Aug 2021 00:07:21 GMT |
@@ -81,7 +81,2 @@ import { AsyncParallelHook } from 'tapable'; | ||
/** | ||
* @public | ||
*/ | ||
export declare type CopyFromCacheMode = 'hardlink' | 'copy'; | ||
/** @beta */ | ||
@@ -88,0 +83,0 @@ export declare type CustomActionParameterType = string | boolean | number | ReadonlyArray<string> | undefined; |
@@ -8,5 +8,5 @@ // This file is read by tools that parse documentation comments conforming to the TSDoc standard. | ||
"packageName": "@microsoft/api-extractor", | ||
"packageVersion": "7.18.4" | ||
"packageVersion": "7.18.5" | ||
} | ||
] | ||
} |
@@ -8,3 +8,3 @@ export { IHeftPlugin } from './pluginFramework/IHeftPlugin'; | ||
export { StageHooksBase, IStageContext } from './stages/StageBase'; | ||
export { BuildStageHooks, BuildSubstageHooksBase, CompileSubstageHooks, BundleSubstageHooks, CopyFromCacheMode, IBuildStageContext, IBuildStageProperties, IBuildSubstage, IBundleSubstage, IBundleSubstageProperties, ICompileSubstage, ICompileSubstageProperties, IPostBuildSubstage, IPreCompileSubstage } from './stages/BuildStage'; | ||
export { BuildStageHooks, BuildSubstageHooksBase, CompileSubstageHooks, BundleSubstageHooks, IBuildStageContext, IBuildStageProperties, IBuildSubstage, IBundleSubstage, IBundleSubstageProperties, ICompileSubstage, ICompileSubstageProperties, IPostBuildSubstage, IPreCompileSubstage } from './stages/BuildStage'; | ||
export { ICleanStageProperties, CleanStageHooks, ICleanStageContext } from './stages/CleanStage'; | ||
@@ -11,0 +11,0 @@ export { ITestStageProperties, TestStageHooks, ITestStageContext } from './stages/TestStage'; |
@@ -7,8 +7,2 @@ import type * as TTypescript from 'typescript'; | ||
/** | ||
* TypeScript's output is placed in the \<project root\>/.heft/build-cache folder. | ||
* This is the the path to the subfolder in the build-cache folder that this emit kind | ||
* written to. | ||
*/ | ||
cacheOutFolderPath: string; | ||
/** | ||
* File extension to use instead of '.js' for emitted ECMAScript files. | ||
@@ -27,5 +21,3 @@ * For example, '.cjs' to indicate commonjs content, or '.mjs' to indicate ECMAScript modules. | ||
private static _baseEmitFiles; | ||
private static _originalOutDir; | ||
private static _redirectedOutDir; | ||
static install(ts: ExtendedTypeScript, tsconfig: TTypescript.ParsedCommandLine, moduleKindsToEmit: ICachedEmitModuleKind[], useBuildCache: boolean, changedFiles?: Set<IExtendedSourceFile>): void; | ||
static install(ts: ExtendedTypeScript, tsconfig: TTypescript.ParsedCommandLine, moduleKindsToEmit: ICachedEmitModuleKind[], changedFiles?: Set<IExtendedSourceFile>): void; | ||
static get isInstalled(): boolean; | ||
@@ -36,5 +28,4 @@ /** | ||
static wrapWriteFile(baseWriteFile: TTypescript.WriteFileCallback, jsExtensionOverride: string | undefined): TTypescript.WriteFileCallback; | ||
static getRedirectedFilePath(filePath: string): string; | ||
static uninstall(ts: ExtendedTypeScript): void; | ||
} | ||
//# sourceMappingURL=EmitFilesPatch.d.ts.map |
"use strict"; | ||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. | ||
// See LICENSE in the project root for license information. | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.EmitFilesPatch = void 0; | ||
const path = __importStar(require("path")); | ||
const node_core_library_1 = require("@rushstack/node-core-library"); | ||
class EmitFilesPatch { | ||
static install(ts, tsconfig, moduleKindsToEmit, useBuildCache, changedFiles) { | ||
static install(ts, tsconfig, moduleKindsToEmit, changedFiles) { | ||
if (EmitFilesPatch._patchedTs === ts) { | ||
@@ -63,6 +43,7 @@ // We already patched this instance of TS | ||
let defaultModuleKindResult; | ||
const diagnostics = []; | ||
let emitSkipped = false; | ||
for (const moduleKindToEmit of moduleKindsToEmit) { | ||
const compilerOptions = moduleKindToEmit.isPrimary | ||
? Object.assign({}, tsconfig.options) : Object.assign(Object.assign({}, tsconfig.options), { module: moduleKindToEmit.moduleKind, | ||
? Object.assign({}, tsconfig.options) : Object.assign(Object.assign({}, tsconfig.options), { module: moduleKindToEmit.moduleKind, outDir: moduleKindToEmit.outFolderPath, | ||
// Don't emit declarations for secondary module kinds | ||
@@ -73,18 +54,14 @@ declaration: false, declarationMap: false }); | ||
} | ||
// Redirect from "path/to/lib" --> "path/to/.heft/build-cache/lib" | ||
EmitFilesPatch._originalOutDir = | ||
compilerOptions.outDir.replace(/([\\\/]+)$/, '') + '/'; /* Ensure trailing slash */ | ||
EmitFilesPatch._redirectedOutDir = useBuildCache | ||
? moduleKindToEmit.cacheOutFolderPath | ||
: moduleKindToEmit.outFolderPath; | ||
const flavorResult = EmitFilesPatch._baseEmitFiles(resolver, Object.assign(Object.assign({}, host), { writeFile: EmitFilesPatch.wrapWriteFile(host.writeFile, moduleKindToEmit.jsExtensionOverride), getCompilerOptions: () => compilerOptions }), targetSourceFile, ts.getTransformers(compilerOptions, undefined, emitOnlyDtsFiles), emitOnlyDtsFiles, onlyBuildInfo, forceDtsEmit); | ||
emitSkipped = emitSkipped || flavorResult.emitSkipped; | ||
for (const diagnostic of flavorResult.diagnostics) { | ||
diagnostics.push(diagnostic); | ||
} | ||
if (moduleKindToEmit.moduleKind === defaultModuleKind) { | ||
defaultModuleKindResult = flavorResult; | ||
} | ||
EmitFilesPatch._originalOutDir = undefined; | ||
EmitFilesPatch._redirectedOutDir = undefined; | ||
// Should results be aggregated, in case for whatever reason the diagnostics are not the same? | ||
} | ||
return Object.assign(Object.assign({}, defaultModuleKindResult), { emitSkipped }); | ||
const mergedDiagnostics = ts.sortAndDeduplicateDiagnostics(diagnostics); | ||
return Object.assign(Object.assign({}, defaultModuleKindResult), { diagnostics: mergedDiagnostics, emitSkipped }); | ||
} | ||
@@ -108,21 +85,2 @@ }; | ||
} | ||
static getRedirectedFilePath(filePath) { | ||
if (!EmitFilesPatch.isInstalled) { | ||
throw new node_core_library_1.InternalError('EmitFilesPatch.getRedirectedFilePath() cannot be used unless the patch is installed'); | ||
} | ||
// Redirect from "path/to/lib" --> "path/to/.heft/build-cache/lib" | ||
let redirectedFilePath = filePath; | ||
if (EmitFilesPatch._redirectedOutDir !== undefined) { | ||
if ( | ||
/* This is significantly faster than Path.isUnderOrEqual */ | ||
filePath.startsWith(EmitFilesPatch._originalOutDir)) { | ||
redirectedFilePath = path.resolve(EmitFilesPatch._redirectedOutDir, path.relative(EmitFilesPatch._originalOutDir, filePath)); | ||
} | ||
else { | ||
// The compiler is writing some other output, for example: | ||
// ./.heft/build-cache/ts_a7cd263b9f06b2440c0f2b2264746621c192f2e2.json | ||
} | ||
} | ||
return redirectedFilePath; | ||
} | ||
static uninstall(ts) { | ||
@@ -143,4 +101,2 @@ if (EmitFilesPatch._patchedTs === undefined) { | ||
EmitFilesPatch._baseEmitFiles = undefined; // eslint-disable-line | ||
EmitFilesPatch._originalOutDir = undefined; | ||
EmitFilesPatch._redirectedOutDir = undefined; | ||
//# sourceMappingURL=EmitFilesPatch.js.map |
@@ -9,3 +9,6 @@ import { Terminal } from '@rushstack/node-core-library'; | ||
buildFolderPath: string; | ||
buildCacheFolderPath: string; | ||
/** | ||
* The path where the linter state will be written to. | ||
*/ | ||
buildMetadataFolderPath: string; | ||
linterConfigFilePath: string; | ||
@@ -36,3 +39,3 @@ /** | ||
protected readonly _buildFolderPath: string; | ||
protected readonly _buildCacheFolderPath: string; | ||
protected readonly _buildMetadataFolderPath: string; | ||
protected readonly _linterConfigFilePath: string; | ||
@@ -39,0 +42,0 @@ protected readonly _measurePerformance: PerformanceMeasurer; |
@@ -27,2 +27,3 @@ "use strict"; | ||
const node_core_library_1 = require("@rushstack/node-core-library"); | ||
const crypto_1 = require("crypto"); | ||
class LinterBase { | ||
@@ -34,3 +35,3 @@ constructor(linterName, options) { | ||
this._buildFolderPath = options.buildFolderPath; | ||
this._buildCacheFolderPath = options.buildCacheFolderPath; | ||
this._buildMetadataFolderPath = options.buildMetadataFolderPath; | ||
this._linterConfigFilePath = options.linterConfigFilePath; | ||
@@ -42,4 +43,14 @@ this._linterName = linterName; | ||
await this.initializeAsync(options.tsProgram); | ||
const commonDirectory = options.tsProgram.getCommonSourceDirectory(); | ||
const relativePaths = new Map(); | ||
const fileHash = crypto_1.createHash('md5'); | ||
for (const file of options.typeScriptFilenames) { | ||
// Need to use relative paths to ensure portability. | ||
const relative = node_core_library_1.Path.convertToSlashes(path.relative(commonDirectory, file)); | ||
relativePaths.set(file, relative); | ||
fileHash.update(relative); | ||
} | ||
const hashSuffix = fileHash.digest('base64').replace(/\+/g, '-').replace(/\//g, '_').slice(0, 8); | ||
const tslintConfigVersion = this.cacheVersion; | ||
const cacheFilePath = path.join(this._buildCacheFolderPath, `${this._linterName}.json`); | ||
const cacheFilePath = path.resolve(this._buildMetadataFolderPath, `_${this._linterName}-${hashSuffix}.json`); | ||
let tslintCacheData; | ||
@@ -66,3 +77,4 @@ try { | ||
const filePath = sourceFile.fileName; | ||
if (!options.typeScriptFilenames.has(filePath) || (await this.isFileExcludedAsync(filePath))) { | ||
const relative = relativePaths.get(filePath); | ||
if (relative === undefined || (await this.isFileExcludedAsync(filePath))) { | ||
continue; | ||
@@ -72,3 +84,3 @@ } | ||
const version = sourceFile.version || ''; | ||
const cachedVersion = cachedNoFailureFileVersions.get(filePath) || ''; | ||
const cachedVersion = cachedNoFailureFileVersions.get(relative) || ''; | ||
if (cachedVersion === '' || | ||
@@ -81,3 +93,3 @@ version === '' || | ||
if (failures.length === 0) { | ||
newNoFailureFileVersions.set(filePath, version); | ||
newNoFailureFileVersions.set(relative, version); | ||
} | ||
@@ -90,3 +102,3 @@ else { | ||
else { | ||
newNoFailureFileVersions.set(filePath, version); | ||
newNoFailureFileVersions.set(relative, version); | ||
} | ||
@@ -93,0 +105,0 @@ } |
@@ -5,4 +5,2 @@ import { ITerminalProvider } from '@rushstack/node-core-library'; | ||
import { PerformanceMeasurer, PerformanceMeasurerAsync } from '../../utilities/Performance'; | ||
import { Tslint } from './Tslint'; | ||
import { Eslint } from './Eslint'; | ||
import { HeftSession } from '../../pluginFramework/HeftSession'; | ||
@@ -12,2 +10,6 @@ import { ISharedTypeScriptConfiguration } from './TypeScriptPlugin'; | ||
buildFolder: string; | ||
/** | ||
* The folder to write build metadata. | ||
*/ | ||
buildMetadataFolder: string; | ||
typeScriptToolPath: string; | ||
@@ -23,6 +25,2 @@ tslintToolPath: string | undefined; | ||
/** | ||
* The path of project's build cache folder | ||
*/ | ||
buildCacheFolder: string; | ||
/** | ||
* Set this to change the maximum number of file handles that will be opened concurrently for writing. | ||
@@ -37,3 +35,3 @@ * The default is 50. | ||
private _capabilities; | ||
private _useIncrementalProgram; | ||
private _useSolutionBuilder; | ||
private _eslintEnabled; | ||
@@ -47,11 +45,17 @@ private _tslintEnabled; | ||
private _emitCompletedCallbackManager; | ||
private __tsCacheFilePath; | ||
private _tsReadJsonCache; | ||
private _cachedFileSystem; | ||
get filename(): string; | ||
private get _tsCacheFilePath(); | ||
constructor(parentGlobalTerminalProvider: ITerminalProvider, configuration: ITypeScriptBuilderConfiguration, heftSession: HeftSession, emitCallback: () => void); | ||
invokeAsync(): Promise<void>; | ||
_runWatch(ts: ExtendedTypeScript, measureTsPerformance: PerformanceMeasurer): Promise<void>; | ||
_runBuild(ts: ExtendedTypeScript, eslint: Eslint | undefined, tslint: Tslint | undefined, measureTsPerformance: PerformanceMeasurer, measureTsPerformanceAsync: PerformanceMeasurerAsync): Promise<void>; | ||
_runBuildAsync(ts: ExtendedTypeScript, measureTsPerformance: PerformanceMeasurer, measureTsPerformanceAsync: PerformanceMeasurerAsync): Promise<void>; | ||
_runSolutionBuildAsync(ts: ExtendedTypeScript, measureTsPerformance: PerformanceMeasurer, measureTsPerformanceAsync: PerformanceMeasurerAsync): Promise<void>; | ||
private _logDiagnostics; | ||
private _logEmitPerformance; | ||
private _logReadPerformance; | ||
private _initTSlintAsync; | ||
private _initESlintAsync; | ||
private _runESlintAsync; | ||
private _runTSlintAsync; | ||
private _printDiagnosticMessage; | ||
@@ -63,4 +67,7 @@ private _getAdjustedDiagnosticCategory; | ||
private _loadTsconfig; | ||
private _buildSolutionBuilderHost; | ||
private _buildIncrementalCompilerHost; | ||
private _getCachingTypeScriptSystem; | ||
private _buildWatchCompilerHost; | ||
private _buildWatchSolutionBuilderHost; | ||
private _overrideTypeScriptReadJson; | ||
@@ -67,0 +74,0 @@ private _parseModuleKind; |
@@ -48,11 +48,2 @@ "use strict"; | ||
} | ||
get _tsCacheFilePath() { | ||
if (!this.__tsCacheFilePath) { | ||
const configHash = Tslint_1.Tslint.getConfigHash(this._configuration.tsconfigPath, this._typescriptTerminal, this._cachedFileSystem); | ||
configHash.update(JSON.stringify(this._configuration.additionalModuleKindsToEmit || {})); | ||
const serializedConfigHash = configHash.digest('hex'); | ||
this.__tsCacheFilePath = path.posix.join(this._configuration.buildCacheFolder, `ts_${serializedConfigHash}.json`); | ||
} | ||
return this.__tsCacheFilePath; | ||
} | ||
async invokeAsync() { | ||
@@ -74,3 +65,4 @@ this._typescriptLogger = await this.requestScopedLoggerAsync('typescript'); | ||
this._capabilities = { | ||
incrementalProgram: false | ||
incrementalProgram: false, | ||
solutionBuilder: this._typescriptParsedVersion.major >= 3 | ||
}; | ||
@@ -81,7 +73,6 @@ if (this._typescriptParsedVersion.major > 3 || | ||
} | ||
// Disable incremental "useIncrementalProgram" in watch mode because its compiler configuration is | ||
// different, which will invalidate the incremental build cache. In order to support this, we'd need | ||
// to delete the cache when switching modes, or else maintain two separate cache folders. | ||
this._useIncrementalProgram = this._capabilities.incrementalProgram && !this._configuration.watchMode; | ||
this._configuration.buildCacheFolder = node_core_library_1.Path.convertToSlashes(this._configuration.buildCacheFolder); | ||
this._useSolutionBuilder = !!this._configuration.buildProjectReferences; | ||
if (this._useSolutionBuilder && !this._capabilities.solutionBuilder) { | ||
throw new Error(`Building project references requires TypeScript@>=3.0, but the current version is ${this._typescriptVersion}`); | ||
} | ||
this._tslintConfigFilePath = path.resolve(this._configuration.buildFolder, 'tslint.json'); | ||
@@ -132,47 +123,11 @@ this._eslintConfigFilePath = path.resolve(this._configuration.buildFolder, '.eslintrc.js'); | ||
}; | ||
let tslint = undefined; | ||
if (this._tslintEnabled) { | ||
if (!this._configuration.tslintToolPath) { | ||
throw new Error('Unable to resolve "tslint" package'); | ||
} | ||
const tslintLogger = await this.requestScopedLoggerAsync('tslint'); | ||
tslint = new Tslint_1.Tslint({ | ||
ts: ts, | ||
tslintPackagePath: this._configuration.tslintToolPath, | ||
scopedLogger: tslintLogger, | ||
buildFolderPath: this._configuration.buildFolder, | ||
buildCacheFolderPath: this._configuration.buildCacheFolder, | ||
linterConfigFilePath: this._tslintConfigFilePath, | ||
cachedFileSystem: this._cachedFileSystem, | ||
measurePerformance: measureTsPerformance | ||
}); | ||
} | ||
let eslint = undefined; | ||
if (this._eslintEnabled) { | ||
if (!this._configuration.eslintToolPath) { | ||
throw new Error('Unable to resolve "eslint" package'); | ||
} | ||
const eslintLogger = await this.requestScopedLoggerAsync('eslint'); | ||
eslint = new Eslint_1.Eslint({ | ||
ts: ts, | ||
eslintPackagePath: this._configuration.eslintToolPath, | ||
scopedLogger: eslintLogger, | ||
buildFolderPath: this._configuration.buildFolder, | ||
buildCacheFolderPath: this._configuration.buildCacheFolder, | ||
linterConfigFilePath: this._eslintConfigFilePath, | ||
measurePerformance: measureTsPerformance | ||
}); | ||
} | ||
this._typescriptTerminal.writeLine(`Using TypeScript version ${ts.version}`); | ||
if (eslint) { | ||
eslint.printVersionHeader(); | ||
} | ||
if (tslint) { | ||
tslint.printVersionHeader(); | ||
} | ||
if (this._configuration.watchMode) { | ||
await this._runWatch(ts, measureTsPerformance); | ||
} | ||
else if (this._useSolutionBuilder) { | ||
await this._runSolutionBuildAsync(ts, measureTsPerformance, measureTsPerformanceAsync); | ||
} | ||
else { | ||
await this._runBuild(ts, eslint, tslint, measureTsPerformance, measureTsPerformanceAsync); | ||
await this._runBuildAsync(ts, measureTsPerformance, measureTsPerformanceAsync); | ||
} | ||
@@ -182,8 +137,8 @@ } | ||
//#region CONFIGURE | ||
const { duration: configureDurationMs, tsconfig, compilerHost } = measureTsPerformance('Configure', () => { | ||
const { duration: configureDurationMs, tsconfig } = measureTsPerformance('Configure', () => { | ||
const _tsconfig = this._loadTsconfig(ts); | ||
const _compilerHost = this._buildWatchCompilerHost(ts, _tsconfig); | ||
this._validateTsconfig(ts, _tsconfig); | ||
EmitFilesPatch_1.EmitFilesPatch.install(ts, _tsconfig, this._moduleKindsToEmit); | ||
return { | ||
tsconfig: _tsconfig, | ||
compilerHost: _compilerHost | ||
tsconfig: _tsconfig | ||
}; | ||
@@ -193,5 +148,11 @@ }); | ||
//#endregion | ||
this._validateTsconfig(ts, tsconfig); | ||
EmitFilesPatch_1.EmitFilesPatch.install(ts, tsconfig, this._moduleKindsToEmit, /* useBuildCache */ false); | ||
ts.createWatchProgram(compilerHost); | ||
if (this._useSolutionBuilder) { | ||
const solutionHost = this._buildWatchSolutionBuilderHost(ts); | ||
const watchBuilder = ts.createSolutionBuilderWithWatch(solutionHost, [this._configuration.tsconfigPath], {}); | ||
watchBuilder.build(); | ||
} | ||
else { | ||
const compilerHost = this._buildWatchCompilerHost(ts, tsconfig); | ||
ts.createWatchProgram(compilerHost); | ||
} | ||
return new Promise(() => { | ||
@@ -201,5 +162,3 @@ /* never terminate */ | ||
} | ||
async _runBuild(ts, eslint, tslint, measureTsPerformance, measureTsPerformanceAsync) { | ||
// Ensure the cache folder exists | ||
this._cachedFileSystem.ensureFolder(this._configuration.buildCacheFolder); | ||
async _runBuildAsync(ts, measureTsPerformance, measureTsPerformanceAsync) { | ||
//#region CONFIGURE | ||
@@ -209,2 +168,3 @@ const { duration: configureDurationMs, tsconfig, compilerHost } = measureTsPerformance('Configure', () => { | ||
const _tsconfig = this._loadTsconfig(ts); | ||
this._validateTsconfig(ts, _tsconfig); | ||
const _compilerHost = this._buildIncrementalCompilerHost(ts, _tsconfig); | ||
@@ -218,3 +178,2 @@ return { | ||
//#endregion | ||
this._validateTsconfig(ts, tsconfig); | ||
//#region PROGRAM | ||
@@ -224,3 +183,3 @@ // There will be only one program here; emit will get a bit abused if we produce multiple outputs | ||
let tsProgram; | ||
if (this._useIncrementalProgram) { | ||
if (tsconfig.options.incremental) { | ||
builderProgram = ts.createIncrementalProgram({ | ||
@@ -246,5 +205,3 @@ rootNames: tsconfig.fileNames, | ||
const genericProgram = builderProgram || tsProgram; | ||
this._typescriptTerminal.writeVerboseLine(`I/O Read: ${ts.performance.getDuration('I/O Read')}ms (${ts.performance.getCount('beforeIORead')} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Parse: ${ts.performance.getDuration('Parse')}ms (${ts.performance.getCount('beforeParse')} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Program (includes Read + Parse): ${ts.performance.getDuration('Program')}ms`); | ||
this._logReadPerformance(ts); | ||
//#endregion | ||
@@ -267,29 +224,21 @@ //#region ANALYSIS | ||
//#endregion | ||
this._typescriptTerminal.writeVerboseLine(`Bind: ${ts.performance.getDuration('Bind')}ms`); | ||
this._typescriptTerminal.writeVerboseLine(`Check: ${ts.performance.getDuration('Check')}ms`); | ||
this._typescriptTerminal.writeVerboseLine(`Transform: ${ts.performance.getDuration('transformTime')}ms ` + | ||
`(${ts.performance.getCount('beforeTransform')} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Print: ${ts.performance.getDuration('printTime')}ms ` + | ||
`(${ts.performance.getCount('beforePrint')} files) (Includes Transform)`); | ||
this._typescriptTerminal.writeVerboseLine(`Emit: ${ts.performance.getDuration('Emit')}ms (Includes Print)`); | ||
this._logEmitPerformance(ts); | ||
//#region FINAL_ANALYSIS | ||
// Need to ensure that we include emit diagnostics, since they might not be part of the other sets | ||
const { duration: mergeDiagnosticDurationMs, diagnostics } = measureTsPerformance('Diagnostics', () => { | ||
const rawDiagnostics = [...preDiagnostics, ...emitResult.diagnostics]; | ||
return { diagnostics: ts.sortAndDeduplicateDiagnostics(rawDiagnostics) }; | ||
}); | ||
this._typescriptTerminal.writeVerboseLine(`Diagnostics: ${mergeDiagnosticDurationMs}ms`); | ||
const rawDiagnostics = [...preDiagnostics, ...emitResult.diagnostics]; | ||
//#endregion | ||
//#region WRITE | ||
// Using async file system I/O for theoretically better peak performance | ||
// Also allows to run concurrently with linting | ||
const writePromise = measureTsPerformanceAsync('Write', () => Async_1.Async.forEachLimitAsync(emitResult.filesToWrite, this._configuration.maxWriteParallelism, async ({ filePath, data }) => this._cachedFileSystem.writeFile(filePath, data, { ensureFolderExists: true }))); | ||
//#endregion | ||
const typeScriptFilenames = new Set(tsconfig.fileNames); | ||
const [eslint, tslint] = await Promise.all([ | ||
this._initESlintAsync(ts, measureTsPerformance), | ||
this._initTSlintAsync(ts, measureTsPerformance) | ||
]); | ||
const lintPromises = []; | ||
const extendedProgram = tsProgram; | ||
//#region ESLINT | ||
if (eslint) { | ||
await eslint.performLintingAsync({ | ||
tsProgram: extendedProgram, | ||
typeScriptFilenames: typeScriptFilenames, | ||
changedFiles: emitResult.changedSourceFiles | ||
}); | ||
lintPromises.push(this._runESlintAsync(eslint, extendedProgram, emitResult.changedSourceFiles)); | ||
} | ||
@@ -299,7 +248,3 @@ //#endregion | ||
if (tslint) { | ||
await tslint.performLintingAsync({ | ||
tsProgram: extendedProgram, | ||
typeScriptFilenames: typeScriptFilenames, | ||
changedFiles: emitResult.changedSourceFiles | ||
}); | ||
lintPromises.push(this._runTSlintAsync(tslint, extendedProgram, emitResult.changedSourceFiles)); | ||
} | ||
@@ -309,95 +254,62 @@ //#endregion | ||
this._typescriptTerminal.writeVerboseLine(`I/O Write: ${writeDuration}ms (${emitResult.filesToWrite.length} files)`); | ||
//#region HARDLINK/COPY | ||
const shouldHardlink = this._configuration.copyFromCacheMode !== 'copy'; | ||
const { duration: hardlinkDuration, linkCount: hardlinkCount } = await measureTsPerformanceAsync(shouldHardlink ? 'Hardlink' : 'CopyFromCache', async () => { | ||
const commonSourceDirectory = extendedProgram.getCommonSourceDirectory(); | ||
const linkPromises = []; | ||
let linkCount = 0; | ||
const resolverHost = { | ||
getCurrentDirectory: () => compilerHost.getCurrentDirectory(), | ||
getCommonSourceDirectory: () => commonSourceDirectory, | ||
getCanonicalFileName: (filename) => compilerHost.getCanonicalFileName(filename) | ||
// In non-watch mode, notify EmitCompletedCallbackManager once after we complete the compile step | ||
this._emitCompletedCallbackManager.callback(); | ||
const linters = await Promise.all(lintPromises); | ||
this._logDiagnostics(ts, rawDiagnostics, linters); | ||
} | ||
async _runSolutionBuildAsync(ts, measureTsPerformance, measureTsPerformanceAsync) { | ||
this._typescriptTerminal.writeVerboseLine(`Using solution mode`); | ||
const lintPromises = []; | ||
//#region CONFIGURE | ||
const { duration: configureDurationMs, rawDiagnostics, solutionBuilderHost } = await measureTsPerformanceAsync('Configure', async () => { | ||
this._overrideTypeScriptReadJson(ts); | ||
const _tsconfig = this._loadTsconfig(ts); | ||
this._validateTsconfig(ts, _tsconfig); | ||
const _rawDiagnostics = []; | ||
const reportDiagnostic = (diagnostic) => { | ||
_rawDiagnostics.push(diagnostic); | ||
}; | ||
let queueLinkOrCopy; | ||
if (shouldHardlink) { | ||
queueLinkOrCopy = (options) => { | ||
linkPromises.push(this._cachedFileSystem | ||
.createHardLinkAsync(Object.assign(Object.assign({}, options), { alreadyExistsBehavior: "ignore" /* Ignore */ })) | ||
.then(() => { | ||
linkCount++; | ||
}) | ||
.catch((error) => { | ||
if (!node_core_library_1.FileSystem.isNotExistError(error)) { | ||
// Only re-throw errors that aren't not-exist errors | ||
throw error; | ||
} | ||
})); | ||
}; | ||
} | ||
else { | ||
queueLinkOrCopy = (options) => { | ||
linkPromises.push(this._cachedFileSystem | ||
.copyFileAsync({ | ||
sourcePath: options.linkTargetPath, | ||
destinationPath: options.newLinkPath | ||
}) | ||
.then(() => { | ||
linkCount++; | ||
}) | ||
.catch((error) => { | ||
if (!node_core_library_1.FileSystem.isNotExistError(error)) { | ||
// Only re-throw errors that aren't not-exist errors | ||
throw error; | ||
} | ||
})); | ||
}; | ||
} | ||
for (const sourceFile of genericProgram.getSourceFiles()) { | ||
const filename = sourceFile.fileName; | ||
if (typeScriptFilenames.has(filename)) { | ||
const relativeFilenameWithoutExtension = ts.removeFileExtension(ts.getExternalModuleNameFromPath(resolverHost, filename)); | ||
for (const { cacheOutFolderPath, outFolderPath, jsExtensionOverride = '.js', isPrimary } of this | ||
._moduleKindsToEmit) { | ||
// Only primary module kinds emit declarations | ||
if (isPrimary) { | ||
if (tsconfig.options.declarationMap) { | ||
const dtsMapFilename = `${relativeFilenameWithoutExtension}.d.ts.map`; | ||
queueLinkOrCopy({ | ||
linkTargetPath: path.join(cacheOutFolderPath, dtsMapFilename), | ||
newLinkPath: path.join(outFolderPath, dtsMapFilename) | ||
}); | ||
} | ||
if (tsconfig.options.declaration) { | ||
const dtsFilename = `${relativeFilenameWithoutExtension}.d.ts`; | ||
queueLinkOrCopy({ | ||
linkTargetPath: path.join(cacheOutFolderPath, dtsFilename), | ||
newLinkPath: path.join(outFolderPath, dtsFilename) | ||
}); | ||
} | ||
} | ||
if (tsconfig.options.sourceMap && !sourceFile.isDeclarationFile) { | ||
const jsMapFilename = `${relativeFilenameWithoutExtension}${jsExtensionOverride}.map`; | ||
queueLinkOrCopy({ | ||
linkTargetPath: path.join(cacheOutFolderPath, jsMapFilename), | ||
newLinkPath: path.join(outFolderPath, jsMapFilename) | ||
}); | ||
} | ||
// Write the .js file last in case something is watching its timestamp | ||
if (!sourceFile.isDeclarationFile) { | ||
const jsFilename = `${relativeFilenameWithoutExtension}${jsExtensionOverride}`; | ||
queueLinkOrCopy({ | ||
linkTargetPath: path.join(cacheOutFolderPath, jsFilename), | ||
newLinkPath: path.join(outFolderPath, jsFilename) | ||
}); | ||
} | ||
const [eslint, tslint] = await Promise.all([ | ||
this._initESlintAsync(ts, measureTsPerformance), | ||
this._initTSlintAsync(ts, measureTsPerformance) | ||
]); | ||
// TypeScript doesn't have a | ||
EmitFilesPatch_1.EmitFilesPatch.install(ts, _tsconfig, this._moduleKindsToEmit); | ||
const _solutionBuilderHost = this._buildSolutionBuilderHost(ts, reportDiagnostic); | ||
_solutionBuilderHost.afterProgramEmitAndDiagnostics = (program) => { | ||
const tsProgram = program.getProgram(); | ||
if (tsProgram) { | ||
const extendedProgram = tsProgram; | ||
if (eslint) { | ||
lintPromises.push(this._runESlintAsync(eslint, extendedProgram)); | ||
} | ||
if (tslint) { | ||
lintPromises.push(this._runTSlintAsync(tslint, extendedProgram)); | ||
} | ||
} | ||
} | ||
await Promise.all(linkPromises); | ||
return { linkCount }; | ||
}; | ||
return { | ||
rawDiagnostics: _rawDiagnostics, | ||
solutionBuilderHost: _solutionBuilderHost | ||
}; | ||
}); | ||
this._typescriptTerminal.writeVerboseLine(`${shouldHardlink ? 'Hardlink' : 'Copy from cache'}: ${hardlinkDuration}ms (${hardlinkCount} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Configure: ${configureDurationMs}ms`); | ||
//#endregion | ||
const solutionBuilder = ts.createSolutionBuilder(solutionBuilderHost, [this._configuration.tsconfigPath], {}); | ||
//#region EMIT | ||
// Ignoring the exit status because we only care about presence of diagnostics | ||
solutionBuilder.build(); | ||
//#endregion | ||
this._logReadPerformance(ts); | ||
this._logEmitPerformance(ts); | ||
// Use the native metric since we aren't overwriting the writer | ||
this._typescriptTerminal.writeVerboseLine(`I/O Write: ${ts.performance.getDuration('I/O Write')}ms (${ts.performance.getCount('beforeIOWrite')} files)`); | ||
// In non-watch mode, notify EmitCompletedCallbackManager once after we complete the compile step | ||
this._emitCompletedCallbackManager.callback(); | ||
//#endregion | ||
const linters = await Promise.all(lintPromises); | ||
this._logDiagnostics(ts, rawDiagnostics, linters); | ||
EmitFilesPatch_1.EmitFilesPatch.uninstall(ts); | ||
} | ||
_logDiagnostics(ts, rawDiagnostics, linters) { | ||
const diagnostics = ts.sortAndDeduplicateDiagnostics(rawDiagnostics); | ||
let typeScriptErrorCount = 0; | ||
@@ -414,8 +326,5 @@ if (diagnostics.length > 0) { | ||
} | ||
if (eslint) { | ||
eslint.reportFailures(); | ||
for (const linter of linters) { | ||
linter.reportFailures(); | ||
} | ||
if (tslint) { | ||
tslint.reportFailures(); | ||
} | ||
if (typeScriptErrorCount > 0) { | ||
@@ -425,2 +334,81 @@ throw new Error(`Encountered TypeScript error${typeScriptErrorCount > 1 ? 's' : ''}`); | ||
} | ||
_logEmitPerformance(ts) { | ||
this._typescriptTerminal.writeVerboseLine(`Bind: ${ts.performance.getDuration('Bind')}ms`); | ||
this._typescriptTerminal.writeVerboseLine(`Check: ${ts.performance.getDuration('Check')}ms`); | ||
this._typescriptTerminal.writeVerboseLine(`Transform: ${ts.performance.getDuration('transformTime')}ms ` + | ||
`(${ts.performance.getCount('beforeTransform')} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Print: ${ts.performance.getDuration('printTime')}ms ` + | ||
`(${ts.performance.getCount('beforePrint')} files) (Includes Transform)`); | ||
this._typescriptTerminal.writeVerboseLine(`Emit: ${ts.performance.getDuration('Emit')}ms (Includes Print)`); | ||
} | ||
_logReadPerformance(ts) { | ||
this._typescriptTerminal.writeVerboseLine(`I/O Read: ${ts.performance.getDuration('I/O Read')}ms (${ts.performance.getCount('beforeIORead')} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Parse: ${ts.performance.getDuration('Parse')}ms (${ts.performance.getCount('beforeParse')} files)`); | ||
this._typescriptTerminal.writeVerboseLine(`Program (includes Read + Parse): ${ts.performance.getDuration('Program')}ms`); | ||
} | ||
async _initTSlintAsync(ts, measureTsPerformance) { | ||
if (this._tslintEnabled) { | ||
if (!this._configuration.tslintToolPath) { | ||
throw new Error('Unable to resolve "tslint" package'); | ||
} | ||
const logger = await this.requestScopedLoggerAsync('tslint'); | ||
return { | ||
logger, | ||
ts, | ||
measureTsPerformance | ||
}; | ||
} | ||
} | ||
async _initESlintAsync(ts, measureTsPerformance) { | ||
if (this._eslintEnabled) { | ||
if (!this._configuration.eslintToolPath) { | ||
throw new Error('Unable to resolve "eslint" package'); | ||
} | ||
const logger = await this.requestScopedLoggerAsync('eslint'); | ||
return { | ||
logger, | ||
ts, | ||
measureTsPerformance | ||
}; | ||
} | ||
} | ||
async _runESlintAsync(linter, tsProgram, changedFiles) { | ||
const eslint = new Eslint_1.Eslint({ | ||
ts: linter.ts, | ||
scopedLogger: linter.logger, | ||
buildFolderPath: this._configuration.buildFolder, | ||
buildMetadataFolderPath: this._configuration.buildMetadataFolder, | ||
linterConfigFilePath: this._eslintConfigFilePath, | ||
measurePerformance: linter.measureTsPerformance, | ||
eslintPackagePath: this._configuration.eslintToolPath | ||
}); | ||
eslint.printVersionHeader(); | ||
const typeScriptFilenames = new Set(tsProgram.getRootFileNames()); | ||
await eslint.performLintingAsync({ | ||
tsProgram, | ||
typeScriptFilenames, | ||
changedFiles: changedFiles || new Set(tsProgram.getSourceFiles()) | ||
}); | ||
return eslint; | ||
} | ||
async _runTSlintAsync(linter, tsProgram, changedFiles) { | ||
const tslint = new Tslint_1.Tslint({ | ||
ts: linter.ts, | ||
scopedLogger: linter.logger, | ||
buildFolderPath: this._configuration.buildFolder, | ||
buildMetadataFolderPath: this._configuration.buildMetadataFolder, | ||
linterConfigFilePath: this._tslintConfigFilePath, | ||
measurePerformance: linter.measureTsPerformance, | ||
cachedFileSystem: this._cachedFileSystem, | ||
tslintPackagePath: this._configuration.tslintToolPath | ||
}); | ||
tslint.printVersionHeader(); | ||
const typeScriptFilenames = new Set(tsProgram.getRootFileNames()); | ||
await tslint.performLintingAsync({ | ||
tsProgram, | ||
typeScriptFilenames, | ||
changedFiles: changedFiles || new Set(tsProgram.getSourceFiles()) | ||
}); | ||
return tslint; | ||
} | ||
_printDiagnosticMessage(ts, diagnostic, diagnosticCategory = this._getAdjustedDiagnosticCategory(diagnostic, ts)) { | ||
@@ -481,6 +469,5 @@ // Code taken from reference example | ||
const changedFiles = new Set(); | ||
EmitFilesPatch_1.EmitFilesPatch.install(ts, tsconfig, this._moduleKindsToEmit, /* useBuildCache */ true, changedFiles); | ||
EmitFilesPatch_1.EmitFilesPatch.install(ts, tsconfig, this._moduleKindsToEmit, changedFiles); | ||
const writeFileCallback = (filePath, data) => { | ||
const redirectedFilePath = EmitFilesPatch_1.EmitFilesPatch.getRedirectedFilePath(filePath); | ||
filesToWrite.push({ filePath: redirectedFilePath, data }); | ||
filesToWrite.push({ filePath, data }); | ||
}; | ||
@@ -616,3 +603,2 @@ const result = genericProgram.emit(undefined, // Target source file | ||
moduleKind, | ||
cacheOutFolderPath: node_core_library_1.Path.convertToSlashes(path.resolve(this._configuration.buildCacheFolder, outFolderName)), | ||
jsExtensionOverride, | ||
@@ -633,65 +619,79 @@ isPrimary | ||
}, currentFolder); | ||
if (this._useIncrementalProgram) { | ||
tsconfig.options.incremental = true; | ||
tsconfig.options.tsBuildInfoFile = this._tsCacheFilePath; | ||
} | ||
return tsconfig; | ||
} | ||
_buildSolutionBuilderHost(ts, reportDiagnostic) { | ||
const reportSolutionBuilderStatus = reportDiagnostic; | ||
const reportEmitErrorSummary = (errorCount) => { | ||
// Do nothing | ||
}; | ||
const compilerHost = ts.createSolutionBuilderHost(this._getCachingTypeScriptSystem(ts), ts.createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, reportSolutionBuilderStatus, reportEmitErrorSummary); | ||
return compilerHost; | ||
} | ||
_buildIncrementalCompilerHost(ts, tsconfig) { | ||
let compilerHost; | ||
if (this._useIncrementalProgram) { | ||
compilerHost = ts.createIncrementalCompilerHost(tsconfig.options); | ||
if (tsconfig.options.incremental) { | ||
return ts.createIncrementalCompilerHost(tsconfig.options, this._getCachingTypeScriptSystem(ts)); | ||
} | ||
else { | ||
compilerHost = ts.createCompilerHost(tsconfig.options); | ||
return ts.createCompilerHost(tsconfig.options); | ||
} | ||
compilerHost.realpath = this._cachedFileSystem.getRealPath.bind(this._cachedFileSystem); | ||
compilerHost.readFile = (filePath) => { | ||
try { | ||
return this._cachedFileSystem.readFile(filePath, {}); | ||
} | ||
catch (error) { | ||
if (node_core_library_1.FileSystem.isNotExistError(error)) { | ||
return undefined; | ||
} | ||
_getCachingTypeScriptSystem(ts) { | ||
const sys = Object.assign(Object.assign({}, ts.sys), { deleteFile: this._cachedFileSystem.deleteFile.bind(this._cachedFileSystem), | ||
/** Check if the path exists and is a directory */ | ||
directoryExists: (directoryPath) => { | ||
try { | ||
const stats = this._cachedFileSystem.getStatistics(directoryPath); | ||
return stats.isDirectory() || stats.isSymbolicLink(); | ||
} | ||
else { | ||
throw error; | ||
catch (error) { | ||
if (node_core_library_1.FileSystem.isNotExistError(error)) { | ||
return false; | ||
} | ||
else { | ||
throw error; | ||
} | ||
} | ||
} | ||
}; | ||
compilerHost.fileExists = this._cachedFileSystem.exists.bind(this._cachedFileSystem); | ||
compilerHost.directoryExists = (directoryPath) => { | ||
try { | ||
const stats = this._cachedFileSystem.getStatistics(directoryPath); | ||
return stats.isDirectory() || stats.isSymbolicLink(); | ||
} | ||
catch (error) { | ||
if (node_core_library_1.FileSystem.isNotExistError(error)) { | ||
return false; | ||
}, | ||
/** Check if the path exists and is a file */ | ||
fileExists: (filePath) => { | ||
try { | ||
const stats = this._cachedFileSystem.getStatistics(filePath); | ||
return stats.isFile(); | ||
} | ||
else { | ||
throw error; | ||
catch (error) { | ||
if (node_core_library_1.FileSystem.isNotExistError(error)) { | ||
return false; | ||
} | ||
else { | ||
throw error; | ||
} | ||
} | ||
}, | ||
/* Use the Heft config's build folder because it has corrected casing */ | ||
getCurrentDirectory: () => this._configuration.buildFolder, getDirectories: (folderPath) => { | ||
return this._cachedFileSystem.readFolderFilesAndDirectories(folderPath).directories; | ||
}, realpath: this._cachedFileSystem.getRealPath.bind(this._cachedFileSystem) }); | ||
return sys; | ||
} | ||
_buildWatchCompilerHost(ts, tsconfig) { | ||
const reportDiagnostic = (diagnostic) => { | ||
this._printDiagnosticMessage(ts, diagnostic); | ||
}; | ||
const reportWatchStatus = (diagnostic) => { | ||
this._printDiagnosticMessage(ts, diagnostic); | ||
// In watch mode, notify EmitCompletedCallbackManager every time we finish recompiling. | ||
if (diagnostic.code === ts.Diagnostics.Found_0_errors_Watching_for_file_changes.code || | ||
diagnostic.code === ts.Diagnostics.Found_1_error_Watching_for_file_changes.code) { | ||
this._emitCompletedCallbackManager.callback(); | ||
} | ||
}; | ||
compilerHost.getDirectories = (folderPath) => this._cachedFileSystem.readFolderFilesAndDirectories(folderPath).directories; | ||
/* Use the Heft config's build folder because it has corrected casing */ | ||
compilerHost.getCurrentDirectory = () => this._configuration.buildFolder; | ||
return compilerHost; | ||
return ts.createWatchCompilerHost(tsconfig.fileNames, tsconfig.options, this._getCachingTypeScriptSystem(ts), ts.createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, reportWatchStatus, tsconfig.projectReferences); | ||
} | ||
_buildWatchCompilerHost(ts, tsconfig) { | ||
return ts.createWatchCompilerHost(tsconfig.fileNames, tsconfig.options, ts.sys, (rootNames, options, compilerHost, oldProgram, configFileParsingDiagnostics, projectReferences) => { | ||
if (compilerHost === undefined) { | ||
throw new node_core_library_1.InternalError('_buildWatchCompilerHost() expects a compilerHost to be configured'); | ||
} | ||
const originalWriteFile = compilerHost.writeFile; | ||
compilerHost.writeFile = (filePath, | ||
// Do this with a "rest" argument in case the TS API changes | ||
...rest) => { | ||
const redirectedFilePath = EmitFilesPatch_1.EmitFilesPatch.getRedirectedFilePath(filePath); | ||
originalWriteFile.call(this, redirectedFilePath, ...rest); | ||
}; | ||
return ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, options, compilerHost, oldProgram, configFileParsingDiagnostics, projectReferences); | ||
}, (diagnostic) => this._printDiagnosticMessage(ts, diagnostic), (diagnostic) => { | ||
_buildWatchSolutionBuilderHost(ts) { | ||
const reportDiagnostic = (diagnostic) => { | ||
this._printDiagnosticMessage(ts, diagnostic); | ||
}; | ||
const reportSolutionBuilderStatus = reportDiagnostic; | ||
const reportWatchStatus = (diagnostic) => { | ||
this._printDiagnosticMessage(ts, diagnostic); | ||
// In watch mode, notify EmitCompletedCallbackManager every time we finish recompiling. | ||
@@ -702,3 +702,4 @@ if (diagnostic.code === ts.Diagnostics.Found_0_errors_Watching_for_file_changes.code || | ||
} | ||
}, tsconfig.projectReferences); | ||
}; | ||
return ts.createSolutionBuilderWithWatchHost(this._getCachingTypeScriptSystem(ts), ts.createEmitAndSemanticDiagnosticsBuilderProgram, reportDiagnostic, reportSolutionBuilderStatus, reportWatchStatus); | ||
} | ||
@@ -705,0 +706,0 @@ _overrideTypeScriptReadJson(ts) { |
import { HeftSession } from '../../pluginFramework/HeftSession'; | ||
import { HeftConfiguration } from '../../configuration/HeftConfiguration'; | ||
import { IHeftPlugin } from '../../pluginFramework/IHeftPlugin'; | ||
import { CopyFromCacheMode } from '../../stages/BuildStage'; | ||
import { ToolPackageResolver } from '../../utilities/ToolPackageResolver'; | ||
@@ -14,8 +13,2 @@ import { ISharedCopyConfiguration } from '../../utilities/CoreConfigFiles'; | ||
/** | ||
* Can be set to 'copy' or 'hardlink'. If set to 'copy', copy files from cache. If set to 'hardlink', files will be | ||
* hardlinked to the cache location. This option is useful when producing a tarball of build output as TAR files | ||
* don't handle these hardlinks correctly. 'hardlink' is the default behavior. | ||
*/ | ||
copyFromCacheMode?: CopyFromCacheMode | undefined; | ||
/** | ||
* If provided, emit these module kinds in addition to the modules specified in the tsconfig. | ||
@@ -34,2 +27,8 @@ * Note that this option only applies to the main tsconfig.json configuration. | ||
/** | ||
* If true, enable behavior analogous to the "tsc --build" command. Will build projects referenced by the main project in dependency order. | ||
* Note that this will effectively enable \"noEmitOnError\". | ||
*/ | ||
buildProjectReferences?: boolean; | ||
project?: string; | ||
/** | ||
* Specifies the intermediary folder that tests will use. Because Jest uses the | ||
@@ -36,0 +35,0 @@ * Node.js runtime to execute tests, the module format must be CommonJS. |
@@ -98,3 +98,5 @@ "use strict"; | ||
const typescriptConfigurationJson = await this._ensureConfigFileLoadedAsync(logger.terminal, heftConfiguration); | ||
const tsconfigFilePath = `${heftConfiguration.buildFolder}/tsconfig.json`; | ||
const { project = './tsconfig.json' } = typescriptConfigurationJson || {}; | ||
const tsconfigFilePath = path.resolve(heftConfiguration.buildFolder, project); | ||
logger.terminal.writeVerboseLine(`Looking for tsconfig at ${tsconfigFilePath}`); | ||
buildProperties.isTypeScriptProject = await node_core_library_1.FileSystem.existsAsync(tsconfigFilePath); | ||
@@ -106,4 +108,4 @@ if (!buildProperties.isTypeScriptProject) { | ||
const typeScriptConfiguration = { | ||
copyFromCacheMode: typescriptConfigurationJson === null || typescriptConfigurationJson === void 0 ? void 0 : typescriptConfigurationJson.copyFromCacheMode, | ||
additionalModuleKindsToEmit: typescriptConfigurationJson === null || typescriptConfigurationJson === void 0 ? void 0 : typescriptConfigurationJson.additionalModuleKindsToEmit, | ||
buildProjectReferences: typescriptConfigurationJson === null || typescriptConfigurationJson === void 0 ? void 0 : typescriptConfigurationJson.buildProjectReferences, | ||
emitCjsExtensionForCommonJS: typescriptConfigurationJson === null || typescriptConfigurationJson === void 0 ? void 0 : typescriptConfigurationJson.emitCjsExtensionForCommonJS, | ||
@@ -115,16 +117,2 @@ emitMjsExtensionForESModule: typescriptConfigurationJson === null || typescriptConfigurationJson === void 0 ? void 0 : typescriptConfigurationJson.emitMjsExtensionForESModule, | ||
}; | ||
if (heftConfiguration.projectPackageJson.private !== true) { | ||
if (typeScriptConfiguration.copyFromCacheMode === undefined) { | ||
logger.terminal.writeVerboseLine('Setting TypeScript copyFromCacheMode to "copy" because the "private" field ' + | ||
'in package.json is not set to true. Linked files are not handled correctly ' + | ||
'when package are packed for publishing.'); | ||
// Copy if the package is intended to be published | ||
typeScriptConfiguration.copyFromCacheMode = 'copy'; | ||
} | ||
else if (typeScriptConfiguration.copyFromCacheMode !== 'copy') { | ||
logger.emitWarning(new Error(`The TypeScript copyFromCacheMode is set to "${typeScriptConfiguration.copyFromCacheMode}", ` + | ||
'but the the "private" field in package.json is not set to true. ' + | ||
'Linked files are not handled correctly when packages are packed for publishing.')); | ||
} | ||
} | ||
const toolPackageResolution = await this._taskPackageResolver.resolveToolPackagesAsync(heftConfiguration, logger.terminal); | ||
@@ -141,12 +129,12 @@ if (!toolPackageResolution.typeScriptPackagePath) { | ||
buildFolder: heftConfiguration.buildFolder, | ||
buildMetadataFolder: path.join(heftConfiguration.buildFolder, 'temp'), | ||
typeScriptToolPath: toolPackageResolution.typeScriptPackagePath, | ||
tslintToolPath: toolPackageResolution.tslintPackagePath, | ||
eslintToolPath: toolPackageResolution.eslintPackagePath, | ||
buildProjectReferences: typescriptConfigurationJson === null || typescriptConfigurationJson === void 0 ? void 0 : typescriptConfigurationJson.buildProjectReferences, | ||
tsconfigPath: tsconfigFilePath, | ||
lintingEnabled: !!typeScriptConfiguration.isLintingEnabled, | ||
buildCacheFolder: heftConfiguration.buildCacheFolder, | ||
additionalModuleKindsToEmit: typeScriptConfiguration.additionalModuleKindsToEmit, | ||
emitCjsExtensionForCommonJS: !!typeScriptConfiguration.emitCjsExtensionForCommonJS, | ||
emitMjsExtensionForESModule: !!typeScriptConfiguration.emitMjsExtensionForESModule, | ||
copyFromCacheMode: typeScriptConfiguration.copyFromCacheMode, | ||
watchMode: watchMode, | ||
@@ -153,0 +141,0 @@ maxWriteParallelism: typeScriptConfiguration.maxWriteParallelism |
@@ -20,8 +20,2 @@ { | ||
"copyFromCacheMode": { | ||
"description": "Can be set to \"copy\" or \"hardlink\". If set to \"copy\", copy files from cache. If set to \"hardlink\", files will be hardlinked to the cache location. This option is useful when producing a tarball of build output as TAR files don't handle these hardlinks correctly. \"hardlink\" is the default behavior.", | ||
"type": "string", | ||
"enum": ["hardlink", "copy"] | ||
}, | ||
"additionalModuleKindsToEmit": { | ||
@@ -62,2 +56,12 @@ "type": "array", | ||
"buildProjectReferences": { | ||
"description": "If true, enable behavior analogous to the \"tsc --build\" command. Will build projects referenced by the main project. Note that this will effectively enable \"noEmitOnError\".", | ||
"type": "boolean" | ||
}, | ||
"project": { | ||
"description": "Specifies the tsconfig.json file that will be used for compilation. Equivalent to the \"project\" argument for the 'tsc' and 'tslint' command line tools. The default value is \"./tsconfig.json\".", | ||
"type": "string" | ||
}, | ||
"disableTslint": { | ||
@@ -64,0 +68,0 @@ "description": "If set to \"true\", disable TSlint.", |
@@ -22,6 +22,2 @@ import { SyncHook, AsyncParallelHook, AsyncSeriesHook, AsyncSeriesWaterfallHook } from 'tapable'; | ||
*/ | ||
export declare type CopyFromCacheMode = 'hardlink' | 'copy'; | ||
/** | ||
* @public | ||
*/ | ||
export declare class CompileSubstageHooks extends BuildSubstageHooksBase { | ||
@@ -28,0 +24,0 @@ /** |
@@ -14,10 +14,2 @@ /** | ||
/** | ||
* Can be set to "copy" or "hardlink". If set to "copy", copy files from cache. | ||
* If set to "hardlink", files will be hardlinked to the cache location. | ||
* This option is useful when producing a tarball of build output as TAR files don't | ||
* handle these hardlinks correctly. "hardlink" is the default behavior. | ||
*/ | ||
// "copyFromCacheMode": "copy", | ||
/** | ||
* If provided, emit these module kinds in addition to the modules specified in the tsconfig. | ||
@@ -24,0 +16,0 @@ * Note that this option only applies to the main tsconfig.json configuration. |
{ | ||
"name": "@rushstack/heft", | ||
"version": "0.35.1", | ||
"version": "0.36.0", | ||
"description": "Build all your JavaScript projects the same way: A way that works.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1114263
11596