Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions components/Icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1253,3 +1253,23 @@ export function SecurityIcon({ style }: IconProps) {
</Svg>
);
}

export function LintToolsIcon({ style }: IconProps) {
return (
<Svg
viewBox="0 0 256 256"
style={[tw`size-5`, style]}
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="16">
<line x1="128" y1="128" x2="216" y2="128" fill="none" stroke="currentColor" />
<line x1="128" y1="64" x2="216" y2="64" fill="none" stroke="currentColor" />
<line x1="128" y1="192" x2="216" y2="192" fill="none" stroke="currentColor" />
<polyline points="40 64 56 80 88 48" fill="none" stroke="currentColor" strokeWidth="20" />
<polyline points="40 128 56 144 88 112" fill="none" stroke="currentColor" strokeWidth="20" />
<polyline points="40 192 56 208 88 176" fill="none" stroke="currentColor" strokeWidth="20" />
</Svg>
);
}
15 changes: 13 additions & 2 deletions components/Library/MetaData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ForkIcon,
IssueIcon,
LicenseIcon,
LintToolsIcon,
ModuleIcon,
NativeCodeIcon,
NightlyTestIcon,
Expand All @@ -25,7 +26,7 @@ import { ConfigPluginContent } from '~/components/Library/ConfigPlugin';
import { Tooltip } from '~/components/Tooltip';
import { type LibraryDataEntryType, type LibraryType, type MetadataEntryType } from '~/types';
import { formatBytes } from '~/util/formatBytes';
import { formatPackageManager, pluralize } from '~/util/strings';
import { formatLintTools, formatPackageManager, pluralize } from '~/util/strings';
import tw from '~/util/tailwind';

import { DirectoryScore } from './DirectoryScore';
Expand Down Expand Up @@ -266,6 +267,16 @@ function generateSecondaryData(library: LibraryType, skipExamples: boolean): Met
tooltip: 'Package manager',
}
: null,
skipExamples && (github?.lintTools?.length ?? 0) > 0
? {
id: 'lintTools',
icon: <LintToolsIcon style={iconColor} />,
content: (
<P style={paragraphStyles}>{github.lintTools?.map(formatLintTools).join(', ')}</P>
),
tooltip: 'Lint tools',
}
: null,
];
}

Expand All @@ -291,7 +302,7 @@ function MetaData({ library, secondary, skipExamples = false }: Props) {
);

