From 1c28f460aeb787d8fbd8c03a2e3ab600c883f77c Mon Sep 17 00:00:00 2001 From: Felix Kling Date: Wed, 12 Jun 2024 15:59:32 +0200 Subject: [PATCH] feat(search/svelte): Support more file icons (#63181) Closes SRCH-452 Until this commit both the React and Svelte apps have used the same code to determine the correct file icon. That code however still held separate logic for the two apps with the result that our file icon set for the Svelte app was a lot smaller. This commit separates the logic and now makes use of `unplugin-icons` in the Svelte app. I tried to use `unplugin-icons` in the React so that the code could continued to be shared but I couldn't get it to work. Note that I didn't use the exact same icons as in the React app. I wanted to minimize the number of different icon sets we are using. ## Test plan Builds without error, manual testing. --- client/web-sveltekit/BUILD.bazel | 3 + client/web-sveltekit/package.json | 3 + client/web-sveltekit/src/auto-imports.d.ts | 77 +++++ client/web-sveltekit/src/lib/Icon2.svelte | 2 +- .../web-sveltekit/src/lib/LanguageIcon.svelte | 19 +- .../src/lib/repo/FileIcon.svelte | 21 +- .../lib/search/dynamicFilters/Sidebar.svelte | 2 +- .../web-sveltekit/src/lib/wildcard/index.ts | 2 +- .../src/lib/wildcard/language-icons.ts | 152 +++++++++ client/wildcard/BUILD.bazel | 1 + .../Icon/language-icon/LanguageIcon.tsx | 4 +- .../Icon/language-icon/language-icons.test.ts | 29 +- .../Icon/language-icon/language-icons.ts | 315 ++++++------------ pnpm-lock.yaml | 27 ++ 14 files changed, 392 insertions(+), 265 deletions(-) create mode 100644 client/web-sveltekit/src/lib/wildcard/language-icons.ts diff --git a/client/web-sveltekit/BUILD.bazel b/client/web-sveltekit/BUILD.bazel index 01f96b9a072..2555c0564c9 100644 --- a/client/web-sveltekit/BUILD.bazel +++ b/client/web-sveltekit/BUILD.bazel @@ -82,7 +82,10 @@ BUILD_DEPS = [ ":node_modules/@graphql-codegen/typescript", ":node_modules/@graphql-codegen/typescript-operations", ":node_modules/@graphql-tools/utils", + ":node_modules/@iconify-json/devicon-plain", ":node_modules/@iconify-json/lucide", + ":node_modules/@iconify-json/ph", + ":node_modules/@iconify-json/simple-icons", ":node_modules/@melt-ui/svelte", ":node_modules/@sentry/sveltekit", ":node_modules/@sourcegraph/branded", diff --git a/client/web-sveltekit/package.json b/client/web-sveltekit/package.json index 1dfe4f66708..31482f0d3e0 100644 --- a/client/web-sveltekit/package.json +++ b/client/web-sveltekit/package.json @@ -31,7 +31,10 @@ "@graphql-codegen/typescript-operations": "^4.0.1", "@graphql-tools/utils": "^10.0.11", "@graphql-typed-document-node/core": "^3.2.0", + "@iconify-json/devicon-plain": "^1.1.42", "@iconify-json/lucide": "^1.1.188", + "@iconify-json/ph": "^1.1.13", + "@iconify-json/simple-icons": "^1.1.104", "@playwright/test": "1.42.1", "@storybook/addon-essentials": "^8.0.5", "@storybook/addon-interactions": "^7.2.0", diff --git a/client/web-sveltekit/src/auto-imports.d.ts b/client/web-sveltekit/src/auto-imports.d.ts index 0faed7228ee..312a85e1a55 100644 --- a/client/web-sveltekit/src/auto-imports.d.ts +++ b/client/web-sveltekit/src/auto-imports.d.ts @@ -5,6 +5,7 @@ // Generated by unplugin-auto-import export {} declare global { + const IDeviconPlainJava: typeof import('~icons/devicon-plain/java')['default'] const ILucideArrowDownFromLine: typeof import('~icons/lucide/arrow-down-from-line')['default'] const ILucideArrowLeftFromLine: typeof import('~icons/lucide/arrow-left-from-line')['default'] const ILucideArrowRightFromLine: typeof import('~icons/lucide/arrow-right-from-line')['default'] @@ -13,7 +14,13 @@ declare global { const ILucideChevronLast: typeof import('~icons/lucide/chevron-last')['default'] const ILucideChevronLeft: typeof import('~icons/lucide/chevron-left')['default'] const ILucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] + const ILucideDatabase: typeof import('~icons/lucide/database')['default'] + const ILucideEarth: typeof import('~icons/lucide/earth')['default'] const ILucideEllipsis: typeof import('~icons/lucide/ellipsis')['default'] + const ILucideFileCode: typeof import('~icons/lucide/file-code')['default'] + const ILucideFileJson: typeof import('~icons/lucide/file-json')['default'] + const ILucideFileTerminal: typeof import('~icons/lucide/file-terminal')['default'] + const ILucideFileText: typeof import('~icons/lucide/file-text')['default'] const ILucideFolder: typeof import('~icons/lucide/folder')['default'] const ILucideGitCompareArrows: typeof import('~icons/lucide/git-compare-arrows')['default'] const ILucideGitMerge: typeof import('~icons/lucide/git-merge')['default'] @@ -23,7 +30,77 @@ declare global { const ILucidePanelLeftClose: typeof import('~icons/lucide/panel-left-close')['default'] const ILucidePanelLeftOpen: typeof import('~icons/lucide/panel-left-open')['default'] const ILucideRegex: typeof import('~icons/lucide/regex')['default'] + const ILucideSettings: typeof import('~icons/lucide/settings')['default'] + const ILucideSetttings: typeof import('~icons/lucide/setttings')['default'] const ILucideSquareSlash: typeof import('~icons/lucide/square-slash')['default'] const ILucideStar: typeof import('~icons/lucide/star')['default'] const ILucideX: typeof import('~icons/lucide/x')['default'] + const IPhFileJpgLight: typeof import('~icons/ph/file-jpg-light')['default'] + const IPhFilePngLight: typeof import('~icons/ph/file-png-light')['default'] + const IPhGifFill: typeof import('~icons/ph/gif-fill')['default'] + const IPhJpgLight: typeof import('~icons/ph/jpg-light')['default'] + const IPhPngLight: typeof import('~icons/ph/png-light')['default'] + const IPhPnglight: typeof import('~icons/ph/pnglight')['default'] + const IPhosphorPngLight: typeof import('~icons/ph/osphor-png-light')['default'] + const IPhosphorePngLight: typeof import('~icons/ph/osphore-png-light')['default'] + const ISimpleIconsApachegroovy: typeof import('~icons/simple-icons/apachegroovy')['default'] + const ISimpleIconsC: typeof import('~icons/simple-icons/c')['default'] + const ISimpleIconsCSS3: typeof import('~icons/simple-icons/c-s-s3')['default'] + const ISimpleIconsClojure: typeof import('~icons/simple-icons/clojure')['default'] + const ISimpleIconsCmake: typeof import('~icons/simple-icons/cmake')['default'] + const ISimpleIconsCoffeescript: typeof import('~icons/simple-icons/coffeescript')['default'] + const ISimpleIconsCplusplus: typeof import('~icons/simple-icons/cplusplus')['default'] + const ISimpleIconsCrystal: typeof import('~icons/simple-icons/crystal')['default'] + const ISimpleIconsCsharp: typeof import('~icons/simple-icons/csharp')['default'] + const ISimpleIconsCss3: typeof import('~icons/simple-icons/css3')['default'] + const ISimpleIconsD: typeof import('~icons/simple-icons/d')['default'] + const ISimpleIconsDart: typeof import('~icons/simple-icons/dart')['default'] + const ISimpleIconsDocker: typeof import('~icons/simple-icons/docker')['default'] + const ISimpleIconsEditorconfig: typeof import('~icons/simple-icons/editorconfig')['default'] + const ISimpleIconsElixir: typeof import('~icons/simple-icons/elixir')['default'] + const ISimpleIconsElm: typeof import('~icons/simple-icons/elm')['default'] + const ISimpleIconsErlang: typeof import('~icons/simple-icons/erlang')['default'] + const ISimpleIconsFortran: typeof import('~icons/simple-icons/fortran')['default'] + const ISimpleIconsFsharp: typeof import('~icons/simple-icons/fsharp')['default'] + const ISimpleIconsGit: typeof import('~icons/simple-icons/git')['default'] + const ISimpleIconsGnuemacs: typeof import('~icons/simple-icons/gnuemacs')['default'] + const ISimpleIconsGo: typeof import('~icons/simple-icons/go')['default'] + const ISimpleIconsGraphql: typeof import('~icons/simple-icons/graphql')['default'] + const ISimpleIconsHaskell: typeof import('~icons/simple-icons/haskell')['default'] + const ISimpleIconsHtml5: typeof import('~icons/simple-icons/html5')['default'] + const ISimpleIconsJavascript: typeof import('~icons/simple-icons/javascript')['default'] + const ISimpleIconsJinja: typeof import('~icons/simple-icons/jinja')['default'] + const ISimpleIconsJpeg: typeof import('~icons/simple-icons/jpeg')['default'] + const ISimpleIconsJulia: typeof import('~icons/simple-icons/julia')['default'] + const ISimpleIconsKotlin: typeof import('~icons/simple-icons/kotlin')['default'] + const ISimpleIconsLlvm: typeof import('~icons/simple-icons/llvm')['default'] + const ISimpleIconsLua: typeof import('~icons/simple-icons/lua')['default'] + const ISimpleIconsMarkdown: typeof import('~icons/simple-icons/markdown')['default'] + const ISimpleIconsNginx: typeof import('~icons/simple-icons/nginx')['default'] + const ISimpleIconsNim: typeof import('~icons/simple-icons/nim')['default'] + const ISimpleIconsNixos: typeof import('~icons/simple-icons/nixos')['default'] + const ISimpleIconsNpm: typeof import('~icons/simple-icons/npm')['default'] + const ISimpleIconsOcaml: typeof import('~icons/simple-icons/ocaml')['default'] + const ISimpleIconsPerl: typeof import('~icons/simple-icons/perl')['default'] + const ISimpleIconsPhp: typeof import('~icons/simple-icons/php')['default'] + const ISimpleIconsPurescript: typeof import('~icons/simple-icons/purescript')['default'] + const ISimpleIconsPython: typeof import('~icons/simple-icons/python')['default'] + const ISimpleIconsR: typeof import('~icons/simple-icons/r')['default'] + const ISimpleIconsRuby: typeof import('~icons/simple-icons/ruby')['default'] + const ISimpleIconsRust: typeof import('~icons/simple-icons/rust')['default'] + const ISimpleIconsSass: typeof import('~icons/simple-icons/sass')['default'] + const ISimpleIconsScala: typeof import('~icons/simple-icons/scala')['default'] + const ISimpleIconsSvelte: typeof import('~icons/simple-icons/svelte')['default'] + const ISimpleIconsSvg: typeof import('~icons/simple-icons/svg')['default'] + const ISimpleIconsSwift: typeof import('~icons/simple-icons/swift')['default'] + const ISimpleIconsTerraform: typeof import('~icons/simple-icons/terraform')['default'] + const ISimpleIconsToml: typeof import('~icons/simple-icons/toml')['default'] + const ISimpleIconsTypescript: typeof import('~icons/simple-icons/typescript')['default'] + const ISimpleIconsUnrealengine: typeof import('~icons/simple-icons/unrealengine')['default'] + const ISimpleIconsVim: typeof import('~icons/simple-icons/vim')['default'] + const ISimpleIconsVisualbasic: typeof import('~icons/simple-icons/visualbasic')['default'] + const ISimpleIconsVuedotjs: typeof import('~icons/simple-icons/vuedotjs')['default'] + const ISimpleIconsWebassembly: typeof import('~icons/simple-icons/webassembly')['default'] + const ISimpleIconsWolframmathematica: typeof import('~icons/simple-icons/wolframmathematica')['default'] + const ISimpleIconsZig: typeof import('~icons/simple-icons/zig')['default'] } diff --git a/client/web-sveltekit/src/lib/Icon2.svelte b/client/web-sveltekit/src/lib/Icon2.svelte index 7ed1a092ce6..d963fe29503 100644 --- a/client/web-sveltekit/src/lib/Icon2.svelte +++ b/client/web-sveltekit/src/lib/Icon2.svelte @@ -30,4 +30,4 @@ export let inline: boolean = false - + diff --git a/client/web-sveltekit/src/lib/LanguageIcon.svelte b/client/web-sveltekit/src/lib/LanguageIcon.svelte index d668d01ffc5..a989232594c 100644 --- a/client/web-sveltekit/src/lib/LanguageIcon.svelte +++ b/client/web-sveltekit/src/lib/LanguageIcon.svelte @@ -1,15 +1,18 @@ - + + + diff --git a/client/web-sveltekit/src/lib/repo/FileIcon.svelte b/client/web-sveltekit/src/lib/repo/FileIcon.svelte index 6ef24e0ad42..b0200b9d056 100644 --- a/client/web-sveltekit/src/lib/repo/FileIcon.svelte +++ b/client/web-sveltekit/src/lib/repo/FileIcon.svelte @@ -1,16 +1,21 @@ - + diff --git a/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte b/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte index c33e7277ae2..01143c7a393 100644 --- a/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte +++ b/client/web-sveltekit/src/lib/search/dynamicFilters/Sidebar.svelte @@ -156,7 +156,7 @@ onFilterSelect={handleFilterSelect} > -   +   {label} diff --git a/client/web-sveltekit/src/lib/wildcard/index.ts b/client/web-sveltekit/src/lib/wildcard/index.ts index 382959cb5d2..26edee5740f 100644 --- a/client/web-sveltekit/src/lib/wildcard/index.ts +++ b/client/web-sveltekit/src/lib/wildcard/index.ts @@ -15,4 +15,4 @@ export { default as Input } from './Input.svelte' export { default as PanelGroup } from './resizable-panel/PanelGroup.svelte' export { default as Panel } from './resizable-panel/Panel.svelte' export { default as PanelResizeHandle } from './resizable-panel/PanelResizeHandle.svelte' -export { getFileIconInfo } from '@sourcegraph/wildcard/src/components/Icon' +export { getFileIconInfo, DEFAULT_ICON_COLOR } from './language-icons' diff --git a/client/web-sveltekit/src/lib/wildcard/language-icons.ts b/client/web-sveltekit/src/lib/wildcard/language-icons.ts new file mode 100644 index 00000000000..be6678ffe7f --- /dev/null +++ b/client/web-sveltekit/src/lib/wildcard/language-icons.ts @@ -0,0 +1,152 @@ +import type { ComponentType, SvelteComponent } from 'svelte' +import type { SvelteHTMLElements } from 'svelte/elements' + +interface LanguageIcon { + icon: ComponentType> + color: string +} + +const BLUE = 'var(--blue)' +const PINK = 'var(--pink)' +const YELLOW = 'var(--yellow)' +const RED = 'var(--red)' +const GREEN = 'var(--green)' +const CYAN = 'var(--blue)' +const GRAY = 'var(--gray-05)' +export const DEFAULT_ICON_COLOR = GRAY + +/** + * The keys of this map must be present in the list of `languageFilter.ALL_LANGUAGES`. + * + * This map is deliberately not public, use {@link getFileIconInfo} instead. + * + * See FIXME(id: language-detection) for context on why this map uses the + * language as the key instead of something simpler like a file extension. + */ +export const FILE_ICONS_BY_LANGUAGE: Map = new Map([ + ['Bash', { icon: ILucideFileTerminal, color: DEFAULT_ICON_COLOR }], + ['BASIC', { icon: ISimpleIconsVisualbasic, color: DEFAULT_ICON_COLOR }], + ['C', { icon: ISimpleIconsC, color: BLUE }], + ['C++', { icon: ISimpleIconsCplusplus, color: BLUE }], + ['C#', { icon: ISimpleIconsCsharp, color: BLUE }], + ['Clojure', { icon: ISimpleIconsClojure, color: BLUE }], + ['CMake', { icon: ISimpleIconsCmake, color: DEFAULT_ICON_COLOR }], + ['CoffeeScript', { icon: ISimpleIconsCoffeescript, color: DEFAULT_ICON_COLOR }], + + // TODO: Decide icon for CSV? + ['Crystal', { icon: ISimpleIconsCrystal, color: BLUE }], + ['CSS', { icon: ISimpleIconsCss3, color: BLUE }], + ['D', { icon: ISimpleIconsD, color: RED }], + ['Dart', { icon: ISimpleIconsDart, color: BLUE }], + ['Dockerfile', { icon: ISimpleIconsDocker, color: BLUE }], + ['EditorConfig', { icon: ISimpleIconsEditorconfig, color: DEFAULT_ICON_COLOR }], + ['Elixir', { icon: ISimpleIconsElixir, color: BLUE }], + ['Elm', { icon: ISimpleIconsElm, color: BLUE }], + ['Emacs Lisp', { icon: ISimpleIconsGnuemacs, color: DEFAULT_ICON_COLOR }], + ['Erlang', { icon: ISimpleIconsErlang, color: BLUE }], + ['Fortran', { icon: ISimpleIconsFortran, color: DEFAULT_ICON_COLOR }], + ['Fortran Free Form', { icon: ISimpleIconsFortran, color: DEFAULT_ICON_COLOR }], + ['F#', { icon: ISimpleIconsFsharp, color: BLUE }], + ['Git Attributes', { icon: ISimpleIconsGit, color: RED }], + ['Go', { icon: ISimpleIconsGo, color: BLUE }], + ['Go Module', { icon: ISimpleIconsGo, color: PINK }], + ['Go Checksums', { icon: ISimpleIconsGo, color: PINK }], + ['Groovy', { icon: ISimpleIconsApachegroovy, color: BLUE }], + ['GraphQL', { icon: ISimpleIconsGraphql, color: PINK }], + ['Haskell', { icon: ISimpleIconsHaskell, color: BLUE }], + ['HTML', { icon: ISimpleIconsHtml5, color: BLUE }], + ['HTML+ECR', { icon: ISimpleIconsCrystal, color: BLUE }], + ['HTML+EEX', { icon: ISimpleIconsElixir, color: BLUE }], + ['HTML+ERB', { icon: ISimpleIconsRuby, color: BLUE }], + ['HTML+PHP', { icon: ISimpleIconsPhp, color: BLUE }], + ['HTML+Razor', { icon: ISimpleIconsCsharp, color: BLUE }], + ['Ignore List', { icon: ILucideSettings, color: DEFAULT_ICON_COLOR }], + ['Java', { icon: IDeviconPlainJava, color: DEFAULT_ICON_COLOR }], + ['JavaScript', { icon: ISimpleIconsJavascript, color: YELLOW }], + ['Jinja', { icon: ISimpleIconsJinja, color: DEFAULT_ICON_COLOR }], + ['JSON with Comments', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['JSON', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['JSON5', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['JSONLD', { icon: ILucideFileJson, color: DEFAULT_ICON_COLOR }], + ['Julia', { icon: ISimpleIconsJulia, color: DEFAULT_ICON_COLOR }], + ['Kotlin', { icon: ISimpleIconsKotlin, color: GREEN }], + ['LLVM', { icon: ISimpleIconsLlvm, color: GRAY }], + ['Lua', { icon: ISimpleIconsLua, color: BLUE }], + ['Markdown', { icon: ISimpleIconsMarkdown, color: BLUE }], + ['Mathematica', { icon: ISimpleIconsWolframmathematica, color: RED }], + + // https://github.com/NCAR/ncl, not tweag/nickel + ['NCL', { icon: ILucideEarth, color: DEFAULT_ICON_COLOR }], + ['Nginx', { icon: ISimpleIconsNginx, color: DEFAULT_ICON_COLOR }], + ['Nim', { icon: ISimpleIconsNim, color: YELLOW }], + ['Nix', { icon: ISimpleIconsNixos, color: GRAY }], + ['NPM Config', { icon: ISimpleIconsNpm, color: RED }], + + // Missing an icon for Objective-C + ['OCaml', { icon: ISimpleIconsOcaml, color: YELLOW }], + ['PHP', { icon: ISimpleIconsPhp, color: CYAN }], + ['Perl', { icon: ISimpleIconsPerl, color: DEFAULT_ICON_COLOR }], + ['PLpgSQL', { icon: ILucideDatabase, color: BLUE }], + ['PowerShell', { icon: ILucideFileTerminal, color: DEFAULT_ICON_COLOR }], + + // Missing icon for Protobuf + ['PureScript', { icon: ISimpleIconsPurescript, color: DEFAULT_ICON_COLOR }], + ['Python', { icon: ISimpleIconsPython, color: BLUE }], + ['R', { icon: ISimpleIconsR, color: RED }], + ['Ruby', { icon: ISimpleIconsRuby, color: RED }], + ['Rust', { icon: ISimpleIconsRust, color: DEFAULT_ICON_COLOR }], + ['Scala', { icon: ISimpleIconsScala, color: RED }], + ['Sass', { icon: ISimpleIconsSass, color: PINK }], + ['SCSS', { icon: ISimpleIconsSass, color: PINK }], + ['SQL', { icon: ILucideDatabase, color: BLUE }], + + // Missing icon for Starlark + ['Svelte', { icon: ISimpleIconsSvelte, color: RED }], + ['SVG', { icon: ISimpleIconsSvg, color: YELLOW }], + ['Swift', { icon: ISimpleIconsSwift, color: BLUE }], + ['Terraform', { icon: ISimpleIconsTerraform, color: BLUE }], + ['TSX', { icon: ISimpleIconsTypescript, color: BLUE }], + ['TypeScript', { icon: ISimpleIconsTypescript, color: BLUE }], + ['Text', { icon: ILucideFileText, color: DEFAULT_ICON_COLOR }], + + // Missing icon for Thrift + ['TOML', { icon: ISimpleIconsToml, color: DEFAULT_ICON_COLOR }], + ['UnrealScript', { icon: ISimpleIconsUnrealengine, color: DEFAULT_ICON_COLOR }], + ['VBA', { icon: ISimpleIconsVisualbasic, color: BLUE }], + ['VBScript', { icon: ISimpleIconsVisualbasic, color: BLUE }], + ['Vim Script', { icon: ISimpleIconsVim, color: DEFAULT_ICON_COLOR }], + ['Vue', { icon: ISimpleIconsVuedotjs, color: GREEN }], + ['WebAssembly', { icon: ISimpleIconsWebassembly, color: BLUE }], + ['XML', { icon: ILucideSettings, color: DEFAULT_ICON_COLOR }], + ['YAML', { icon: ILucideSettings, color: DEFAULT_ICON_COLOR }], + ['Zig', { icon: ISimpleIconsZig, color: YELLOW }], +]) + +/** + * DO NOT add any extensions here for which there are multiple different + * file formats in practice which use the same extensions. + * + * For programming languages, update {@link FILE_ICONS_BY_LANGUAGE}. + */ +const BINARY_FILE_ICONS_BY_EXTENSION: Map = new Map([ + ['gif', { icon: IPhGifFill, color: DEFAULT_ICON_COLOR }], + ['giff', { icon: IPhGifFill, color: DEFAULT_ICON_COLOR }], + ['jpg', { icon: IPhFileJpgLight, color: YELLOW }], + ['jpeg', { icon: IPhFileJpgLight, color: YELLOW }], + ['png', { icon: IPhFilePngLight, color: DEFAULT_ICON_COLOR }], +]) + +/** + * + * See FIXME(id: language-detection) for context on why this takes a + * languages argument instead of directly using the file extension + * for determining the language. + * + * @param path The path of the file (or just its name). + * @param language Alias to the file language name. + * @returns undefined if the language is not a known language. + */ +export function getFileIconInfo(path: string, language: string): LanguageIcon | undefined { + const extension = path.split('.').at(-1) ?? '' + return BINARY_FILE_ICONS_BY_EXTENSION.get(extension) ?? FILE_ICONS_BY_LANGUAGE.get(language) +} diff --git a/client/wildcard/BUILD.bazel b/client/wildcard/BUILD.bazel index 609cbb3c728..e531f392019 100644 --- a/client/wildcard/BUILD.bazel +++ b/client/wildcard/BUILD.bazel @@ -565,6 +565,7 @@ ts_project( "//:node_modules/js-cookie", "//:node_modules/mdi-react", "//:node_modules/react", + "//:node_modules/react-icons", "//:node_modules/rxjs", "//:node_modules/sinon", "//:node_modules/vitest", diff --git a/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx b/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx index 75bd5e10dcc..7377ddf4ac8 100644 --- a/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx +++ b/client/wildcard/src/components/Icon/language-icon/LanguageIcon.tsx @@ -24,8 +24,8 @@ export const LanguageIcon: FC = props => { if (fileIcon) { return ( ) diff --git a/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts b/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts index 48b5a024c96..960edcf4b36 100644 --- a/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts +++ b/client/wildcard/src/components/Icon/language-icon/language-icons.test.ts @@ -1,50 +1,35 @@ -import { mdiFileCodeOutline, mdiFilePngBox, mdiLanguageJavascript } from '@mdi/js' +import { PiFilePngLight } from 'react-icons/pi' +import { SiJavascript } from 'react-icons/si' import { describe, expect, it } from 'vitest' import { getFileIconInfo } from './language-icons' describe('getFileIconInfo', () => { - const tests: { - name: string - file: string - language: string - expectedSvgPath: string | undefined - expectedIsTest: boolean - }[] = [ + const tests = [ { name: 'check that png works', file: 'myfile.png', language: '', - expectedSvgPath: mdiFilePngBox, - expectedIsTest: false, + expectedIcon: PiFilePngLight, }, { name: 'works with simple file name', file: 'my-file.js', language: 'JavaScript', - expectedSvgPath: mdiLanguageJavascript, - expectedIsTest: false, - }, - { - name: 'check fallback behavior', - file: 'placeholder', - language: 'Vim Script', - expectedSvgPath: mdiFileCodeOutline, - expectedIsTest: false, + expectedIcon: SiJavascript, }, { name: 'check unknown language', file: 'my-file.test.unknown', language: 'Unknown', - expectedSvgPath: undefined, - expectedIsTest: true, + expectedIcon: undefined, }, ] for (const t of tests) { it(t.name, () => { const iconInfo = getFileIconInfo(t.file, t.language) - expect(iconInfo?.svg.path).toBe(t.expectedSvgPath) + expect(iconInfo?.icon).toBe(t.expectedIcon) }) } }) diff --git a/client/wildcard/src/components/Icon/language-icon/language-icons.ts b/client/wildcard/src/components/Icon/language-icon/language-icons.ts index de688680e35..d33b212454a 100644 --- a/client/wildcard/src/components/Icon/language-icon/language-icons.ts +++ b/client/wildcard/src/components/Icon/language-icon/language-icons.ts @@ -1,57 +1,5 @@ import type { ComponentType } from 'react' -// TODO(id: md-icons-and-react-icons) -// -// We're using react-icons for the React version of the web app -// since it has a large number of icons. However, those aren't -// usable in SvelteKit as the icons are React components. -// -// So we also use Material Design icons which are exposed as SVGs. -// However, this has two drawbacks: -// - It has many fewer languages compared to react-icons. -// - A future version of Material Design icons will remove programming -// language and file type icons. -// https://github.com/Templarian/MaterialDesign/issues/6602 -// -// It would be valuable to explore other icon libraries that can -// be used both by React and SvelteKit or make our own. -import { - mdiCodeJson, - mdiCog, - mdiConsole, - mdiDocker, - mdiEarth, - mdiFileCodeOutline, - mdiFileGifBox, - mdiFileJpgBox, - mdiFilePngBox, - mdiGit, - mdiGraphql, - mdiLanguageC, - mdiLanguageCpp, - mdiLanguageCsharp, - mdiLanguageCss3, - mdiLanguageGo, - mdiLanguageHaskell, - mdiLanguageHtml5, - mdiLanguageJava, - mdiLanguageJavascript, - mdiLanguageKotlin, - mdiLanguageLua, - mdiLanguageMarkdown, - mdiLanguagePhp, - mdiLanguagePython, - mdiLanguageR, - mdiLanguageRuby, - mdiLanguageRust, - mdiLanguageSwift, - mdiLanguageTypescript, - mdiNix, - mdiNpm, - mdiSass, - mdiSvg, - mdiText, -} from '@mdi/js' import { CiSettings, CiTextAlignLeft } from 'react-icons/ci' import { FaCss3Alt, FaSass, FaVuejs } from 'react-icons/fa' import { GoDatabase, GoTerminal } from 'react-icons/go' @@ -123,21 +71,11 @@ import styles from './LanguageIcon.module.scss' export type CustomIcon = ComponentType<{ className?: string }> -export interface ReactIcon { +export interface IconInfo { icon: CustomIcon className: string } -export interface SvgIcon { - path: string - color: string -} - -interface UnifiedIcon { - react: ReactIcon - svg?: SvgIcon['path'] -} - /** * The keys of this map must be present in the list of `languageFilter.ALL_LANGUAGES`. * @@ -146,133 +84,119 @@ interface UnifiedIcon { * See FIXME(id: language-detection) for context on why this map uses the * language as the key instead of something simpler like a file extension. */ -export const FILE_ICONS_BY_LANGUAGE: Map = new Map([ - ['Bash', { react: { icon: GoTerminal, className: styles.defaultIcon }, svg: mdiConsole }], - ['BASIC', { react: { icon: SiVisualbasic, className: styles.defaultIcon } }], - ['C', { react: { icon: SiC, className: styles.blue }, svg: mdiLanguageC }], - ['C++', { react: { icon: SiCplusplus, className: styles.blue }, svg: mdiLanguageCpp }], - ['C#', { react: { icon: SiCsharp, className: styles.blue }, svg: mdiLanguageCsharp }], - ['Clojure', { react: { icon: SiClojure, className: styles.blue } }], - ['CMake', { react: { icon: SiCmake, className: styles.defaultIcon } }], - ['CoffeeScript', { react: { icon: SiCoffeescript, className: styles.defaultIcon } }], +export const FILE_ICONS_BY_LANGUAGE: Map = new Map([ + ['Bash', { icon: GoTerminal, className: styles.defaultIcon }], + ['BASIC', { icon: SiVisualbasic, className: styles.defaultIcon }], + ['C', { icon: SiC, className: styles.blue }], + ['C++', { icon: SiCplusplus, className: styles.blue }], + ['C#', { icon: SiCsharp, className: styles.blue }], + ['Clojure', { icon: SiClojure, className: styles.blue }], + ['CMake', { icon: SiCmake, className: styles.defaultIcon }], + ['CoffeeScript', { icon: SiCoffeescript, className: styles.defaultIcon }], // TODO: Decide icon for CSV? - ['Crystal', { react: { icon: SiCrystal, className: styles.blue } }], - ['CSS', { react: { icon: FaCss3Alt, className: styles.blue }, svg: mdiLanguageCss3 }], - ['D', { react: { icon: SiD, className: styles.red } }], - ['Dart', { react: { icon: SiDart, className: styles.blue } }], - ['Dockerfile', { react: { icon: SiDocker, className: styles.blue }, svg: mdiDocker }], - ['EditorConfig', { react: { icon: SiEditorconfig, className: styles.defaultIcon } }], - ['Elixir', { react: { icon: SiElixir, className: styles.blue } }], - ['Elm', { react: { icon: SiElm, className: styles.blue } }], - ['Emacs Lisp', { react: { icon: SiGnuemacs, className: styles.defaultIcon } }], - ['Erlang', { react: { icon: SiErlang, className: styles.blue } }], - ['Fortran', { react: { icon: SiFortran, className: styles.defaultIcon } }], - ['Fortran Free Form', { react: { icon: SiFortran, className: styles.defaultIcon } }], - ['F#', { react: { icon: SiFsharp, className: styles.blue } }], - ['Git Attributes', { react: { icon: SiGit, className: styles.red }, svg: mdiGit }], - ['Go', { react: { icon: SiGo, className: styles.blue }, svg: mdiLanguageGo }], - ['Go Module', { react: { icon: SiGo, className: styles.pink }, svg: mdiLanguageGo }], - ['Go Checksums', { react: { icon: SiGo, className: styles.pink }, svg: mdiLanguageGo }], - ['Groovy', { react: { icon: SiApachegroovy, className: styles.blue } }], - ['GraphQL', { react: { icon: SiGraphql, className: styles.pink }, svg: mdiGraphql }], - ['Haskell', { react: { icon: SiHaskell, className: styles.blue }, svg: mdiLanguageHaskell }], - ['HTML', { react: { icon: SiHtml5, className: styles.blue }, svg: mdiLanguageHtml5 }], - ['HTML+ECR', { react: { icon: SiCrystal, className: styles.blue } }], - ['HTML+EEX', { react: { icon: SiElixir, className: styles.blue } }], - ['HTML+ERB', { react: { icon: SiRuby, className: styles.blue } }], - ['HTML+PHP', { react: { icon: SiPhp, className: styles.blue } }], - ['HTML+Razor', { react: { icon: SiCsharp, className: styles.blue } }], - ['Ignore List', { react: { icon: CiSettings, className: styles.defaultIcon }, svg: mdiCog }], - ['Java', { react: { icon: GrJava, className: styles.defaultIcon }, svg: mdiLanguageJava }], - ['JavaScript', { react: { icon: SiJavascript, className: styles.yellow }, svg: mdiLanguageJavascript }], - ['Jinja', { react: { icon: SiJinja, className: styles.defaultIcon } }], - ['JSON with Comments', { react: { icon: VscJson, className: styles.defaultIcon } }], - ['JSON', { react: { icon: VscJson, className: styles.defaultIcon }, svg: mdiCodeJson }], - ['JSON5', { react: { icon: VscJson, className: styles.defaultIcon }, svg: mdiCodeJson }], - ['JSONLD', { react: { icon: VscJson, className: styles.defaultIcon }, svg: mdiCodeJson }], - ['Julia', { react: { icon: SiJulia, className: styles.defaultIcon } }], - ['Kotlin', { react: { icon: SiKotlin, className: styles.green }, svg: mdiLanguageKotlin }], - ['LLVM', { react: { icon: SiLlvm, className: styles.gray } }], - ['Lua', { react: { icon: SiLua, className: styles.blue }, svg: mdiLanguageLua }], - ['Markdown', { react: { icon: SiMarkdown, className: styles.blue }, svg: mdiLanguageMarkdown }], - ['Mathematica', { react: { icon: SiWolframmathematica, className: styles.red } }], + ['Crystal', { icon: SiCrystal, className: styles.blue }], + ['CSS', { icon: FaCss3Alt, className: styles.blue }], + ['D', { icon: SiD, className: styles.red }], + ['Dart', { icon: SiDart, className: styles.blue }], + ['Dockerfile', { icon: SiDocker, className: styles.blue }], + ['EditorConfig', { icon: SiEditorconfig, className: styles.defaultIcon }], + ['Elixir', { icon: SiElixir, className: styles.blue }], + ['Elm', { icon: SiElm, className: styles.blue }], + ['Emacs Lisp', { icon: SiGnuemacs, className: styles.defaultIcon }], + ['Erlang', { icon: SiErlang, className: styles.blue }], + ['Fortran', { icon: SiFortran, className: styles.defaultIcon }], + ['Fortran Free Form', { icon: SiFortran, className: styles.defaultIcon }], + ['F#', { icon: SiFsharp, className: styles.blue }], + ['Git Attributes', { icon: SiGit, className: styles.red }], + ['Go', { icon: SiGo, className: styles.blue }], + ['Go Module', { icon: SiGo, className: styles.pink }], + ['Go Checksums', { icon: SiGo, className: styles.pink }], + ['Groovy', { icon: SiApachegroovy, className: styles.blue }], + ['GraphQL', { icon: SiGraphql, className: styles.pink }], + ['Haskell', { icon: SiHaskell, className: styles.blue }], + ['HTML', { icon: SiHtml5, className: styles.blue }], + ['HTML+ECR', { icon: SiCrystal, className: styles.blue }], + ['HTML+EEX', { icon: SiElixir, className: styles.blue }], + ['HTML+ERB', { icon: SiRuby, className: styles.blue }], + ['HTML+PHP', { icon: SiPhp, className: styles.blue }], + ['HTML+Razor', { icon: SiCsharp, className: styles.blue }], + ['Ignore List', { icon: CiSettings, className: styles.defaultIcon }], + ['Java', { icon: GrJava, className: styles.defaultIcon }], + ['JavaScript', { icon: SiJavascript, className: styles.yellow }], + ['Jinja', { icon: SiJinja, className: styles.defaultIcon }], + ['JSON with Comments', { icon: VscJson, className: styles.defaultIcon }], + ['JSON', { icon: VscJson, className: styles.defaultIcon }], + ['JSON5', { icon: VscJson, className: styles.defaultIcon }], + ['JSONLD', { icon: VscJson, className: styles.defaultIcon }], + ['Julia', { icon: SiJulia, className: styles.defaultIcon }], + ['Kotlin', { icon: SiKotlin, className: styles.green }], + ['LLVM', { icon: SiLlvm, className: styles.gray }], + ['Lua', { icon: SiLua, className: styles.blue }], + ['Markdown', { icon: SiMarkdown, className: styles.blue }], + ['Mathematica', { icon: SiWolframmathematica, className: styles.red }], // https://github.com/NCAR/ncl, not tweag/nickel - ['NCL', { react: { icon: ImEarth, className: styles.defaultIcon }, svg: mdiEarth }], - ['Nginx', { react: { icon: SiNginx, className: styles.defaultIcon } }], - ['Nim', { react: { icon: SiNim, className: styles.yellow } }], - ['Nix', { react: { icon: SiNixos, className: styles.gray }, svg: mdiNix }], - ['NPM Config', { react: { icon: SiNpm, className: styles.red }, svg: mdiNpm }], + ['NCL', { icon: ImEarth, className: styles.defaultIcon }], + ['Nginx', { icon: SiNginx, className: styles.defaultIcon }], + ['Nim', { icon: SiNim, className: styles.yellow }], + ['Nix', { icon: SiNixos, className: styles.gray }], + ['NPM Config', { icon: SiNpm, className: styles.red }], // Missing an icon for Objective-C - ['OCaml', { react: { icon: SiOcaml, className: styles.yellow } }], - ['PHP', { react: { icon: SiPhp, className: styles.cyan }, svg: mdiLanguagePhp }], - ['Perl', { react: { icon: SiPerl, className: styles.defaultIcon } }], - ['PLpgSQL', { react: { icon: GoDatabase, className: styles.blue } }], - ['PowerShell', { react: { icon: GoTerminal, className: styles.defaultIcon }, svg: mdiConsole }], + ['OCaml', { icon: SiOcaml, className: styles.yellow }], + ['PHP', { icon: SiPhp, className: styles.cyan }], + ['Perl', { icon: SiPerl, className: styles.defaultIcon }], + ['PLpgSQL', { icon: GoDatabase, className: styles.blue }], + ['PowerShell', { icon: GoTerminal, className: styles.defaultIcon }], // Missing icon for Protobuf - ['PureScript', { react: { icon: SiPurescript, className: styles.defaultIcon } }], - ['Python', { react: { icon: SiPython, className: styles.blue }, svg: mdiLanguagePython }], - ['R', { react: { icon: SiR, className: styles.red }, svg: mdiLanguageR }], - ['Ruby', { react: { icon: SiRuby, className: styles.red }, svg: mdiLanguageRuby }], - ['Rust', { react: { icon: SiRust, className: styles.defaultIcon }, svg: mdiLanguageRust }], - ['Scala', { react: { icon: SiScala, className: styles.red } }], - ['Sass', { react: { icon: FaSass, className: styles.pink }, svg: mdiSass }], - ['SCSS', { react: { icon: FaSass, className: styles.pink } }], - ['SQL', { react: { icon: GoDatabase, className: styles.blue } }], + ['PureScript', { icon: SiPurescript, className: styles.defaultIcon }], + ['Python', { icon: SiPython, className: styles.blue }], + ['R', { icon: SiR, className: styles.red }], + ['Ruby', { icon: SiRuby, className: styles.red }], + ['Rust', { icon: SiRust, className: styles.defaultIcon }], + ['Scala', { icon: SiScala, className: styles.red }], + ['Sass', { icon: FaSass, className: styles.pink }], + ['SCSS', { icon: FaSass, className: styles.pink }], + ['SQL', { icon: GoDatabase, className: styles.blue }], // Missing icon for Starlark - ['Svelte', { react: { icon: SiSvelte, className: styles.red } }], - ['SVG', { react: { icon: SiSvg, className: styles.yellow }, svg: mdiSvg }], - ['Swift', { react: { icon: SiSwift, className: styles.blue }, svg: mdiLanguageSwift }], - ['Terraform', { react: { icon: SiTerraform, className: styles.blue } }], - ['TSX', { react: { icon: SiTypescript, className: styles.blue }, svg: mdiLanguageTypescript }], - ['TypeScript', { react: { icon: SiTypescript, className: styles.blue }, svg: mdiLanguageTypescript }], - ['Text', { react: { icon: CiTextAlignLeft, className: styles.defaultIcon }, svg: mdiText }], + ['Svelte', { icon: SiSvelte, className: styles.red }], + ['SVG', { icon: SiSvg, className: styles.yellow }], + ['Swift', { icon: SiSwift, className: styles.blue }], + ['Terraform', { icon: SiTerraform, className: styles.blue }], + ['TSX', { icon: SiTypescript, className: styles.blue }], + ['TypeScript', { icon: SiTypescript, className: styles.blue }], + ['Text', { icon: CiTextAlignLeft, className: styles.defaultIcon }], // Missing icon for Thrift - ['TOML', { react: { icon: SiToml, className: styles.defaultIcon } }], - ['UnrealScript', { react: { icon: SiUnrealengine, className: styles.defaultIcon } }], - ['VBA', { react: { icon: SiVisualbasic, className: styles.blue } }], - ['VBScript', { react: { icon: SiVisualbasic, className: styles.blue } }], - ['Vim Script', { react: { icon: SiVim, className: styles.defaultIcon } }], - ['Vue', { react: { icon: FaVuejs, className: styles.green } }], - ['WebAssembly', { react: { icon: SiWebassembly, className: styles.blue } }], - ['XML', { react: { icon: CiSettings, className: styles.defaultIcon }, svg: mdiCog }], - ['YAML', { react: { icon: CiSettings, className: styles.defaultIcon }, svg: mdiCog }], - ['Zig', { react: { icon: SiZig, className: styles.yellow } }], + ['TOML', { icon: SiToml, className: styles.defaultIcon }], + ['UnrealScript', { icon: SiUnrealengine, className: styles.defaultIcon }], + ['VBA', { icon: SiVisualbasic, className: styles.blue }], + ['VBScript', { icon: SiVisualbasic, className: styles.blue }], + ['Vim Script', { icon: SiVim, className: styles.defaultIcon }], + ['Vue', { icon: FaVuejs, className: styles.green }], + ['WebAssembly', { icon: SiWebassembly, className: styles.blue }], + ['XML', { icon: CiSettings, className: styles.defaultIcon }], + ['YAML', { icon: CiSettings, className: styles.defaultIcon }], + ['Zig', { icon: SiZig, className: styles.yellow }], ]) -export interface BinaryFileIcon { - react: ReactIcon - svg: string -} - /** * DO NOT add any extensions here for which there are multiple different * file formats in practice which use the same extensions. * * For programming languages, update {@link FILE_ICONS_BY_LANGUAGE}. */ -const BINARY_FILE_ICONS_BY_EXTENSION: Map = new Map([ - ['gif', { react: { icon: MdGif, className: styles.defaultIcon }, svg: mdiFileGifBox }], - ['giff', { react: { icon: MdGif, className: styles.defaultIcon }, svg: mdiFileGifBox }], - ['jpg', { react: { icon: SiJpeg, className: styles.yellow }, svg: mdiFileJpgBox }], - ['jpeg', { react: { icon: SiJpeg, className: styles.yellow }, svg: mdiFileJpgBox }], - ['png', { react: { icon: PiFilePngLight, className: styles.defaultIcon }, svg: mdiFilePngBox }], +const BINARY_FILE_ICONS_BY_EXTENSION: Map = new Map([ + ['gif', { icon: MdGif, className: styles.defaultIcon }], + ['giff', { icon: MdGif, className: styles.defaultIcon }], + ['jpg', { icon: SiJpeg, className: styles.yellow }], + ['jpeg', { icon: SiJpeg, className: styles.yellow }], + ['png', { icon: PiFilePngLight, className: styles.defaultIcon }], ]) -// See TODO(id: md-icons-and-react-icons) for context -export interface IconInfo { - // For use in the React webapp - react: ReactIcon - - // For use in the SvelteKit rewrite - svg: SvgIcon -} - /** * * See FIXME(id: language-detection) for context on why this takes a @@ -285,58 +209,5 @@ export interface IconInfo { */ export function getFileIconInfo(path: string, language: string): IconInfo | undefined { const extension = path.split('.').at(-1) ?? '' - const icon1 = BINARY_FILE_ICONS_BY_EXTENSION.get(extension) - - if (icon1 !== undefined) { - return { - react: icon1.react, - svg: { path: icon1.svg, color: classNameToColor(icon1.react.className) }, - } - } - - const icon2 = FILE_ICONS_BY_LANGUAGE.get(language) - - if (icon2 !== undefined) { - return { - react: icon2.react, - svg: { path: icon2.svg ?? mdiFileCodeOutline, color: classNameToColor(icon2.react.className) }, - } - } - - return -} - -const BLUE = 'var(--blue)' -const PINK = 'var(--pink)' -const YELLOW = 'var(--yellow)' -const RED = 'var(--red)' -const GREEN = 'var(--green)' -const CYAN = 'var(--blue)' -const GRAY = 'var(--gray-05)' - -function classNameToColor(name: string): string { - switch (name) { - case styles.blue: { - return BLUE - } - case styles.red: { - return RED - } - case styles.yellow: { - return YELLOW - } - case styles.pink: { - return PINK - } - case styles.green: { - return GREEN - } - case styles.cyan: { - return CYAN - } - case styles.gray: - default: { - return GRAY - } - } + return BINARY_FILE_ICONS_BY_EXTENSION.get(extension) ?? FILE_ICONS_BY_LANGUAGE.get(language) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a42de46c6f1..dea3349d422 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1639,9 +1639,18 @@ importers: '@graphql-typed-document-node/core': specifier: ^3.2.0 version: 3.2.0(graphql@15.4.0) + '@iconify-json/devicon-plain': + specifier: ^1.1.42 + version: 1.1.42 '@iconify-json/lucide': specifier: ^1.1.188 version: 1.1.188 + '@iconify-json/ph': + specifier: ^1.1.13 + version: 1.1.13 + '@iconify-json/simple-icons': + specifier: ^1.1.104 + version: 1.1.104 '@playwright/test': specifier: 1.42.1 version: 1.42.1 @@ -5508,12 +5517,30 @@ packages: resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} dev: true + /@iconify-json/devicon-plain@1.1.42: + resolution: {integrity: sha512-NGCuw6BHn8lpjbVVa4XvYnprhWZ6IWkr6O19b69ySpDyi8QF3lh/Pe5As7C6eAG1KsblxQFqwcztgILOOmZqAA==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + /@iconify-json/lucide@1.1.188: resolution: {integrity: sha512-yJNoU7vX11OvdeSKBiAvmbk/etAq2HPCZQkZX1U687NZhn8dTfx1PfyNhPxtJilrd288XGDKK+NQgygcZ+Ho4g==} dependencies: '@iconify/types': 2.0.0 dev: true + /@iconify-json/ph@1.1.13: + resolution: {integrity: sha512-xtM4JJ63HCKj09WRqrBswXiHrpliBlqboWSZH8odcmqYXbvIFceU9/Til4V+MQr6+MoUC+KB72cxhky2+A6r/g==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + + /@iconify-json/simple-icons@1.1.104: + resolution: {integrity: sha512-986r56QHsIqa84MdGpV+tUJVhJjDXFb1OfFJZKMDiuQOi0CacK5QsM3if5RNjmT9WhrQtm5uTRHy4/4u7Cb9oQ==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + /@iconify/types@2.0.0: resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} dev: true