Open
Description
π Search Terms
"solution builder cache", "tsbuildinfo cache", "compiler options change build"
π Version & Regression Information
n/a
β― Playground Link
n/a
π» Code
n/a
π Actual behavior
- In a project using references, suppose you have a
tsconfig.json
specifying any random option (e.g.,skipLibCheck: true
). - Run a build using
tsc --build ./tsconfig.json
. - See output.
- Run the build again, this time programmatically using
createSolutionBuilder
and override one compiler option viagetParsedCommandLine
in the builder host (e.g.,skipLibCheck: false
). - Build will be skipped as the builder sees project files are not out of date, but fails to detect that compiler options are not the same used during the last build. We get the same output of the previous run.
- Cleaning up
.tsbuildinfo
files and running the build programmatically again work as expected. New output is different from the first one.
π Expected behavior
I would expect the builder to detect that new compiler options are different from the last used and re-build that project.
Additional information about the issue
I narrowed down the issue to ./src/compiler/tsbuildPublic.ts
- Line 1606
Basically, there is where UpToDateStatusType.OutOfDateOptions
is reported, but current compiler options are not being compared with the ones from the relative tsbuildinfo
file.
Manually changing the tsconfig.json
file works because the timestamp of the file changes and not because options are different.
I discovered this while creating a script to address #30511
Script
const {
formatDiagnostic,
sys,
createSolutionBuilderHost,
getParsedCommandLineOfConfigFile,
DiagnosticCategory,
formatDiagnosticsWithColorAndContext,
createSolutionBuilder
} = require('typescript');
const {resolveRoot} = require('../utils/resolve-root.cjs');
// PATH TO YOUR ROOT tsconfig.json (THE ONE WITH 'references')
const TSCONFIG_PATH = resolveRoot('./tsconfig.json');
// FUNCTION TO FORMAT DIAGNOSTICS (REUSED FOR CONSISTENCY)
const format = (diagnostic) => {
return formatDiagnostic(diagnostic, {
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine
});
};
const context = {
allDiagnostics: [],
workspaceDiagnostics: [],
hasErrors: false,
hasWorkspaceErrors: false
};
// CREATE THE DEFAULT SOLUTION BUILDER HOST FIRST
const defaultSolutionBuilderHost = createSolutionBuilderHost(sys);
// NOW, CREATE OUR CUSTOM HOST BY EXTENDING THE DEFAULT ONE
const customHost = {
...defaultSolutionBuilderHost,
// OVERRIDE getParsedCommandLine TO MODIFY OPTIONS FOR EACH PROJECT
// THIS FUNCTION IS CALLED BY THE SOLUTION BUILDER FOR EACH TSCONFIG FILE IT PROCESSES
getParsedCommandLine: (fileName) => {
const parsed = getParsedCommandLineOfConfigFile(
fileName,
{},
{
...sys,
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
console.error(format(diagnostic));
}
}
);
if (parsed) {
parsed.options.noEmitOnError = false;
parsed.options.skipLibCheck = false;
}
return parsed;
},
// OVERRIDE reportDiagnostic TO ADD FILTERING LOGIC
reportDiagnostic: (diagnostic) => {
context.allDiagnostics.push(diagnostic);
// FILTER DIAGNOSTICS: EXCLUDE THOSE FROM NODE_MODULES
if (diagnostic.file) {
const filePath = diagnostic.file.fileName;
if (filePath.includes('node_modules')) {
if (diagnostic.category === DiagnosticCategory.Error) {
context.hasErrors = true;
}
return;
}
}
context.workspaceDiagnostics.push(diagnostic);
if (diagnostic.category === DiagnosticCategory.Error) {
context.hasErrors = true;
context.hasWorkspaceErrors = true;
}
},
// OVERRIDE reportSolutionBuilderStatus
reportSolutionBuilderStatus: (diagnostic) => {
// THESE ARE USUALLY BUILD STATUS MESSAGES, NOT CODE ERRORS.
// YOU CAN CHOOSE TO FILTER THESE OR NOT. FOR NOW, LET'S PRINT THEM DIRECTLY.
// YOU MIGHT STILL WANT TO USE formatDiagnostic FOR CONSISTENT OUTPUT
console.log(format(diagnostic));
}
};
// CREATE THE SOLUTION BUILDER USING YOUR CUSTOM HOST
const builder = createSolutionBuilder(customHost, [TSCONFIG_PATH], {
verbose: true
});
// PERFORM THE BUILD
console.log();
console.log('STARTING SOLUTION BUILD...');
console.log();
builder.build();
console.log('SOLUTION BUILD FINISHED.');
console.log();
// AFTER THE BUILD, FORMAT AND PRINT THE COLLECTED (AND FILTERED) DIAGNOSTICS
if (context.workspaceDiagnostics.length) {
console.log('WORKSPACE TYPESCRIPT DIAGNOSTICS:\n');
context.workspaceDiagnostics.forEach((diag, index) => {
const formatted = formatDiagnosticsWithColorAndContext([diag], {
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine
});
console.log(formatted);
if (index < context.workspaceDiagnostics.length - 1) {
console.log('\n---\n');
}
});
}
console.log(
`WORKSPACE TYPESCRIPT DIAGNOSTICS FOUND: ${context.workspaceDiagnostics.length}`
);
console.log(
`TOTAL TYPESCRIPT DIAGNOSTICS FOUND: ${context.allDiagnostics.length}`
);
console.log();
if (context.hasWorkspaceErrors) {
throw new Error('BUILD CONTAINS ERRORS. SEE LOGS ABOVE.');
}
Note: I would be happy to open a PR