return tooltip ? (
<Tooltip key={id} sideOffset={-2} delayDuration={250} trigger={component}>
<Tooltip key={id} sideOffset={-2} side="left" delayDuration={250} trigger={component}>
{tooltip}
</Tooltip>
) : (
Expand Down
2 changes: 2 additions & 0 deletions scripts/fetch-github-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type LibraryLicenseType, type LibraryType } from '~/types';
import detectModuleType from '~/util/github/detectModuleType';
import hasConfigPlugin from '~/util/github/hasConfigPlugin';
import {
detectLintStack,
detectPackageManager,
hasCCFile,
hasChangelogFile,
Expand Down Expand Up @@ -252,5 +253,6 @@ function createRepoDataWithResponse(json: any, monorepo: boolean): LibraryType['
json.packageManager ??
detectPackageManager(json.files) ??
detectPackageManager(json.rootFiles),
lintTools: detectLintStack(json.files) ?? detectLintStack(json.rootFiles),
};
}
1 change: 1 addition & 0 deletions types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export type LibraryType = LibraryDataEntryType & {
configPlugin?: boolean;
moduleType?: 'expo' | 'nitro' | 'turbo';
packageManager?: string;
lintTools?: string[];
urls: {
repo: string;
homepage?: string | null;
Expand Down
95 changes: 80 additions & 15 deletions util/github/hasFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,131 @@ import { type RepositoryTreeNode } from '~/types';

type RootFiles = { entries: RepositoryTreeNode[] } | null;

function hasMatchingFiles(rootFiles: RootFiles, fileNames: string[]) {
function hasMatchingFiles(rootFiles: RootFiles, fileNames: Set<string>) {
if (!rootFiles?.entries.length) {
return false;
}

return rootFiles.entries.some(
node => fileNames.includes(node.name.toLowerCase()) && node.type === 'blob'
node => fileNames.has(node.name.toLowerCase()) && node.type === 'blob'
);
}

const README_FILE_NAMES = ['readme', 'readme.md', 'readme.mdx'];
const README_FILE_NAMES = new Set(['readme', 'readme.md', 'readme.mdx']);

export function hasReadmeFile(rootFiles: RootFiles) {
return hasMatchingFiles(rootFiles, README_FILE_NAMES);
}

const CONTRIBUTING_FILE_NAMES = ['contributing.md'];
const CONTRIBUTING_FILE_NAMES = new Set(['contributing.md']);

export function hasContributingFile(rootFiles: RootFiles) {
return hasMatchingFiles(rootFiles, CONTRIBUTING_FILE_NAMES);
}

const CHANGELOG_FILE_NAMES = ['changelog.md'];
const CHANGELOG_FILE_NAMES = new Set(['changelog.md']);

export function hasChangelogFile(rootFiles: RootFiles) {
return hasMatchingFiles(rootFiles, CHANGELOG_FILE_NAMES);
}

const CC_FILE_NAMES = ['code_of_conduct.md'];
const CC_FILE_NAMES = new Set(['code_of_conduct.md']);

export function hasCCFile(rootFiles: RootFiles) {
return hasMatchingFiles(rootFiles, CC_FILE_NAMES);
}

const SECURITY_FILE_NAMES = ['security.md'];
const SECURITY_FILE_NAMES = new Set(['security.md']);

export function hasSecurityFile(rootFiles: RootFiles) {
return hasMatchingFiles(rootFiles, SECURITY_FILE_NAMES);
}

export function detectPackageManager(rootFiles: RootFiles) {
// LOCKS
if (hasMatchingFiles(rootFiles, ['bun.lock', 'bun.lockb'])) {
if (hasMatchingFiles(rootFiles, new Set(['bun.lock', 'bun.lockb']))) {
return 'bun';
} else if (hasMatchingFiles(rootFiles, ['pnpm-lock.yaml', 'shrinkwrap.yaml'])) {
} else if (hasMatchingFiles(rootFiles, new Set(['pnpm-lock.yaml', 'shrinkwrap.yaml']))) {
return 'pnpm';
} else if (hasMatchingFiles(rootFiles, ['yarn.lock'])) {
} else if (hasMatchingFiles(rootFiles, new Set(['yarn.lock']))) {
return 'yarn';
} else if (hasMatchingFiles(rootFiles, ['package-lock.json', 'shrinkwrap.json'])) {
} else if (hasMatchingFiles(rootFiles, new Set(['package-lock.json', 'shrinkwrap.json']))) {
return 'npm';
}
// CONFIGS
if (hasMatchingFiles(rootFiles, ['bunfig.toml'])) {
if (hasMatchingFiles(rootFiles, new Set(['bunfig.toml']))) {
return 'bun';
} else if (hasMatchingFiles(rootFiles, ['.pnpm-workspace.yaml', '.pnpmfile.cjs'])) {
} else if (hasMatchingFiles(rootFiles, new Set(['.pnpm-workspace.yaml', '.pnpmfile.cjs']))) {
return 'pnpm';
} else if (hasMatchingFiles(rootFiles, ['.yarnrc'])) {
} else if (hasMatchingFiles(rootFiles, new Set(['.yarnrc']))) {
return 'yarn@1';
} else if (hasMatchingFiles(rootFiles, ['.yarnrc.yml'])) {
} else if (hasMatchingFiles(rootFiles, new Set(['.yarnrc.yml']))) {
return 'yarn@2+';
}
return undefined;
}

const OXLINT_CONFIG_FILE_NAMES = new Set([
'.oxlintrc.json',
'.oxlintrc.jsonc',
'oxlint.config.mts',
'oxlint.config.ts',
'oxlint.config.mjs',
'oxlint.config.js',
]);
const OXFMT_CONFIG_FILE_NAMES = new Set([
'.oxfmtrc.json',
'.oxfmtrc.jsonc',
'oxfmt.config.mts',
'oxfmt.config.ts',
'oxfmt.config.mjs',
'oxfmt.config.js',
]);
const ESLINT_CONFIG_FILE_NAMES = new Set([
'eslint.config.js',
'eslint.config.mjs',
'eslint.config.cjs',
'eslint.config.ts',
'eslint.config.mjs',
'eslint.config.cjs',
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.json',
'.eslintrc.yaml',
'.eslintrc.yml',
]);
const PRETTIER_CONFIG_FILE_NAMES = new Set([
'.prettierrc',
'.prettierrc.json',
'.prettierrc.json5',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.js',
'.prettierrc.cjs',
'.prettierrc.toml',
'.prettierrc.mjs',
'prettier.config.js',
'prettier.config.cjs',
'prettier.config.mjs',
]);
const BIOME_CONFIG_FILE_NAMES = new Set(['biome.json', 'biome.jsonc']);

export function detectLintStack(rootFiles: RootFiles) {
const lintTools = [];
if (hasMatchingFiles(rootFiles, OXLINT_CONFIG_FILE_NAMES)) {
lintTools.push('oxlint');
}
if (hasMatchingFiles(rootFiles, OXFMT_CONFIG_FILE_NAMES)) {
lintTools.push('oxfmt');
}
if (hasMatchingFiles(rootFiles, ESLINT_CONFIG_FILE_NAMES)) {
lintTools.push('eslint');
}
if (hasMatchingFiles(rootFiles, PRETTIER_CONFIG_FILE_NAMES)) {
lintTools.push('prettier');
}
if (hasMatchingFiles(rootFiles, BIOME_CONFIG_FILE_NAMES)) {
lintTools.push('biome');
}
return lintTools;
}
16 changes: 16 additions & 0 deletions util/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ export function formatPackageManager(pmRaw?: string) {
}
}

export function formatLintTools(lintTool: string) {
if (lintTool === 'oxlint') {
return 'Oxlint';
} else if (lintTool === 'oxfmt') {
return 'Oxfmt';
} else if (lintTool === 'eslint') {
return 'ESLint';
} else if (lintTool === 'prettier') {
return 'Prettier';
} else if (lintTool === 'biome') {
return 'Biome';
} else {
return lintTool;
}
}

export function childrenToText(children: ReactNode): string {
return Children.toArray(children)
.map(child => {
Expand Down