···11+/**
22+ * Detects if a package name is a platform-specific native binary package.
33+ * These are typically optional dependencies that contain native binaries
44+ * for specific OS/architecture combinations (e.g., @oxlint/win32-x64, esbuild-darwin-arm64).
55+ * Sourced from searches for esbuild, and the napi-rs build triplets support matrix.
66+ */
77+88+const PLATFORMS = new Set([
99+ 'win32',
1010+ 'darwin',
1111+ 'linux',
1212+ 'android',
1313+ 'freebsd',
1414+ 'openbsd',
1515+ 'netbsd',
1616+ 'sunos',
1717+ 'aix',
1818+])
1919+2020+const ARCHITECTURES = new Set([
2121+ 'x64',
2222+ 'arm64',
2323+ 'arm',
2424+ 'ia32',
2525+ 'ppc64',
2626+ 'ppc64le',
2727+ 's390x',
2828+ 'riscv64',
2929+ 'mips64el',
3030+ 'loong64',
3131+])
3232+3333+const ABI_SUFFIXES = new Set(['gnu', 'musl', 'msvc', 'gnueabihf'])
3434+3535+/**
3636+ * Checks if a package name is a platform-specific native binary package.
3737+ * Matches patterns like:
3838+ * - @scope/pkg-win32-x64
3939+ * - @scope/pkg-linux-arm64-gnu
4040+ * - pkg-darwin-arm64
4141+ * - @rollup/rollup-linux-x64-musl
4242+ *
4343+ * @param name - The full package name (including scope if present)
4444+ * @returns true if the package appears to be a platform-specific binary
4545+ */
4646+export function isPlatformSpecificPackage(name: string): boolean {
4747+ const unscopedName = name.startsWith('@') ? (name.split('/')[1] ?? '') : name
4848+ if (!unscopedName) return false
4949+5050+ const parts = unscopedName.split('-')
5151+ if (parts.length < 2) return false
5252+5353+ // Look for OS-arch pattern anywhere in the name as suffix parts
5454+ // e.g., "pkg-linux-x64-gnu" -> ["pkg", "linux", "x64", "gnu"]
5555+ for (let i = 0; i < parts.length - 1; i++) {
5656+ const os = parts[i]
5757+ const arch = parts[i + 1]
5858+5959+ if (os && arch && PLATFORMS.has(os) && ARCHITECTURES.has(arch)) {
6060+ // Optional ABI suffix check (next part if exists)
6161+ const abiSuffix = parts[i + 2]
6262+ if (abiSuffix && !ABI_SUFFIXES.has(abiSuffix)) {
6363+ // NOTE: Has an extra part after arch but it's not a known ABI - might be a false positive??
6464+ // but still consider it a match if OS+arch pattern is found at the end
6565+ if (i + 2 === parts.length - 1) {
6666+ // Extra unknown suffix at the end - be conservative
6767+ continue
6868+ }
6969+ }
7070+ return true
7171+ }
7272+ }
7373+7474+ return false
7575+}
+2
i18n/locales/en.json
···5959 "relative_dates_description": "Show \"3 days ago\" instead of full dates",
6060 "include_types": "Include {'@'}types in install",
6161 "include_types_description": "Add {'@'}types package to install commands for untyped packages",
6262+ "hide_platform_packages": "Hide platform-specific packages in search",
6363+ "hide_platform_packages_description": "Hide native binary packages like {'@'}esbuild/linux-x64 from results",
6264 "theme": "Theme",
6365 "theme_light": "Light",
6466 "theme_dark": "Dark",
+2
lunaria/files/en-US.json
···5959 "relative_dates_description": "Show \"3 days ago\" instead of full dates",
6060 "include_types": "Include {'@'}types in install",
6161 "include_types_description": "Add {'@'}types package to install commands for untyped packages",
6262+ "hide_platform_packages": "Hide platform-specific packages in search",
6363+ "hide_platform_packages_description": "Hide native binary packages like {'@'}esbuild/linux-x64 from results",
6264 "theme": "Theme",
6365 "theme_light": "Light",
6466 "theme_dark": "Dark",
+149
test/unit/platform-packages.spec.ts
···11+import { describe, expect, it } from 'vitest'
22+import { isPlatformSpecificPackage } from '../../app/utils/platform-packages'
33+44+describe('isPlatformSpecificPackage', () => {
55+ describe('standard platform packages', () => {
66+ it.each([
77+ 'esbuild-linux-x64',
88+ 'esbuild-darwin-arm64',
99+ 'esbuild-win32-x64',
1010+ 'esbuild-win32-ia32',
1111+ 'esbuild-freebsd-x64',
1212+ 'esbuild-android-arm64',
1313+ ])('detects "%s" as platform-specific', name => {
1414+ expect(isPlatformSpecificPackage(name)).toBe(true)
1515+ })
1616+ })
1717+1818+ describe('scoped platform packages', () => {
1919+ it.each([
2020+ '@oxlint/win32-x64',
2121+ '@oxlint/linux-arm64',
2222+ '@oxlint/darwin-arm64',
2323+ '@swc/core-win32-x64-msvc',
2424+ '@swc/core-linux-x64-gnu',
2525+ '@swc/core-linux-arm64-musl',
2626+ '@rollup/rollup-linux-x64-gnu',
2727+ '@rollup/rollup-darwin-arm64',
2828+ '@rollup/rollup-win32-x64-msvc',
2929+ '@esbuild/linux-x64',
3030+ '@esbuild/darwin-arm64',
3131+ '@esbuild/win32-ia32',
3232+ ])('detects "%s" as platform-specific', name => {
3333+ expect(isPlatformSpecificPackage(name)).toBe(true)
3434+ })
3535+ })
3636+3737+ describe('packages with ABI suffixes', () => {
3838+ it.each([
3939+ 'pkg-linux-x64-gnu',
4040+ 'pkg-linux-x64-musl',
4141+ 'pkg-win32-x64-msvc',
4242+ 'pkg-win32-arm64-msvc',
4343+ 'pkg-linux-arm-gnueabihf',
4444+ ])('detects "%s" with ABI suffix as platform-specific', name => {
4545+ expect(isPlatformSpecificPackage(name)).toBe(true)
4646+ })
4747+ })
4848+4949+ describe('all platform combinations', () => {
5050+ it.each([
5151+ // Windows variants
5252+ 'pkg-win32-x64',
5353+ 'pkg-win32-arm64',
5454+ 'pkg-win32-ia32',
5555+ // macOS variants
5656+ 'pkg-darwin-x64',
5757+ 'pkg-darwin-arm64',
5858+ // Linux variants
5959+ 'pkg-linux-x64',
6060+ 'pkg-linux-arm64',
6161+ 'pkg-linux-arm',
6262+ 'pkg-linux-ia32',
6363+ 'pkg-linux-ppc64',
6464+ 'pkg-linux-ppc64le',
6565+ 'pkg-linux-s390x',
6666+ 'pkg-linux-riscv64',
6767+ 'pkg-linux-mips64el',
6868+ 'pkg-linux-loong64',
6969+ // Android
7070+ 'pkg-android-arm64',
7171+ 'pkg-android-arm',
7272+ 'pkg-android-x64',
7373+ // BSD variants
7474+ 'pkg-freebsd-x64',
7575+ 'pkg-freebsd-arm64',
7676+ 'pkg-openbsd-x64',
7777+ 'pkg-netbsd-x64',
7878+ // Others
7979+ 'pkg-sunos-x64',
8080+ 'pkg-aix-ppc64',
8181+ ])('detects "%s" as platform-specific', name => {
8282+ expect(isPlatformSpecificPackage(name)).toBe(true)
8383+ })
8484+ })
8585+8686+ describe('false positives - should NOT match', () => {
8787+ it.each([
8888+ 'linux-tips',
8989+ 'node-linux',
9090+ 'darwin-utils',
9191+ 'win32-api',
9292+ 'android-sdk',
9393+ 'express',
9494+ 'react',
9595+ 'vue',
9696+ '@types/node',
9797+ '@babel/core',
9898+ 'lodash',
9999+ 'typescript',
100100+ 'eslint',
101101+ 'prettier',
102102+ 'platform-tools',
103103+ 'arch-decision-records',
104104+ 'arm-controller',
105105+ 'x64-utils',
106106+ ])('does NOT detect "%s" as platform-specific', name => {
107107+ expect(isPlatformSpecificPackage(name)).toBe(false)
108108+ })
109109+ })
110110+111111+ describe('edge cases', () => {
112112+ it('returns false for empty string', () => {
113113+ expect(isPlatformSpecificPackage('')).toBe(false)
114114+ })
115115+116116+ it('returns false for scoped package with empty name', () => {
117117+ expect(isPlatformSpecificPackage('@scope/')).toBe(false)
118118+ })
119119+120120+ it('returns false for single-part names', () => {
121121+ expect(isPlatformSpecificPackage('linux')).toBe(false)
122122+ expect(isPlatformSpecificPackage('x64')).toBe(false)
123123+ })
124124+125125+ it('returns false for package with only OS, no arch', () => {
126126+ expect(isPlatformSpecificPackage('pkg-linux')).toBe(false)
127127+ expect(isPlatformSpecificPackage('pkg-darwin')).toBe(false)
128128+ expect(isPlatformSpecificPackage('pkg-win32')).toBe(false)
129129+ })
130130+131131+ it('returns false for package with only arch, no OS', () => {
132132+ expect(isPlatformSpecificPackage('pkg-x64')).toBe(false)
133133+ expect(isPlatformSpecificPackage('pkg-arm64')).toBe(false)
134134+ })
135135+136136+ it('is conservative with OS-arch in middle of name followed by unknown suffix', () => {
137137+ // These have unknown suffixes after the arch, so we're conservative
138138+ expect(isPlatformSpecificPackage('my-linux-x64-bindings')).toBe(false)
139139+ expect(isPlatformSpecificPackage('@scope/my-darwin-arm64-lib')).toBe(false)
140140+ })
141141+142142+ it('is conservative with unknown suffixes at the end', () => {
143143+ // Unknown suffix after arch at the very end - should be conservative
144144+ expect(isPlatformSpecificPackage('pkg-linux-x64-unknown')).toBe(false)
145145+ // But if there are more parts after, still matches
146146+ expect(isPlatformSpecificPackage('pkg-linux-x64-foo-bar')).toBe(true)
147147+ })
148148+ })
149149+})