Chore: Remove betterer (#110469)

This commit is contained in:
Tom Ratcliffe
2025-09-04 18:17:53 +01:00
committed by GitHub
parent ea7c370edd
commit 3d6d632686
14 changed files with 13 additions and 604 deletions
-7
View File
@@ -1,7 +0,0 @@
Hi <%= owner %>!
The following files have been marked as having issues regarding `<%= issueFilter %>: <%= issueMessageFilter %>`.
There are <%= totalIssueCount %> <%= plural('issue', totalIssueCount) %> over <%= fileCount %> <%= plural('file', fileCount) %>:
<% files.forEach((file) => { %>
- [ ] <%= file.issueCount %> <%= plural('issue', file.issueCount) %> in [<%= file.fileName %>](https://github.com/grafana/grafana/blob/main/<%= file.fileName %>) <% }) %>
-109
View File
@@ -1,109 +0,0 @@
#!/bin/bash
# Script to create GitHub issues from text files in a directory
# Usage: ./create_issues_from_files.sh <directory> <owner/repo>
# FYI: Generated almost entirely with warp.dev as a quick hack for creating issues from betterer issues output
set -e
# Function to display usage
usage() {
echo "Usage: $0 <directory> <owner/repo>"
echo ""
echo "Arguments:"
echo " directory Directory containing text files"
echo " owner/repo GitHub repository in format 'owner/repository'"
echo ""
echo "Examples:"
echo " $0 ./issues myuser/myrepo"
echo " $0 /path/to/issues organization/project"
exit 1
}
# Check if correct number of arguments provided
if [ $# -ne 2 ]; then
echo "Error: Incorrect number of arguments"
usage
fi
DIRECTORY="$1"
REPO="$2"
# Validate directory exists
if [ ! -d "$DIRECTORY" ]; then
echo "Error: Directory '$DIRECTORY' does not exist"
exit 1
fi
# Validate repository format
if [[ ! "$REPO" =~ ^[^/]+/[^/]+$ ]]; then
echo "Error: Repository must be in format 'owner/repository'"
exit 1
fi
echo "Creating issues from text files in '$DIRECTORY' for repository '$REPO'"
echo
# Counter for created issues
created_count=0
error_count=0
# Process each text file in the directory
for file in "$DIRECTORY"/*.txt; do
# Check if glob matched any files
[ -e "$file" ] || continue
filename=$(basename "$file")
echo "Processing: $filename"
# Read file content
if [ ! -r "$file" ]; then
echo " ❌ Error: Cannot read file '$file'"
((error_count++))
continue
fi
content=$(cat "$file")
# Skip empty files
if [ -z "$content" ]; then
echo " ⚠️ Skipping empty file"
continue
fi
# Parse codeowner from first line (assuming format like "@username" or "@team/name")
first_line=$(echo "$content" | head -n 1)
codeowner=""
# Extract codeowner if first line contains @username or @team/name pattern
if [[ "$first_line" =~ @[a-zA-Z0-9_/-]+ ]]; then
codeowner=$(echo "$first_line" | grep -o '@[a-zA-Z0-9_/-]*' | head -n 1)
# Strip @grafana/ prefix if present
codeowner=${codeowner#@grafana/}
fi
title="Betterer: React Hooks ($codeowner)"
# Create the issue using GitHub CLI
if gh issue create \
--repo "$REPO" \
--title "$title" \
--body "$content" > /dev/null 2>&1; then
echo " ✅ Created issue: $title"
((created_count++))
else
echo " ❌ Failed to create issue: $title"
((error_count++))
fi
done
echo
echo "Summary:"
echo " Issues created: $created_count"
echo " Errors: $error_count"
if [ $created_count -eq 0 ] && [ $error_count -eq 0 ]; then
echo " No .txt files found in '$DIRECTORY'"
fi
-148
View File
@@ -1,148 +0,0 @@
import { betterer, BettererFileIssues } from '@betterer/betterer';
import Codeowners from 'codeowners';
import { readFile, writeFile } from 'fs/promises';
import { template } from 'lodash';
import path from 'path';
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs/yargs';
const argv = yargs(hideBin(process.argv))
.option('template', {
demandOption: true,
alias: 't',
describe: 'Path to a template to use for each issue. See source bettererIssueTemplate.md for an example',
type: 'string',
default: './scripts/cli/bettererIssueTemplate.md',
})
.option('output', {
demandOption: true,
alias: 'o',
describe: 'Path to directory to save issues to',
type: 'string',
})
.option('test', {
demandOption: true,
alias: 'b',
describe: 'Name of the betterer test to produce the report for',
type: 'string',
})
.option('test-message', {
alias: 'm',
describe: 'Filter issues containing this message',
type: 'string',
})
.option('single-owner', {
type: 'boolean',
alias: 's',
describe: 'Only use first owner for files with multiple owners',
default: false,
})
.usage('Usage: yarn betterer:issues -t [path] -o [path] -b [string]')
.version(false)
.help('help').argv;
interface FileDetails {
fileName: string;
issueCount: number;
issues: BettererFileIssues;
}
// really dumb and simple pluralize function. not meant to be exhaustive
function plural(word: string, count: number) {
if (count === 0 || count > 1) {
return word + 's';
}
return word;
}
async function main() {
const args = await argv;
const templatePath = path.resolve(args.template);
const outputPath = path.resolve(args.output);
const templateString = (await readFile(templatePath)).toString();
const owners = new Codeowners();
const results = await betterer.results();
const filesByOwner: Record<string, FileDetails[]> = {};
for (const testResults of results.resultSummaries) {
if (testResults.name !== args.test) {
continue;
}
if (typeof testResults.details === 'string') {
continue;
}
for (const _fileName in testResults.details) {
const fileName = _fileName.replace(process.cwd() + '/', '');
const _details = testResults.details[_fileName];
let ownersForFile = owners.getOwner(fileName);
if (args.singleOwner) {
ownersForFile = [ownersForFile[0]];
}
const filterByMessage = args.testMessage?.length ? args.testMessage.toLowerCase() : undefined;
const filteredDetails = filterByMessage
? _details.filter((v) => v.message.toLowerCase().includes(filterByMessage))
: _details;
const numberOfIssues = filteredDetails.length;
if (numberOfIssues === 0) {
continue;
}
for (const owner of ownersForFile) {
if (!filesByOwner[owner]) {
filesByOwner[owner] = [];
}
filesByOwner[owner].push({
fileName,
issueCount: numberOfIssues,
issues: filteredDetails,
});
}
}
}
const contexts = Object.entries(filesByOwner).map(([owner, files]) => {
const fileCount = files.length;
const totalIssueCount = files.reduce((acc, v) => acc + v.issueCount, 0);
return {
owner,
files,
fileCount,
totalIssueCount,
issueFilter: args.test,
issueMessageFilter: args.testMessage,
};
});
const compiledTemplate = template(templateString, { imports: { plural } });
for (const context of contexts) {
const fileSafeOwner = context.owner.replace(/[^a-z0-9-]/gi, '_');
const fileSafeTestName = args.test.replace(/[^a-z0-9-]/gi, '_');
const outputFilePath = path.join(outputPath, `${fileSafeTestName}_${fileSafeOwner}.txt`);
const printed = compiledTemplate(context);
await writeFile(outputFilePath, printed);
const indented = printed
.split('\n')
.map((v) => `\t${v}`)
.join('\n');
console.log(`Printed issue for owner`, context.owner, 'to', outputFilePath);
console.log(indented);
}
}
main().catch(console.error);
-51
View File
@@ -1,51 +0,0 @@
import { betterer } from '@betterer/betterer';
import { camelCase } from 'lodash';
function logStat(name: string, value: number) {
// Note that this output format must match the parsing in ci-frontend-metrics.sh
// which expects the two values to be separated by a space
console.log(`${name} ${value}`);
}
/**
* Array of regexes + name overrides for legacy checks that have been moved to ESLint
*
* This is so we can still report things like "gfFormUsage..." as "noGfFormUsage_gfFormUsage..."
* rather than "betterEslint_gfFormUsage..." for continuity on our dashboards
*/
const legacyChecksToTransform = [
{ messageRegex: /gfFormUsage/i, prefix: 'noGfFormUsage' },
{ messageRegex: /noUndocumentedStories/i, prefix: 'noUndocumentedStories' },
{ messageRegex: /noSkippingOfA11YTests/i, prefix: 'noSkippingA11YTestsInStories' },
];
async function main() {
const results = await betterer.results();
for (const testResults of results.resultSummaries) {
const countByMessage = {};
const name = camelCase(testResults.name);
Object.values(testResults.details)
.flatMap((v) => v)
.forEach((detail) => {
const message = camelCase(detail.message);
const nameToUse =
legacyChecksToTransform.find((v) => {
return v.messageRegex.test(message);
})?.prefix || name;
const metricName = `${nameToUse}_${message}`;
if (metricName in countByMessage) {
countByMessage[metricName]++;
} else {
countByMessage[metricName] = 1;
}
});
for (const [metricName, count] of Object.entries<number>(countByMessage)) {
logStat(metricName, count);
}
}
}
main().catch(console.error);