Skip to content

Commit be3a14a

Browse files
committed
typescript script, tests and fix CI
1 parent fe74d8e commit be3a14a

16 files changed

Lines changed: 619 additions & 83 deletions

File tree

.github/workflows/ecosystem-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ jobs:
271271
- name: Aggregate results
272272
id: aggregate
273273
run: |
274-
MESSAGE=$(node scripts/aggregate-outcomes.js \
274+
MESSAGE=$(node --experimental-strip-types scripts/index.ts \
275275
--outcomes-dir outcomes \
276276
--reports-dir reports \
277277
--previous-reports-dir previous-reports \

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"type": "module"
2+
"type": "module",
3+
"scripts": {
4+
"test": "node --experimental-strip-types --test test/aggregate-outcomes.test.ts"
5+
}
36
}
Lines changed: 107 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
* @example
1010
* # Full usage with reports
11-
* node scripts/aggregate-outcomes.js \
11+
* node --experimental-strip-types scripts/aggregate-outcomes.ts \
1212
* --outcomes-dir ./outcomes \
1313
* --reports-dir ./reports \
1414
* --previous-reports-dir ./previous-reports \
@@ -17,7 +17,7 @@
1717
*
1818
* @example
1919
* # Using short flags
20-
* node scripts/aggregate-outcomes.js \
20+
* node --experimental-strip-types scripts/aggregate-outcomes.ts \
2121
* -o ./outcomes \
2222
* -r ./reports \
2323
* -p ./previous-reports \
@@ -26,7 +26,7 @@
2626
*
2727
* @example
2828
* # Minimal usage (no trends)
29-
* node scripts/aggregate-outcomes.js -o ./outcomes -r ./reports
29+
* node --experimental-strip-types scripts/aggregate-outcomes.ts -o ./outcomes -r ./reports
3030
*/
3131

3232
import fs from "node:fs";
@@ -35,40 +35,81 @@ import { parseArgs } from "node:util";
3535

3636
/**
3737
* Minimal outcome data from matrix jobs
38-
*
39-
* @typedef {Object} MinimalOutcome
40-
* @property {string} id - Project identifier (e.g., "ant-design")
41-
* @property {string} outcome - Test outcome: "success" | "failure" | "skipped" | other
4238
*/
39+
export interface MinimalOutcome {
40+
/** Project identifier (e.g., "ant-design") */
41+
id: string;
42+
/** Test outcome: "success" | "failure" | "skipped" | other */
43+
outcome: string;
44+
}
45+
46+
/**
47+
* Duration object from Biome JSON report
48+
*/
49+
export interface Duration {
50+
/** Seconds */
51+
secs: number;
52+
/** Nanoseconds */
53+
nanos: number;
54+
}
55+
56+
/**
57+
* Summary statistics from Biome JSON report
58+
*/
59+
export interface BiomeSummary {
60+
/** Execution duration */
61+
duration?: Duration;
62+
/** Number of errors */
63+
errors?: number;
64+
/** Number of warnings */
65+
warnings?: number;
66+
/** Number of info diagnostics */
67+
infos?: number;
68+
}
4369

4470
/**
4571
* Biome JSON report structure
46-
*
47-
* @typedef {Object} BiomeReport
48-
* @property {Object} summary - Summary statistics
49-
* @property {Object} summary.duration - Execution duration
50-
* @property {number} summary.duration.secs - Seconds
51-
* @property {number} summary.duration.nanos - Nanoseconds
52-
* @property {number} summary.errors - Number of errors
53-
* @property {number} summary.warnings - Number of warnings
54-
* @property {number} summary.infos - Number of info diagnostics
5572
*/
73+
export interface BiomeReport {
74+
/** Summary statistics */
75+
summary?: BiomeSummary;
76+
/** Error flag (set when report generation failed) */
77+
error?: boolean;
78+
}
5679

5780
/**
5881
* Full outcome data with computed trends
59-
*
60-
* @typedef {Object} OutcomeData
61-
* @property {string} id - Project identifier (e.g., "ant-design")
62-
* @property {string} tag - Status emoji with indicators:
63-
* - "✅" = success
64-
* - "❌" = failure
65-
* - "❓" = skipped/timeout/other
66-
* - "🆕" = new project (appended to base emoji)
67-
* - "📈" = more diagnostics than before (appended)
68-
* - "📉" = fewer diagnostics than before (appended)
69-
* @property {string} time - Execution time (e.g., "1.2s", "30ms", "?")
70-
* @property {string} outcome - Test outcome: "success" | "failure" | "skipped" | other
7182
*/
83+
export interface OutcomeData {
84+
/** Project identifier (e.g., "ant-design") */
85+
id: string;
86+
/**
87+
* Status emoji with indicators:
88+
* - "✅" = success
89+
* - "❌" = failure
90+
* - "❓" = skipped/timeout/other
91+
* - "🆕" = new project (appended to base emoji)
92+
* - "📈" = more diagnostics than before (appended)
93+
* - "📉" = fewer diagnostics than before (appended)
94+
* - "⚠️" = error running biome (appended)
95+
*/
96+
tag: string;
97+
/** Execution time (e.g., "1.2s", "30ms", "?") */
98+
time: string;
99+
/** Test outcome: "success" | "failure" | "skipped" | other */
100+
outcome: string;
101+
}
102+
103+
/**
104+
* Configuration from CLI arguments
105+
*/
106+
export interface Config {
107+
outcomesDir: string;
108+
reportsDir: string;
109+
previousReportsDir?: string;
110+
biomeRef?: string;
111+
runUrl?: string;
112+
}
72113

73114
/*
74115
* Example JSON data structure that the script expects to receive:
@@ -109,9 +150,9 @@ import { parseArgs } from "node:util";
109150
/**
110151
* Display usage information
111152
*/
112-
function showHelp() {
153+
export function showHelp(): void {
113154
console.info(`
114-
Usage: aggregate-outcomes.js [options]
155+
Usage: aggregate-outcomes.ts [options]
115156
116157
Aggregates Biome ecosystem CI outcome data, computes trends from JSON reports,
117158
and generates a Discord message.
@@ -126,18 +167,18 @@ Options:
126167
127168
Examples:
128169
# Full usage with trend computation
129-
node scripts/aggregate-outcomes.js \\
170+
node --experimental-strip-types scripts/aggregate-outcomes.ts \\
130171
--outcomes-dir ./outcomes \\
131172
--reports-dir ./reports \\
132173
--previous-reports-dir ./previous-reports \\
133174
--biome-ref main \\
134175
--run-url "https://github.com/biomejs/ecosystem-ci/actions/runs/123456"
135176
136177
# Using short flags
137-
node scripts/aggregate-outcomes.js -o ./outcomes -r ./reports -p ./prev -b main -u "https://..."
178+
node --experimental-strip-types scripts/aggregate-outcomes.ts -o ./outcomes -r ./reports -p ./prev -b main -u "https://..."
138179
139180
# Without trends (no previous reports)
140-
node scripts/aggregate-outcomes.js -o ./outcomes -r ./reports
181+
node --experimental-strip-types scripts/aggregate-outcomes.ts -o ./outcomes -r ./reports
141182
142183
Expected Directory Structure:
143184
outcomes/
@@ -161,9 +202,8 @@ Output:
161202

162203
/**
163204
* Parse command line arguments using Node.js util.parseArgs
164-
* @returns {{outcomesDir: string, reportsDir: string, previousReportsDir?: string, biomeRef?: string, runUrl?: string}}
165205
*/
166-
function parseCliArgs() {
206+
export function parseCliArgs(): Config {
167207
const { values } = parseArgs({
168208
options: {
169209
"outcomes-dir": {
@@ -221,11 +261,9 @@ function parseCliArgs() {
221261

222262
/**
223263
* Read all minimal outcome JSON files from the outcomes directory
224-
* @param {string} outcomesDir - Path to directory containing outcome-* subdirectories
225-
* @returns {MinimalOutcome[]}
226264
*/
227-
function readMinimalOutcomes(outcomesDir) {
228-
const outcomes = [];
265+
export function readMinimalOutcomes(outcomesDir: string): MinimalOutcome[] {
266+
const outcomes: MinimalOutcome[] = [];
229267

230268
try {
231269
const entries = fs.readdirSync(outcomesDir);
@@ -237,12 +275,15 @@ function readMinimalOutcomes(outcomesDir) {
237275

238276
if (fs.existsSync(outcomeFile)) {
239277
const content = fs.readFileSync(outcomeFile, "utf-8");
240-
const data = JSON.parse(content);
278+
const data = JSON.parse(content) as MinimalOutcome;
241279
outcomes.push(data);
242280
}
243281
}
244282
} catch (error) {
245-
console.error(`Error reading outcomes from ${outcomesDir}:`, error.message);
283+
console.error(
284+
`Error reading outcomes from ${outcomesDir}:`,
285+
(error as Error).message,
286+
);
246287
process.exit(1);
247288
}
248289

@@ -251,11 +292,11 @@ function readMinimalOutcomes(outcomesDir) {
251292

252293
/**
253294
* Read a biome JSON report file
254-
* @param {string} reportsDir - Directory containing biome-report-* subdirectories
255-
* @param {string} projectId - Project identifier
256-
* @returns {BiomeReport | null} - Parsed report or null if not found
257295
*/
258-
function readReport(reportsDir, projectId) {
296+
export function readReport(
297+
reportsDir: string,
298+
projectId: string,
299+
): BiomeReport | null {
259300
const reportPath = path.join(
260301
reportsDir,
261302
`biome-report-${projectId}`,
@@ -265,7 +306,7 @@ function readReport(reportsDir, projectId) {
265306
if (fs.existsSync(reportPath)) {
266307
try {
267308
const content = fs.readFileSync(reportPath, "utf-8");
268-
const report = JSON.parse(content);
309+
const report = JSON.parse(content) as BiomeReport;
269310

270311
// Check if this is an error placeholder
271312
if (report.error === true) {
@@ -275,12 +316,9 @@ function readReport(reportsDir, projectId) {
275316
return report;
276317
} catch (error) {
277318
console.error(
278-
`Warning: Failed to parse JSON report for ${projectId}: ${error.message}`,
319+
`Warning: Failed to parse JSON report for ${projectId}: ${(error as Error).message}`,
279320
);
280321
console.error(`Report path: ${reportPath}`);
281-
console.error(
282-
`Content preview: ${content.substring(0, 200)}...`,
283-
);
284322
return null;
285323
}
286324
}
@@ -290,10 +328,8 @@ function readReport(reportsDir, projectId) {
290328

291329
/**
292330
* Format duration from biome report
293-
* @param {Object} duration - Duration object with secs and nanos
294-
* @returns {string} - Formatted duration (e.g., "1.2s", "234ms")
295331
*/
296-
function formatDuration(duration) {
332+
export function formatDuration(duration: Duration | undefined): string {
297333
if (!duration) return "?";
298334

299335
const totalMs = duration.secs * 1000 + duration.nanos / 1000000;
@@ -307,10 +343,8 @@ function formatDuration(duration) {
307343

308344
/**
309345
* Get total diagnostic count from report
310-
* @param {BiomeReport} report - Biome report
311-
* @returns {number} - Total number of diagnostics
312346
*/
313-
function getTotalDiagnostics(report) {
347+
export function getTotalDiagnostics(report: BiomeReport | null): number {
314348
if (!report || !report.summary) return 0;
315349
return (
316350
(report.summary.errors || 0) +
@@ -321,11 +355,11 @@ function getTotalDiagnostics(report) {
321355

322356
/**
323357
* Compare two reports and determine trend indicator
324-
* @param {BiomeReport | null} previousReport - Previous report
325-
* @param {BiomeReport | null} currentReport - Current report
326-
* @returns {string} - Trend indicator: "🆕" | "📈" | "📉" | "⚠️" | ""
327358
*/
328-
function computeTrend(previousReport, currentReport) {
359+
export function computeTrend(
360+
previousReport: BiomeReport | null,
361+
currentReport: BiomeReport | null,
362+
): string {
329363
// Error in current run
330364
if (currentReport?.error) {
331365
return "⚠️";
@@ -354,10 +388,8 @@ function computeTrend(previousReport, currentReport) {
354388

355389
/**
356390
* Compute base tag from test outcome
357-
* @param {string} outcome - Test outcome: "success" | "failure" | other
358-
* @returns {string} - Base tag emoji
359391
*/
360-
function computeBaseTag(outcome) {
392+
export function computeBaseTag(outcome: string): string {
361393
if (outcome === "success") {
362394
return "✅";
363395
}
@@ -369,16 +401,12 @@ function computeBaseTag(outcome) {
369401

370402
/**
371403
* Compute full outcome data with trends
372-
* @param {MinimalOutcome} minimalOutcome - Minimal outcome from matrix job
373-
* @param {string} reportsDir - Directory with current reports
374-
* @param {string | undefined} previousReportsDir - Directory with previous reports (optional)
375-
* @returns {OutcomeData} - Full outcome with computed tag, time, and trends
376404
*/
377-
function computeFullOutcome(
378-
minimalOutcome,
379-
reportsDir,
380-
previousReportsDir,
381-
) {
405+
export function computeFullOutcome(
406+
minimalOutcome: MinimalOutcome,
407+
reportsDir: string,
408+
previousReportsDir?: string,
409+
): OutcomeData {
382410
const { id, outcome } = minimalOutcome;
383411

384412
// Read reports
@@ -409,12 +437,12 @@ function computeFullOutcome(
409437

410438
/**
411439
* Aggregate outcomes and generate Discord message
412-
* @param {OutcomeData[]} outcomes - Array of outcome data
413-
* @param {string | undefined} biomeRef - Biome reference (branch/tag), optional
414-
* @param {string | undefined} runUrl - GitHub Actions run URL, optional
415-
* @returns {string} - Formatted Discord message
416440
*/
417-
function aggregateResults(outcomes, biomeRef, runUrl) {
441+
export function aggregateResults(
442+
outcomes: OutcomeData[],
443+
biomeRef?: string,
444+
runUrl?: string,
445+
): string {
418446
// Sort outcomes by id for consistent ordering
419447
outcomes.sort((a, b) => a.id.localeCompare(b.id));
420448

@@ -439,7 +467,7 @@ function aggregateResults(outcomes, biomeRef, runUrl) {
439467
const total = outcomes.length;
440468

441469
// Build the message parts
442-
const messageParts = [];
470+
const messageParts: string[] = [];
443471

444472
// Add header with optional biome ref
445473
if (biomeRef) {
@@ -468,7 +496,7 @@ function aggregateResults(outcomes, biomeRef, runUrl) {
468496
/**
469497
* Main function
470498
*/
471-
function main() {
499+
export function main(): void {
472500
const config = parseCliArgs();
473501

474502
// Read minimal outcomes
@@ -496,5 +524,3 @@ function main() {
496524
);
497525
console.info(message);
498526
}
499-
500-
main();

scripts/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { main } from "./aggregate-outcomes.ts";
2+
3+
main();

0 commit comments

Comments
 (0)