Files
react-test/scripts/generateCodeowners.mjs
how2ice 005cf56baf
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
init project
2025-12-12 14:26:25 +09:00

350 lines
9.3 KiB
JavaScript

/* eslint-disable no-console */
import * as fs from 'fs';
import * as path from 'path';
import * as url from 'url';
const componentAreas = {
accordion: 'surfaces',
accordionactions: 'surfaces',
accordiondetails: 'surfaces',
accordionsummary: 'surfaces',
alert: 'feedback',
alerttitle: 'feedback',
appbar: 'surfaces',
aspectratio: 'dataDisplay',
autocomplete: 'inputs',
avatar: 'dataDisplay',
avatargroup: 'dataDisplay',
backdrop: 'feedback',
badge: 'dataDisplay',
bottomnavigation: 'navigation',
bottomnavigationaction: 'navigation',
box: 'layout',
breadcrumbs: 'navigation',
button: 'inputs',
buttonbase: 'inputs',
buttongroup: 'inputs',
card: 'surfaces',
cardactionarea: 'surfaces',
cardactions: 'surfaces',
cardcontent: 'surfaces',
cardcover: 'surfaces',
cardheader: 'surfaces',
cardmedia: 'surfaces',
cardoverflow: 'surfaces',
checkbox: 'inputs',
chip: 'dataDisplay',
chipdelete: 'dataDisplay',
circularprogress: 'feedback',
clickawaylistener: 'utils',
collapse: 'utils',
container: 'layout',
cssbaseline: 'utils',
dialog: 'feedback',
dialogactions: 'feedback',
dialogcontent: 'feedback',
dialogcontenttext: 'feedback',
dialogtitle: 'feedback',
divider: 'dataDisplay',
drawer: 'navigation',
fab: 'inputs',
fade: 'utils',
filledinput: 'inputs',
formcontrol: 'inputs',
formcontrollabel: 'inputs',
formgroup: 'inputs',
formhelpertext: 'inputs',
formlabel: 'inputs',
globalstyles: 'utils',
gridlegacy: 'layout',
grid: 'layout',
grow: 'utils',
hidden: 'layout',
icon: 'dataDisplay',
iconbutton: 'inputs',
imagelist: 'layout',
imagelistitem: 'layout',
imagelistitembar: 'layout',
input: 'inputs',
inputadornment: 'inputs',
inputbase: 'inputs',
inputlabel: 'inputs',
linearprogress: 'feedback',
link: 'navigation',
list: 'dataDisplay',
listbox: 'utils',
listdivider: 'dataDisplay',
listitem: 'dataDisplay',
listitemavatar: 'dataDisplay',
listitembutton: 'dataDisplay',
listitemcontent: 'dataDisplay',
listitemdecorator: 'dataDisplay',
listitemicon: 'dataDisplay',
listitemsecondaryaction: 'dataDisplay',
listitemtext: 'dataDisplay',
listsubheader: 'dataDisplay',
masonry: 'layout',
mediaquery: 'utils',
menu: 'navigation',
menuitem: 'navigation',
menulist: 'navigation',
mobilestepper: 'navigation',
modal: 'utils',
multiselect: 'inputs',
nativeselect: 'inputs',
nossr: 'utils',
option: 'inputs',
optiongroup: 'inputs',
outlinedinput: 'inputs',
pagination: 'navigation',
paginationitem: 'navigation',
paper: 'surfaces',
popover: 'utils',
popper: 'utils',
portal: 'utils',
progress: 'feedback',
radio: 'inputs',
radiogroup: 'inputs',
rating: 'inputs',
scopedcssbaseline: 'utils',
scrolltrigger: 'surfaces',
select: 'inputs',
sheet: 'surfaces',
skeleton: 'feedback',
slide: 'utils',
slider: 'inputs',
snackbar: 'feedback',
snackbarcontent: 'feedback',
speeddial: 'navigation',
speeddialaction: 'navigation',
speeddialicon: 'navigation',
stack: 'layout',
step: 'navigation',
stepbutton: 'navigation',
stepconnector: 'navigation',
stepcontent: 'navigation',
stepicon: 'navigation',
steplabel: 'navigation',
stepper: 'navigation',
svgicon: 'dataDisplay',
swipeabledrawer: 'navigation',
switch: 'inputs',
tab: 'navigation',
table: 'dataDisplay',
tablebody: 'dataDisplay',
tablecell: 'dataDisplay',
tablecontainer: 'dataDisplay',
tablefooter: 'dataDisplay',
tablehead: 'dataDisplay',
tablepagination: 'dataDisplay',
tablerow: 'dataDisplay',
tablesortlabel: 'dataDisplay',
tablist: 'navigation',
tabpanel: 'navigation',
tabs: 'navigation',
tabscrollbutton: 'navigation',
tabslist: 'navigation',
textarea: 'inputs',
textareaautosize: 'utils',
textfield: 'inputs',
timeline: 'dataDisplay',
togglebutton: 'inputs',
togglebuttongroup: 'inputs',
toolbar: 'surfaces',
tooltip: 'dataDisplay',
touchripple: 'inputs',
transferlist: 'inputs',
transitions: 'utils',
focustrap: 'utils',
treeview: 'dataDisplay',
typography: 'dataDisplay',
zoom: 'utils',
};
const areaMaintainers = {
inputs: ['michaldudak', 'mnajdova'],
dataDisplay: ['siriwatknp', 'michaldudak'],
feedback: ['siriwatknp', 'hbjORbj'],
surfaces: ['siriwatknp', 'hbjORbj'],
navigation: ['mnajdova', 'michaldudak'],
layout: ['siriwatknp', 'hbjORbj'],
utils: ['mnajdova', 'michaldudak'],
};
const packageOwners = {
base: ['michaldudak'],
joy: ['siriwatknp'],
material: ['mnajdova'],
};
const packageMaintainers = {
base: ['michaldudak', 'mnajdova'],
'icons-material': ['michaldudak', 'siriwatknp'],
joy: ['siriwatknp', 'danilo-leal'],
material: ['mnajdova', 'danilo-leal'],
system: ['mnajdova', 'siriwatknp'],
};
const additionalRules = {
'/scripts/': ['michaldudak', 'm4theushw'],
};
const thisDirectory = url.fileURLToPath(new URL('.', import.meta.url));
const buffer = [];
function write(text) {
buffer.push(text);
}
function save() {
const fileContents = [...buffer, ''].join('\n');
fs.writeFileSync(path.join(thisDirectory, '../.github/CODEOWNERS'), fileContents);
}
function findComponentArea(componentName) {
// TODO: could make it smarter to reduce the number of entries in componentAreas
// for example, "AccordionActions" could look up "Accordion"
return componentAreas[componentName];
}
function normalizeComponentName(componentName) {
// remove the "use" and "Unstable_" prefixes and "Unstyled" suffix
return componentName.replace(/^(use|Unstable_)?(.*?)(Unstyled)?$/gm, '$2').toLowerCase();
}
function normalizeDocsComponentName(componentName) {
switch (componentName) {
case 'breadcrumbs':
case 'progress':
case 'transitions':
return componentName;
case 'badges':
return 'badge';
case 'floating-action-button':
return 'fab';
case 'focus-trap':
return 'focustrap';
case 'radio-buttons':
return 'radio';
case 'tables':
return 'table';
default:
// remove the "use" and "Unstable" prefixes and remove the trailing "s" or "es" to make a singular form
return componentName
.replace(/^(use|Unstable)?(.*?)(es|s)?$/gm, '$2')
.replace(/-/g, '')
.toLowerCase();
}
}
function getCodeowners(mapping) {
return Object.entries(mapping)
.map(([directory, maintainers]) => `${directory} @${maintainers.join(' @')}`)
.join('\n');
}
function getAreaMaintainers(area, packageName) {
return Array.from(
new Set([
...areaMaintainers[area],
// Material UI package owner is not added to individual components' owners
// to reduce the number of PRs they'll get to review.
...(packageName === 'material' ? [] : packageOwners[packageName]),
]),
)
.map((name) => `@${name}`)
.join(' ');
}
function processComponents(packageName) {
const componentsDirectory = path.join(thisDirectory, `../packages/mui-${packageName}/src`);
const componentDirectories = fs.readdirSync(componentsDirectory);
const result = [];
for (const componentDirectory of componentDirectories) {
if (!fs.statSync(path.join(componentsDirectory, componentDirectory)).isDirectory()) {
continue;
}
const componentName = normalizeComponentName(componentDirectory);
const componentArea = findComponentArea(componentName);
if (componentArea) {
const maintainers = getAreaMaintainers(componentArea, packageName);
const codeowners = `/packages/mui-${packageName}/src/${componentDirectory}/ ${maintainers}`;
result.push(codeowners);
} else {
console.info(`No explicit owner defined for "${componentDirectory}" in ${packageName}.`);
}
}
return result.join('\n');
}
function processDocs(packageName) {
const docsDirectory = path.join(thisDirectory, `../docs/data/${packageName}/components`);
const componentDirectories = fs.readdirSync(docsDirectory);
const result = [];
for (const componentDirectory of componentDirectories) {
if (!fs.statSync(path.join(docsDirectory, componentDirectory)).isDirectory()) {
continue;
}
const componentName = normalizeDocsComponentName(componentDirectory);
const componentArea = findComponentArea(componentName);
if (componentArea) {
const maintainers = getAreaMaintainers(componentArea, packageName);
const codeowners = `/docs/data/${packageName}/components/${componentDirectory}/ ${maintainers}`;
result.push(codeowners);
} else {
console.info(
`No explicit owner defined for docs of "${componentDirectory}" in ${packageName}.`,
);
}
}
return result.join('\n');
}
function processPackages() {
return Object.entries(packageMaintainers)
.map(([packageName, maintainers]) => `/packages/mui-${packageName}/ @${maintainers.join(' @')}`)
.join('\n');
}
function run() {
write('# This file is auto-generated, do not modify it manually.');
write('# run `pnpm generate-codeowners` to update it.\n\n');
write(getCodeowners(additionalRules));
write('\n# Packages\n');
write(processPackages());
write('\n# Components - Material UI\n');
write(processComponents('material'));
write(processDocs('material'));
write('\n# Components - Base UI\n');
write(processComponents('base'));
write(processDocs('base'));
write('\n# Components - Joy UI\n');
write(processComponents('joy'));
write(processDocs('joy'));
save();
}
run();