diff --git a/packages/assets/build/generate-exports.ts b/packages/assets/build/generate-exports.ts index b4858c251..5b56d4366 100644 --- a/packages/assets/build/generate-exports.ts +++ b/packages/assets/build/generate-exports.ts @@ -1,3 +1,4 @@ +import { compareImportSources } from '@modrinth/tooling-config/script-utils/import-sort' import fs from 'fs' import path from 'path' @@ -22,15 +23,10 @@ function generateIconExports(): { imports: string; exports: string } { throw new Error(`Icons directory not found: ${iconsDir}`) } - const files = fs - .readdirSync(iconsDir) - .filter((file) => file.endsWith('.svg')) - .sort() + const files = fs.readdirSync(iconsDir).filter((file) => file.endsWith('.svg')) - let imports = '' - let exports = '' - - files.forEach((file) => { + // Build icon data with import paths + const icons = files.map((file) => { const baseName = path.basename(file, '.svg') let pascalName = toPascalCase(baseName) @@ -42,9 +38,21 @@ function generateIconExports(): { imports: string; exports: string } { pascalName += 'Icon' } - const privateName = `_${pascalName}` + return { + importPath: `./icons/${file}?component`, + pascalName, + privateName: `_${pascalName}`, + } + }) - imports += `import ${privateName} from './icons/${file}?component'\n` + // Sort by import path using simple-import-sort's algorithm + icons.sort((a, b) => compareImportSources(a.importPath, b.importPath)) + + let imports = '' + let exports = '' + + icons.forEach(({ importPath, pascalName, privateName }) => { + imports += `import ${privateName} from '${importPath}'\n` exports += `export const ${pascalName} = ${privateName}\n` }) diff --git a/packages/assets/generated-icons.ts b/packages/assets/generated-icons.ts index 7d5df72c0..4b77cf76e 100644 --- a/packages/assets/generated-icons.ts +++ b/packages/assets/generated-icons.ts @@ -232,26 +232,26 @@ export const AlignLeftIcon = _AlignLeftIcon export const ArchiveIcon = _ArchiveIcon export const ArrowBigRightDashIcon = _ArrowBigRightDashIcon export const ArrowBigUpDashIcon = _ArrowBigUpDashIcon -export const ArrowDownLeftIcon = _ArrowDownLeftIcon export const ArrowDownIcon = _ArrowDownIcon +export const ArrowDownLeftIcon = _ArrowDownLeftIcon export const ArrowLeftRightIcon = _ArrowLeftRightIcon -export const ArrowUpRightIcon = _ArrowUpRightIcon export const ArrowUpIcon = _ArrowUpIcon +export const ArrowUpRightIcon = _ArrowUpRightIcon export const AsteriskIcon = _AsteriskIcon export const BadgeCheckIcon = _BadgeCheckIcon export const BadgeDollarSignIcon = _BadgeDollarSignIcon export const BanIcon = _BanIcon -export const BellRingIcon = _BellRingIcon export const BellIcon = _BellIcon +export const BellRingIcon = _BellRingIcon export const BlocksIcon = _BlocksIcon export const BoldIcon = _BoldIcon +export const BookIcon = _BookIcon export const BookOpenIcon = _BookOpenIcon export const BookTextIcon = _BookTextIcon -export const BookIcon = _BookIcon export const BookmarkIcon = _BookmarkIcon export const BotIcon = _BotIcon -export const BoxImportIcon = _BoxImportIcon export const BoxIcon = _BoxIcon +export const BoxImportIcon = _BoxImportIcon export const BracesIcon = _BracesIcon export const BrushCleaningIcon = _BrushCleaningIcon export const BugIcon = _BugIcon @@ -259,9 +259,9 @@ export const CalendarIcon = _CalendarIcon export const CardIcon = _CardIcon export const ChangeSkinIcon = _ChangeSkinIcon export const ChartIcon = _ChartIcon +export const CheckIcon = _CheckIcon export const CheckCheckIcon = _CheckCheckIcon export const CheckCircleIcon = _CheckCircleIcon -export const CheckIcon = _CheckIcon export const ChevronDownIcon = _ChevronDownIcon export const ChevronLeftIcon = _ChevronLeftIcon export const ChevronRightIcon = _ChevronRightIcon @@ -293,20 +293,20 @@ export const EditIcon = _EditIcon export const EllipsisVerticalIcon = _EllipsisVerticalIcon export const ExpandIcon = _ExpandIcon export const ExternalIcon = _ExternalIcon -export const EyeOffIcon = _EyeOffIcon export const EyeIcon = _EyeIcon +export const EyeOffIcon = _EyeOffIcon +export const FileIcon = _FileIcon export const FileArchiveIcon = _FileArchiveIcon export const FileCodeIcon = _FileCodeIcon export const FileImageIcon = _FileImageIcon export const FileTextIcon = _FileTextIcon -export const FileIcon = _FileIcon -export const FilterXIcon = _FilterXIcon export const FilterIcon = _FilterIcon +export const FilterXIcon = _FilterXIcon +export const FolderIcon = _FolderIcon export const FolderArchiveIcon = _FolderArchiveIcon export const FolderOpenIcon = _FolderOpenIcon export const FolderSearchIcon = _FolderSearchIcon export const FolderUpIcon = _FolderUpIcon -export const FolderIcon = _FolderIcon export const GameIcon = _GameIcon export const GapIcon = _GapIcon export const GaugeIcon = _GaugeIcon @@ -323,9 +323,9 @@ export const HashIcon = _HashIcon export const Heading1Icon = _Heading1Icon export const Heading2Icon = _Heading2Icon export const Heading3Icon = _Heading3Icon +export const HeartIcon = _HeartIcon export const HeartHandshakeIcon = _HeartHandshakeIcon export const HeartMinusIcon = _HeartMinusIcon -export const HeartIcon = _HeartIcon export const HistoryIcon = _HistoryIcon export const HomeIcon = _HomeIcon export const ImageIcon = _ImageIcon @@ -342,15 +342,15 @@ export const LeftArrowIcon = _LeftArrowIcon export const LibraryIcon = _LibraryIcon export const LightBulbIcon = _LightBulbIcon export const LinkIcon = _LinkIcon +export const ListIcon = _ListIcon export const ListBulletedIcon = _ListBulletedIcon export const ListEndIcon = _ListEndIcon export const ListFilterIcon = _ListFilterIcon export const ListOrderedIcon = _ListOrderedIcon -export const ListIcon = _ListIcon -export const LoaderCircleIcon = _LoaderCircleIcon export const LoaderIcon = _LoaderIcon -export const LockOpenIcon = _LockOpenIcon +export const LoaderCircleIcon = _LoaderCircleIcon export const LockIcon = _LockIcon +export const LockOpenIcon = _LockOpenIcon export const LogInIcon = _LogInIcon export const LogOutIcon = _LogOutIcon export const MailIcon = _MailIcon @@ -361,8 +361,8 @@ export const MessageIcon = _MessageIcon export const MicrophoneIcon = _MicrophoneIcon export const MinimizeIcon = _MinimizeIcon export const MinusIcon = _MinusIcon -export const MonitorSmartphoneIcon = _MonitorSmartphoneIcon export const MonitorIcon = _MonitorIcon +export const MonitorSmartphoneIcon = _MonitorSmartphoneIcon export const MoonIcon = _MoonIcon export const MoreHorizontalIcon = _MoreHorizontalIcon export const MoreVerticalIcon = _MoreVerticalIcon @@ -371,16 +371,16 @@ export const NoSignalIcon = _NoSignalIcon export const NotepadTextIcon = _NotepadTextIcon export const OmorphiaIcon = _OmorphiaIcon export const OrganizationIcon = _OrganizationIcon +export const PackageIcon = _PackageIcon export const PackageClosedIcon = _PackageClosedIcon export const PackageOpenIcon = _PackageOpenIcon -export const PackageIcon = _PackageIcon export const PaintbrushIcon = _PaintbrushIcon export const PickaxeIcon = _PickaxeIcon export const PlayIcon = _PlayIcon export const PlugIcon = _PlugIcon export const PlusIcon = _PlusIcon -export const RadioButtonCheckedIcon = _RadioButtonCheckedIcon export const RadioButtonIcon = _RadioButtonIcon +export const RadioButtonCheckedIcon = _RadioButtonCheckedIcon export const ReceiptTextIcon = _ReceiptTextIcon export const RedoIcon = _RedoIcon export const RefreshCwIcon = _RefreshCwIcon @@ -397,13 +397,13 @@ export const ScaleIcon = _ScaleIcon export const ScanEyeIcon = _ScanEyeIcon export const SearchIcon = _SearchIcon export const SendIcon = _SendIcon -export const ServerPlusIcon = _ServerPlusIcon export const ServerIcon = _ServerIcon +export const ServerPlusIcon = _ServerPlusIcon export const SettingsIcon = _SettingsIcon export const ShareIcon = _ShareIcon +export const ShieldIcon = _ShieldIcon export const ShieldAlertIcon = _ShieldAlertIcon export const ShieldCheckIcon = _ShieldCheckIcon -export const ShieldIcon = _ShieldIcon export const SignalIcon = _SignalIcon export const SkullIcon = _SkullIcon export const SlashIcon = _SlashIcon @@ -430,25 +430,25 @@ export const TrashIcon = _TrashIcon export const TriangleAlertIcon = _TriangleAlertIcon export const UnderlineIcon = _UnderlineIcon export const UndoIcon = _UndoIcon -export const UnknownDonationIcon = _UnknownDonationIcon export const UnknownIcon = _UnknownIcon +export const UnknownDonationIcon = _UnknownDonationIcon export const UnlinkIcon = _UnlinkIcon export const UnplugIcon = _UnplugIcon export const UpdatedIcon = _UpdatedIcon export const UploadIcon = _UploadIcon +export const UserIcon = _UserIcon export const UserCogIcon = _UserCogIcon export const UserPlusIcon = _UserPlusIcon export const UserRoundIcon = _UserRoundIcon export const UserSearchIcon = _UserSearchIcon export const UserXIcon = _UserXIcon -export const UserIcon = _UserIcon export const UsersIcon = _UsersIcon export const VersionIcon = _VersionIcon export const WikiIcon = _WikiIcon export const WindowIcon = _WindowIcon export const WorldIcon = _WorldIcon export const WrenchIcon = _WrenchIcon -export const XCircleIcon = _XCircleIcon export const XIcon = _XIcon +export const XCircleIcon = _XCircleIcon export const ZoomInIcon = _ZoomInIcon export const ZoomOutIcon = _ZoomOutIcon diff --git a/packages/blog/compile.ts b/packages/blog/compile.ts index 504e7715d..27ed30f8a 100644 --- a/packages/blog/compile.ts +++ b/packages/blog/compile.ts @@ -1,3 +1,4 @@ +import { compareImportSources } from '@modrinth/tooling-config/script-utils/import-sort' import { md } from '@modrinth/utils' import { promises as fs } from 'fs' import { glob } from 'glob' @@ -165,12 +166,20 @@ export const article = { console.log(`📂 Compiled ${files.length} articles.`) + // Sort imports using simple-import-sort's algorithm to avoid ESLint reformatting + const articleData = articlesArray.map((varName, i) => ({ + varName, + importPath: `./${varName}`, + exportLine: articleExports[i], + })) + articleData.sort((a, b) => compareImportSources(a.importPath, b.importPath)) + const rootExport = ` // AUTO-GENERATED FILE - DO NOT EDIT -${articleExports.join('\n')} +${articleData.map((a) => a.exportLine).join('\n')} export const articles = [ - ${articlesArray.join(',\n ')} + ${articleData.map((a) => a.varName).join(',\n ')} ]; `.trimStart() diff --git a/packages/blog/compiled/index.ts b/packages/blog/compiled/index.ts index 2dcc7450a..a10664932 100644 --- a/packages/blog/compiled/index.ts +++ b/packages/blog/compiled/index.ts @@ -35,38 +35,38 @@ import { article as whats_modrinth } from "./whats_modrinth"; import { article as windows_borderless_malware_disclosure } from "./windows_borderless_malware_disclosure"; export const articles = [ - windows_borderless_malware_disclosure, - whats_modrinth, - two_years_of_modrinth, - two_years_of_modrinth_history, - streamlined_version_creation, + a_new_chapter_for_modrinth_servers, + accelerating_development, + becoming_sustainable, + capital_return, + carbon_ads, + creator_monetization, + creator_update, + creator_updates_july_2025, + creator_withdrawals_overhaul, + design_refresh, + download_adjustment, + free_server_medal, + knossos_v2_1_0, + licensing_guide, + modpack_changes, + modpacks_alpha, + modrinth_app_beta, + modrinth_beta, + modrinth_servers_asia, + modrinth_servers_beta, + new_environments, + new_site_beta, + plugins_resource_packs, + pride_campaign_2025, + redesign, + russian_censorship, + skins_now_in_modrinth_app, standing_by_our_values, standing_by_our_values_russian, - skins_now_in_modrinth_app, - russian_censorship, - redesign, - pride_campaign_2025, - plugins_resource_packs, - new_site_beta, - new_environments, - modrinth_servers_beta, - modrinth_servers_asia, - modrinth_beta, - modrinth_app_beta, - modpacks_alpha, - modpack_changes, - licensing_guide, - knossos_v2_1_0, - free_server_medal, - download_adjustment, - design_refresh, - creator_withdrawals_overhaul, - creator_updates_july_2025, - creator_update, - creator_monetization, - carbon_ads, - capital_return, - becoming_sustainable, - accelerating_development, - a_new_chapter_for_modrinth_servers + streamlined_version_creation, + two_years_of_modrinth, + two_years_of_modrinth_history, + whats_modrinth, + windows_borderless_malware_disclosure ]; diff --git a/packages/tooling-config/package.json b/packages/tooling-config/package.json index 0839c1578..0bd8c1066 100644 --- a/packages/tooling-config/package.json +++ b/packages/tooling-config/package.json @@ -15,7 +15,8 @@ "./frontend.prettier.config.cjs": "./frontend.prettier.config.cjs", "./app-lib.prettier.config.cjs": "./app-lib.prettier.config.cjs", "./labrinth.prettier.config.cjs": "./labrinth.prettier.config.cjs", - "./tailwind/*": "./tailwind/*" + "./tailwind/*": "./tailwind/*", + "./script-utils/*": "./script-utils/*" }, "files": [ "eslint/", diff --git a/packages/tooling-config/script-utils/import-sort.ts b/packages/tooling-config/script-utils/import-sort.ts new file mode 100644 index 000000000..3564c905e --- /dev/null +++ b/packages/tooling-config/script-utils/import-sort.ts @@ -0,0 +1,73 @@ +/** + * Import sorting utility that matches eslint-plugin-simple-import-sort's algorithm. + * Use this to pre-sort generated imports so ESLint doesn't need to reformat them - which can cause it to have different diffs + */ + +const collator = new Intl.Collator('en', { + sensitivity: 'base', + numeric: true, +}) + +function compare(a: string, b: string): number { + return collator.compare(a, b) || (a < b ? -1 : a > b ? 1 : 0) +} + +/** + * Transforms an import source path the same way simple-import-sort does internally. + * This swaps certain characters to achieve the desired sort order: + * - `.` and `/` sort before other punctuation + * - `..` sorts like `../,` to come after `../../` but before `../a` + */ +function transformSource(source: string): string { + return source + .replace(/^[./]*\.$/, '$&/') + .replace(/^[./]*\/$/, '$&,') + .replace(/[./_-]/g, (char) => { + switch (char) { + case '.': + return '_' + case '/': + return '-' + case '_': + return '.' + case '-': + return '/' + default: + return char + } + }) +} + +/** + * Compares two import source paths using the same algorithm as simple-import-sort. + * Use this as a comparator function for Array.sort(). + * + * @example + * const imports = ['./foo', './bar', './baz']; + * imports.sort(compareImportSources); + */ +export function compareImportSources(a: string, b: string): number { + return compare(transformSource(a), transformSource(b)) +} + +/** + * Sorts an array of import source paths using the same algorithm as simple-import-sort. + * + * @example + * const sorted = sortImportSources(['./z', './a', './m']); + * // Returns: ['./a', './m', './z'] + */ +export function sortImportSources(sources: T[]): T[] { + return sources.slice().sort(compareImportSources) +} + +/** + * Sorts an array of items by their import source path. + * + * @example + * const items = [{ path: './z' }, { path: './a' }]; + * const sorted = sortByImportSource(items, item => item.path); + */ +export function sortByImportSource(items: T[], getSource: (item: T) => string): T[] { + return items.slice().sort((a, b) => compareImportSources(getSource(a), getSource(b))) +}