Skip to content

Commit 54aff99

Browse files
gingerbenwCopilot
andcommitted
update attw check script
Co-authored-by: Copilot <copilot@github.com>
1 parent 516711f commit 54aff99

2 files changed

Lines changed: 231 additions & 56 deletions

File tree

.github/workflows/attw-check.yml

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -36,69 +36,25 @@ jobs:
3636
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
3737
with:
3838
script: |
39-
const fs = require('fs');
39+
const { execSync } = require('child_process');
4040
41-
// Read and parse the results file
42-
let results;
41+
// Run the parse script to get markdown output
42+
let commentBody;
4343
try {
44-
const content = fs.readFileSync('attw-results.json', 'utf8');
45-
// The file contains multiple JSON objects, one per line
46-
const lines = content.trim().split('\n').filter(line => line.trim());
47-
results = lines.map(line => JSON.parse(line));
44+
commentBody = execSync('node scripts/parse-attw-results.js --format=markdown', {
45+
encoding: 'utf8',
46+
stdio: ['pipe', 'pipe', 'pipe']
47+
});
4848
} catch (error) {
49-
console.log('Error reading attw-results.json:', error.message);
50-
return;
49+
// Script exits with code 1 if there are problems, but we still want the output
50+
commentBody = error.stdout || error.message;
5151
}
5252
53-
// Filter for packages with problems
54-
const packagesWithProblems = results.filter(result => {
55-
return result.problems && Object.keys(result.problems).length > 0;
56-
});
57-
58-
// Build comment body
59-
let commentBody = '## 📦 Are The Types Wrong? Report\n\n';
60-
61-
if (packagesWithProblems.length === 0) {
62-
commentBody += '✅ All packages passed the type check!\n';
63-
} else {
64-
commentBody += `❌ Found issues in ${packagesWithProblems.length} package(s):\n\n`;
65-
66-
for (const result of packagesWithProblems) {
67-
const packageName = result.analysis.packageName;
68-
const problemCount = Object.keys(result.problems).length;
69-
70-
commentBody += `### \`${packageName}\`\n\n`;
71-
commentBody += `**${problemCount} problem(s) detected:**\n\n`;
72-
73-
// Group problems by kind
74-
const problemsByKind = {};
75-
for (const [entrypoint, issues] of Object.entries(result.problems)) {
76-
for (const issue of issues) {
77-
const kind = issue.kind;
78-
if (!problemsByKind[kind]) {
79-
problemsByKind[kind] = [];
80-
}
81-
problemsByKind[kind].push({ entrypoint, issue });
82-
}
83-
}
84-
85-
// Display problems by kind
86-
for (const [kind, problems] of Object.entries(problemsByKind)) {
87-
commentBody += `**${kind}** (${problems.length} occurrence(s)):\n`;
88-
for (const { entrypoint, issue } of problems.slice(0, 5)) {
89-
commentBody += `- \`${entrypoint}\`: ${issue.message || 'No message'}\n`;
90-
}
91-
if (problems.length > 5) {
92-
commentBody += `- ... and ${problems.length - 5} more\n`;
93-
}
94-
commentBody += '\n';
95-
}
96-
}
53+
if (!commentBody || !commentBody.includes('Are The Types Wrong? Report')) {
54+
console.log('No valid output from parse script');
55+
return;
9756
}
9857
99-
commentBody += '\n---\n';
100-
commentBody += '*This check helps ensure TypeScript types are correctly exported and work across different module systems.*\n';
101-
10258
// Post or update comment
10359
const { data: comments } = await github.rest.issues.listComments({
10460
owner: context.repo.owner,

scripts/parse-attw-results.js

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
6+
/**
7+
* Parse ATTW results and generate a report
8+
* Usage: node scripts/parse-attw-results.js [--format=markdown|json|text]
9+
*/
10+
11+
function parseResults(filePath) {
12+
const content = fs.readFileSync(filePath, 'utf8');
13+
const lines = content.split('\n');
14+
const results = [];
15+
16+
let currentJson = '';
17+
let braceCount = 0;
18+
let inJson = false;
19+
20+
for (const line of lines) {
21+
// Skip empty lines and lines that don't start a JSON object if we're not already in one
22+
if (!inJson && !line.trim().startsWith('{')) {
23+
continue;
24+
}
25+
26+
// Start tracking a new JSON object
27+
if (line.trim().startsWith('{') && braceCount === 0) {
28+
inJson = true;
29+
currentJson = '';
30+
braceCount = 0;
31+
}
32+
33+
if (inJson) {
34+
currentJson += line + '\n';
35+
36+
// Count braces to know when we've completed a JSON object
37+
for (const char of line) {
38+
if (char === '{') braceCount++;
39+
if (char === '}') braceCount--;
40+
}
41+
42+
// When braceCount returns to 0, we have a complete JSON object
43+
if (braceCount === 0) {
44+
try {
45+
const parsed = JSON.parse(currentJson);
46+
results.push(parsed);
47+
} catch (err) {
48+
console.error('Error parsing JSON object:', err.message);
49+
}
50+
inJson = false;
51+
currentJson = '';
52+
}
53+
}
54+
}
55+
56+
return results;
57+
}
58+
59+
function analyzeResults(results) {
60+
const packagesWithProblems = results.filter(result => {
61+
return result.problems && Object.keys(result.problems).length > 0;
62+
});
63+
64+
const analysis = {
65+
totalPackages: results.length,
66+
packagesWithProblems: packagesWithProblems.length,
67+
packagesWithoutProblems: results.length - packagesWithProblems.length,
68+
details: []
69+
};
70+
71+
for (const result of packagesWithProblems) {
72+
const packageName = result.analysis.packageName;
73+
const problemCount = Object.keys(result.problems).length;
74+
75+
// Group problems by kind
76+
const problemsByKind = {};
77+
for (const [entrypoint, issues] of Object.entries(result.problems)) {
78+
for (const issue of issues) {
79+
const kind = issue.kind;
80+
if (!problemsByKind[kind]) {
81+
problemsByKind[kind] = [];
82+
}
83+
// Extract useful info from the issue
84+
const issueInfo = {
85+
entrypoint,
86+
fileName: issue.fileName,
87+
moduleSpecifier: issue.moduleSpecifier,
88+
resolutionOption: issue.resolutionOption,
89+
// Get the last line of trace which often has the key error message
90+
traceMessage: issue.trace && issue.trace.length > 0
91+
? issue.trace[issue.trace.length - 1]
92+
: null
93+
};
94+
problemsByKind[kind].push(issueInfo);
95+
}
96+
}
97+
98+
analysis.details.push({
99+
packageName,
100+
problemCount,
101+
problemsByKind
102+
});
103+
}
104+
105+
return analysis;
106+
}
107+
108+
function formatAsText(analysis) {
109+
let output = '\n=== Are The Types Wrong? Report ===\n\n';
110+
111+
output += `Total packages checked: ${analysis.totalPackages}\n`;
112+
output += `Packages with problems: ${analysis.packagesWithProblems}\n`;
113+
output += `Packages without problems: ${analysis.packagesWithoutProblems}\n\n`;
114+
115+
if (analysis.packagesWithProblems === 0) {
116+
output += '✅ All packages passed the type check!\n';
117+
} else {
118+
output += `❌ Found issues in ${analysis.packagesWithProblems} package(s):\n\n`;
119+
120+
for (const pkg of analysis.details) {
121+
output += `📦 ${pkg.packageName}\n`;
122+
output += ` ${pkg.problemCount} problem(s) detected\n\n`;
123+
124+
for (const [kind, problems] of Object.entries(pkg.problemsByKind)) {
125+
output += ` ${kind} (${problems.length} occurrence(s)):\n`;
126+
for (const issue of problems.slice(0, 3)) {
127+
if (issue.moduleSpecifier) {
128+
output += ` - ${issue.moduleSpecifier} (in ${issue.fileName?.split('/').pop() || 'unknown'})\n`;
129+
} else {
130+
output += ` - ${issue.entrypoint}\n`;
131+
}
132+
if (issue.traceMessage) {
133+
output += ` ${issue.traceMessage}\n`;
134+
}
135+
}
136+
if (problems.length > 3) {
137+
output += ` - ... and ${problems.length - 3} more\n`;
138+
}
139+
output += '\n';
140+
}
141+
}
142+
}
143+
144+
return output;
145+
}
146+
147+
function formatAsMarkdown(analysis) {
148+
let output = '## 📦 Are The Types Wrong? Report\n\n';
149+
150+
output += `**Total packages checked:** ${analysis.totalPackages} \n`;
151+
output += `**Packages with problems:** ${analysis.packagesWithProblems} \n`;
152+
output += `**Packages without problems:** ${analysis.packagesWithoutProblems}\n\n`;
153+
154+
if (analysis.packagesWithProblems === 0) {
155+
output += '✅ All packages passed the type check!\n';
156+
} else {
157+
output += `❌ Found issues in ${analysis.packagesWithProblems} package(s):\n\n`;
158+
159+
for (const pkg of analysis.details) {
160+
output += `### \`${pkg.packageName}\`\n\n`;
161+
output += `**${pkg.problemCount} problem(s) detected:**\n\n`;
162+
163+
for (const [kind, problems] of Object.entries(pkg.problemsByKind)) {
164+
output += `**${kind}** (${problems.length} occurrence(s)):\n`;
165+
for (const issue of problems.slice(0, 3)) {
166+
if (issue.moduleSpecifier) {
167+
output += `- \`${issue.moduleSpecifier}\` in \`${issue.fileName?.split('/').pop() || 'unknown'}\`\n`;
168+
} else {
169+
output += `- \`${issue.entrypoint}\`\n`;
170+
}
171+
}
172+
if (problems.length > 3) {
173+
output += `- ... and ${problems.length - 3} more\n`;
174+
}
175+
output += '\n';
176+
}
177+
}
178+
}
179+
180+
output += '\n---\n';
181+
output += '*This check helps ensure TypeScript types are correctly exported and work across different module systems.*\n';
182+
183+
return output;
184+
}
185+
186+
function main() {
187+
const args = process.argv.slice(2);
188+
const formatArg = args.find(arg => arg.startsWith('--format='));
189+
const format = formatArg ? formatArg.split('=')[1] : 'text';
190+
191+
const resultsPath = path.join(process.cwd(), 'attw-results.json');
192+
193+
if (!fs.existsSync(resultsPath)) {
194+
console.error('❌ Error: attw-results.json not found');
195+
console.error('Run "npm run test:attw" first to generate the results file');
196+
process.exit(1);
197+
}
198+
199+
try {
200+
const results = parseResults(resultsPath);
201+
const analysis = analyzeResults(results);
202+
203+
if (format === 'json') {
204+
console.log(JSON.stringify(analysis, null, 2));
205+
} else if (format === 'markdown') {
206+
console.log(formatAsMarkdown(analysis));
207+
} else {
208+
console.log(formatAsText(analysis));
209+
}
210+
211+
// Exit with error code if there are problems
212+
process.exit(analysis.packagesWithProblems > 0 ? 1 : 0);
213+
} catch (error) {
214+
console.error('❌ Error parsing results:', error.message);
215+
process.exit(1);
216+
}
217+
}
218+
219+
main();

0 commit comments

Comments
 (0)