Skip to content

Commit 74aea99

Browse files
johnjenkinsJohn Jenkins
andauthored
fix(build): always fail build on typescript failure (#6520)
* fix(build): always fail build on typescript failure * chore: --------- Co-authored-by: John Jenkins <john.jenkins@nanoporetech.com>
1 parent 9e38aa7 commit 74aea99

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

src/compiler/build/build.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type * as d from '../../declarations';
66
import { generateOutputTargets } from '../output-targets';
77
import { emptyOutputTargets } from '../output-targets/empty-dir';
88
import { generateGlobalStyles } from '../style/global-styles';
9-
import { runTsProgram } from '../transpile/run-program';
9+
import { runTsProgram, validateTypesAfterGeneration } from '../transpile/run-program';
1010
import { buildAbort, buildFinish } from './build-finish';
1111
import { writeBuild } from './write-build';
1212

@@ -36,10 +36,20 @@ export const build = async (
3636

3737
// run typescript program
3838
const tsTimeSpan = buildCtx.createTimeSpan('transpile started');
39-
const componentDtsChanged = await runTsProgram(config, compilerCtx, buildCtx, tsBuilder);
39+
const emittedDts = await runTsProgram(config, compilerCtx, buildCtx, tsBuilder);
4040
tsTimeSpan.finish('transpile finished');
4141
if (buildCtx.hasError) return buildAbort(buildCtx);
4242

43+
// generate types and validate AFTER components.d.ts is written
44+
const componentDtsChanged = await validateTypesAfterGeneration(
45+
config,
46+
compilerCtx,
47+
buildCtx,
48+
tsBuilder,
49+
emittedDts,
50+
);
51+
if (buildCtx.hasError) return buildAbort(buildCtx);
52+
4353
if (config.watch && componentDtsChanged) {
4454
// silent abort for watch mode only
4555
return null;

src/compiler/transpile/run-program.ts

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const runTsProgram = async (
2626
compilerCtx: d.CompilerCtx,
2727
buildCtx: d.BuildCtx,
2828
tsBuilder: ts.BuilderProgram,
29-
): Promise<boolean> => {
29+
): Promise<string[]> => {
3030
const tsSyntactic = loadTypeScriptDiagnostics(tsBuilder.getSyntacticDiagnostics());
3131
const tsGlobal = loadTypeScriptDiagnostics(tsBuilder.getGlobalDiagnostics());
3232
const tsOptions = loadTypeScriptDiagnostics(tsBuilder.getOptionsDiagnostics());
@@ -35,7 +35,7 @@ export const runTsProgram = async (
3535
buildCtx.diagnostics.push(...tsOptions);
3636

3737
if (buildCtx.hasError) {
38-
return false;
38+
return [];
3939
}
4040

4141
const tsProgram = tsBuilder.getProgram();
@@ -136,7 +136,54 @@ export const runTsProgram = async (
136136
validateTranspiledComponents(config, buildCtx);
137137

138138
if (buildCtx.hasError) {
139-
return false;
139+
return [];
140+
}
141+
142+
return emittedDts;
143+
};
144+
145+
/**
146+
* Generate types and run semantic validation AFTER components.d.ts exists on disk
147+
*/
148+
export const validateTypesAfterGeneration = async (
149+
config: d.ValidatedConfig,
150+
compilerCtx: d.CompilerCtx,
151+
buildCtx: d.BuildCtx,
152+
tsBuilder: ts.BuilderProgram,
153+
emittedDts: string[],
154+
): Promise<boolean> => {
155+
const tsProgram = tsBuilder.getProgram();
156+
const typesOutputTarget = config.outputTargets.filter(isOutputTargetDistTypes);
157+
158+
// Check if components.d.ts already exists
159+
const componentsDtsPath = join(config.srcDir, 'components.d.ts');
160+
const componentsDtsExists = await compilerCtx.fs.access(componentsDtsPath);
161+
162+
// Only validate source files if components.d.ts already exists
163+
// If it doesn't exist yet (first build), skip validation to avoid chicken-and-egg errors
164+
if (config.validateTypes && componentsDtsExists) {
165+
const sourceFiles = tsProgram.getSourceFiles().filter((sf) => {
166+
const fileName = normalizePath(sf.fileName);
167+
return (
168+
!fileName.includes('node_modules') &&
169+
!fileName.endsWith('.d.ts') &&
170+
fileName.startsWith(normalizePath(config.srcDir))
171+
);
172+
});
173+
174+
for (const sourceFile of sourceFiles) {
175+
const sourceSemanticDiagnostics = tsProgram.getSemanticDiagnostics(sourceFile);
176+
const tsSemantic = loadTypeScriptDiagnostics(sourceSemanticDiagnostics);
177+
178+
if (config.devMode) {
179+
tsSemantic.forEach((semanticDiagnostic) => {
180+
if (semanticDiagnostic.code === '6133' || semanticDiagnostic.code === '6192') {
181+
semanticDiagnostic.level = 'warn';
182+
}
183+
});
184+
}
185+
buildCtx.diagnostics.push(...tsSemantic);
186+
}
140187
}
141188

142189
// create the components.d.ts file and write to disk
@@ -164,21 +211,8 @@ export const runTsProgram = async (
164211
await Promise.all(srcRootDtsFiles);
165212
}
166213

167-
// TODO(STENCIL-540): remove `hasTypesChanged` check and figure out how to generate types before
168-
// executing the TS build program so we don't get semantic diagnostic errors about referencing the
169-
// auto-generated `components.d.ts` file.
170-
if (config.validateTypes && !hasTypesChanged) {
171-
const tsSemantic = loadTypeScriptDiagnostics(tsBuilder.getSemanticDiagnostics());
172-
if (config.devMode) {
173-
tsSemantic.forEach((semanticDiagnostic) => {
174-
// Unused variable errors become warnings in dev mode
175-
if (semanticDiagnostic.code === '6133' || semanticDiagnostic.code === '6192') {
176-
semanticDiagnostic.level = 'warn';
177-
}
178-
});
179-
}
180-
buildCtx.diagnostics.push(...tsSemantic);
181-
}
214+
// Note: We validated user source files above before generating types
215+
// We don't validate components.d.ts itself as it may reference types that will resolve later
182216

183217
return hasTypesChanged;
184218
};

0 commit comments

Comments
 (0)