Skip to content

Commit 2d32957

Browse files
authored
Add a CI job to prevent introducing new TypeScripts errors (#9767)
* Add a CI job to prevent introducing new TypeScripts errors Signed-off-by: Joey Liu <[email protected]> * Update scripts Signed-off-by: Joey Liu <[email protected]> * update tsbuildinfo Signed-off-by: Joey Liu <[email protected]> * update Signed-off-by: Joey Liu <[email protected]> * update chekcer Signed-off-by: Joey Liu <[email protected]> * update Signed-off-by: Joey Liu <[email protected]> * update Signed-off-by: Joey Liu <[email protected]> * Delete build/tstypecheckbuildinfo/opensearch-dashboards Signed-off-by: Joey Liu <[email protected]> * pull latest change Signed-off-by: Joey Liu <[email protected]> * fix a type error Signed-off-by: Joey Liu <[email protected]> --------- Signed-off-by: Joey Liu <[email protected]> Signed-off-by: Joey Liu <[email protected]>
1 parent 2481de4 commit 2d32957

File tree

9 files changed

+10972
-6
lines changed

9 files changed

+10972
-6
lines changed

.github/workflows/build_and_test_workflow.yml

+4
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ jobs:
183183
exit 1
184184
fi
185185
186+
- name: Run TypeScript error check
187+
id: typeScript-error-check
188+
run: yarn typecheck
189+
186190
- name: Run linter
187191
id: linter
188192
run: yarn lint

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,4 @@ snapshots.js
7878

7979
# Ignore the lighthousereport generatated
8080
.lighthouserc.js
81-
.lhci_output.json
81+
.lhci_output.json

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
"test:ftr": "scripts/use_node scripts/functional_tests",
5858
"test:ftr:server": "scripts/use_node scripts/functional_tests_server",
5959
"test:ftr:runner": "scripts/use_node scripts/functional_test_runner",
60+
"typecheck": "node ./scripts/ts_error_checker.js",
61+
"update-ts-baseline": "node ./scripts/update_ts_baseline.js",
6062
"checkLicenses": "scripts/use_node scripts/check_licenses --dev",
6163
"notice:validate": "scripts/use_node scripts/notice --validate",
6264
"notice:generate": "scripts/use_node scripts/notice",

scripts/ts_error_checker.js

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/* eslint no-restricted-syntax: 0 */
7+
const { spawnSync } = require('child_process');
8+
const fs = require('fs');
9+
const path = require('path');
10+
11+
// ANSI color codes for console output
12+
const colors = {
13+
reset: '\x1b[0m',
14+
red: '\x1b[31m',
15+
green: '\x1b[32m',
16+
yellow: '\x1b[33m',
17+
blue: '\x1b[34m',
18+
magenta: '\x1b[35m',
19+
cyan: '\x1b[36m',
20+
white: '\x1b[37m',
21+
bold: '\x1b[1m',
22+
};
23+
24+
const BASELINE_FILE = path.join(process.cwd(), 'ts_error_baseline.json');
25+
const TSC_BIN = path.join(process.cwd(), 'node_modules/.bin/tsc');
26+
const TSCONFIG_PATH = path.join(process.cwd(), 'tsconfig.check.json');
27+
28+
console.log(
29+
`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`
30+
);
31+
console.log(`${colors.bold}TypeScript Error Checker${colors.reset}`);
32+
console.log(
33+
`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`
34+
);
35+
console.log(
36+
`\n${colors.cyan}${colors.reset}Running TypeScript compiler to check for new errors...\n`
37+
);
38+
39+
// Load baseline errors
40+
function loadBaseline() {
41+
try {
42+
if (fs.existsSync(BASELINE_FILE)) {
43+
const content = fs.readFileSync(BASELINE_FILE, 'utf8');
44+
const baseline = JSON.parse(content);
45+
console.log(`${colors.blue}${colors.reset}Loaded baseline from ${BASELINE_FILE}`);
46+
console.log(
47+
`${colors.blue}${colors.reset}Baseline contains ${colors.bold}${baseline.errors.length}${colors.reset} known errors`
48+
);
49+
return baseline.errors;
50+
} else {
51+
console.warn(`${colors.yellow}${colors.reset}Baseline file not found at ${BASELINE_FILE}.`);
52+
console.warn(`${colors.yellow}${colors.reset}All errors will be treated as new.`);
53+
return [];
54+
}
55+
} catch (error) {
56+
console.error(`${colors.red}${colors.reset}Error loading baseline: ${error.message}`);
57+
process.exit(1);
58+
}
59+
}
60+
61+
// Helper to run TypeScript compiler and get structured output
62+
function getCurrentErrors() {
63+
// Check if tsconfig.json exists
64+
if (!fs.existsSync(TSCONFIG_PATH)) {
65+
console.error(`Error: Could not find tsconfig.json at ${TSCONFIG_PATH}`);
66+
process.exit(1);
67+
}
68+
69+
console.log(`Using TypeScript config: ${TSCONFIG_PATH}`);
70+
71+
const args = ['--project', TSCONFIG_PATH, '--noEmit', '--pretty', 'false'];
72+
73+
console.log(`${colors.cyan}${colors.reset}Executing: ${TSC_BIN} ${args.join(' ')}\n`);
74+
75+
const result = spawnSync(TSC_BIN, args, {
76+
encoding: 'utf8',
77+
shell: true,
78+
stdio: 'pipe',
79+
});
80+
81+
// Process doesn't throw an exception when there are type errors
82+
// Instead we need to check the exit code
83+
if (result.status === 0) {
84+
// Success - no errors
85+
console.log(
86+
`${colors.green}${colors.reset}${colors.bold}No TypeScript errors found!${colors.reset}`
87+
);
88+
return [];
89+
} else {
90+
// Parse the error output to get structured error info
91+
const errors = [];
92+
const output = result.stderr || result.stdout || '';
93+
94+
output.split('\n').forEach((line) => {
95+
const match = line.match(/(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)/);
96+
if (match) {
97+
errors.push({
98+
file: match[1],
99+
line: parseInt(match[2]),
100+
column: parseInt(match[3]),
101+
code: match[4],
102+
message: match[5],
103+
});
104+
}
105+
});
106+
107+
console.log(
108+
`${colors.blue}${colors.reset}Found ${colors.bold}${errors.length}${colors.reset} TypeScript errors in current codebase`
109+
);
110+
return errors;
111+
}
112+
}
113+
114+
// Check if an error is new or already in the baseline
115+
function isNewError(error, baselineErrors) {
116+
// An error is considered the same if it has the same file, code, line, and column
117+
// If you touch it, you should fix it!
118+
return !baselineErrors.some(
119+
(baselineError) =>
120+
baselineError.file === error.file &&
121+
baselineError.code === error.code &&
122+
baselineError.line === error.line &&
123+
baselineError.column === error.column
124+
);
125+
}
126+
127+
// Format an error for nice display
128+
function formatError(error, index) {
129+
return [
130+
` ${colors.bold}${index + 1}.${colors.reset} ${colors.red}${error.file}:${error.line}:${
131+
error.column
132+
}${colors.reset}`,
133+
` ${colors.yellow}Error ${error.code}:${colors.reset} ${error.message}`,
134+
].join('\n');
135+
}
136+
137+
// Main function
138+
async function main() {
139+
console.log('\n');
140+
141+
// Get baseline errors
142+
const baselineErrors = loadBaseline();
143+
console.log('');
144+
145+
// Get current errors
146+
const currentErrors = getCurrentErrors();
147+
console.log('');
148+
149+
// Find new errors (not in baseline)
150+
const newErrors = currentErrors.filter((error) => isNewError(error, baselineErrors));
151+
152+
if (newErrors.length === 0) {
153+
console.log(
154+
`${colors.green}${colors.reset}${colors.bold}SUCCESS:${colors.reset} No new TypeScript errors detected!`
155+
);
156+
157+
if (currentErrors.length > 0) {
158+
console.log(
159+
`${colors.blue}${colors.reset}${currentErrors.length} existing errors found (already in baseline)`
160+
);
161+
}
162+
163+
console.log(
164+
`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`
165+
);
166+
process.exit(0); // Success
167+
} else {
168+
console.error(
169+
`${colors.red}${colors.reset}${colors.bold}FAILURE:${colors.reset} Found ${newErrors.length} new TypeScript errors:\n`
170+
);
171+
172+
// Print new errors in a nice format
173+
newErrors.forEach((error, index) => {
174+
console.error(formatError(error, index));
175+
if (index < newErrors.length - 1) console.error('');
176+
});
177+
178+
console.error('\n');
179+
console.error(
180+
`${colors.red}${colors.reset}${colors.bold}Build failed due to new TypeScript errors.${colors.reset}`
181+
);
182+
console.error(
183+
`${colors.yellow}${colors.reset}To update the baseline, run the baseline update script.`
184+
);
185+
console.error(
186+
`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`
187+
);
188+
189+
process.exit(1); // Failure
190+
}
191+
}
192+
193+
main().catch((error) => {
194+
console.error(
195+
`${colors.red}${colors.reset}${colors.bold}ERROR:${colors.reset} Failed to check TypeScript errors`
196+
);
197+
console.error(` ${error.message}`);
198+
process.exit(1);
199+
});

scripts/update_ts_baseline.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/* eslint no-restricted-syntax: 0 */
7+
const { spawnSync } = require('child_process');
8+
const fs = require('fs');
9+
const path = require('path');
10+
11+
const BASELINE_FILE = path.join(process.cwd(), 'ts_error_baseline.json');
12+
const TSC_BIN = path.join(process.cwd(), 'node_modules/.bin/tsc');
13+
const TSCONFIG_PATH = path.join(process.cwd(), 'tsconfig.check.json');
14+
15+
console.log('Running TypeScript compiler to capture current errors...');
16+
17+
// Helper to run TypeCheck with tsconfig.json
18+
function runTypeCheck() {
19+
// Check if tsconfig.json exists
20+
if (!fs.existsSync(TSCONFIG_PATH)) {
21+
console.error(`Error: Could not find tsconfig.json at ${TSCONFIG_PATH}`);
22+
process.exit(1);
23+
}
24+
25+
console.log(`Using TypeScript config: ${TSCONFIG_PATH}`);
26+
27+
// Use project flag to specify tsconfig.json
28+
const args = ['--project', TSCONFIG_PATH, '--noEmit', '--pretty', 'false'];
29+
30+
// Changed stdio to 'pipe' to capture output instead of 'inherit'
31+
const result = spawnSync(TSC_BIN, args, {
32+
encoding: 'utf8',
33+
shell: true,
34+
stdio: 'pipe',
35+
});
36+
37+
// Log the output to console
38+
if (result.stdout) console.log(result.stdout);
39+
if (result.stderr) console.error(result.stderr);
40+
41+
// Process doesn't throw an exception when there are type errors
42+
// Instead we need to check the exit code
43+
if (result.status === 0) {
44+
// Success - no errors
45+
console.log('No TypeScript errors found! Creating empty baseline.');
46+
return { errors: [] };
47+
} else {
48+
// Parse the error output to get structured error info
49+
const errors = [];
50+
const output = result.stderr || result.stdout || '';
51+
52+
output.split('\n').forEach((line) => {
53+
const match = line.match(/(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)/);
54+
if (match) {
55+
errors.push({
56+
file: match[1],
57+
line: parseInt(match[2]),
58+
column: parseInt(match[3]),
59+
code: match[4],
60+
message: match[5],
61+
});
62+
}
63+
});
64+
65+
return { errors };
66+
}
67+
}
68+
69+
// Main function
70+
async function main() {
71+
// Get current errors
72+
const currentStatus = runTypeCheck();
73+
74+
// Save to baseline
75+
fs.writeFileSync(
76+
BASELINE_FILE,
77+
JSON.stringify(
78+
{
79+
errors: currentStatus.errors,
80+
updatedAt: new Date().toISOString(),
81+
tsconfig: TSCONFIG_PATH, // Add reference to which tsconfig was used
82+
},
83+
null,
84+
2
85+
)
86+
);
87+
88+
console.log(`✅ Baseline updated with ${currentStatus.errors.length} errors`);
89+
}
90+
91+
main().catch((error) => {
92+
console.error('❌ Error updating TypeScript baseline:', error);
93+
process.exit(1);
94+
});

src/plugins/saved_objects_management/public/services/types/action.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* under the License.
2929
*/
3030

31-
import { ReactNode } from '@elastic/eui/node_modules/@types/react';
31+
import { ReactNode } from 'react';
3232
import { SavedObjectsManagementRecord } from '.';
3333

3434
export abstract class SavedObjectsManagementAction {

0 commit comments

Comments
 (0)