From 49e99bf0d9e670fd5ecc6a1ac9ddbf76779e6f7e Mon Sep 17 00:00:00 2001 From: Thomas Osmonson Date: Mon, 13 Apr 2020 17:57:01 -0500 Subject: [PATCH] feat: codebox and highlighter --- packages/ui/package.json | 5 +- packages/ui/src/codebox/index.tsx | 31 +++ packages/ui/src/common-types.ts | 9 + packages/ui/src/highlighter/index.tsx | 123 +++++++++++ .../src/highlighter/language-definition.tsx | 197 ++++++++++++++++++ packages/ui/src/highlighter/prism-theme.ts | 76 +++++++ packages/ui/src/highlighter/types.ts | 89 ++++++++ packages/ui/src/index.ts | 2 + packages/ui/src/tooltip/hooks.tsx | 4 + packages/ui/src/utils/index.tsx | 3 + yarn.lock | 48 +++++ 11 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 packages/ui/src/codebox/index.tsx create mode 100644 packages/ui/src/highlighter/index.tsx create mode 100644 packages/ui/src/highlighter/language-definition.tsx create mode 100644 packages/ui/src/highlighter/prism-theme.ts create mode 100644 packages/ui/src/highlighter/types.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index 9d90a15..5bff7da 100755 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,11 +16,14 @@ "@styled-system/should-forward-prop": "^5.1.5", "@types/color": "^3.0.1", "@types/flushable": "^1.0.1", + "@types/prismjs": "^1.16.0", "@types/styled-components": "^5.1.0", "@types/styled-system": "^5.1.6", "@types/styled-system__css": "^5.0.8", "color": "3.1.2", "flushable": "^1.0.0", + "prism-react-renderer": "^1.0.2", + "prismjs": "^1.20.0", "prop-types": "^15.7.2", "react-spring": "8.0.27", "styled-system": "5.1.5", @@ -59,7 +62,7 @@ "fs-extra": "9.0.0", "glob": "7.1.6", "path": "0.12.7", - "prettier": "^2.0.4", + "prettier": "^2.0.5", "react": "^16.13.1", "react-dom": "^16.13.1", "rimraf": "3.0.2", diff --git a/packages/ui/src/codebox/index.tsx b/packages/ui/src/codebox/index.tsx new file mode 100644 index 0000000..7ca82ae --- /dev/null +++ b/packages/ui/src/codebox/index.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { Highlighter } from '../highlighter'; +import { Box, BoxProps } from '../box'; + +export const CodeBlock = ({ + code, + showLineNumbers, + style = {}, + ...rest +}: { code: string; showLineNumbers?: boolean } & BoxProps) => { + const [editorCode] = useState(code?.toString().trim()); + + return ( + + + + ); +}; diff --git a/packages/ui/src/common-types.ts b/packages/ui/src/common-types.ts index 6a568e8..f44452c 100755 --- a/packages/ui/src/common-types.ts +++ b/packages/ui/src/common-types.ts @@ -1 +1,10 @@ export type Omit = Pick>; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace JSX { + interface IntrinsicAttributes { + css?: any; + } + } +} diff --git a/packages/ui/src/highlighter/index.tsx b/packages/ui/src/highlighter/index.tsx new file mode 100644 index 0000000..01c6c95 --- /dev/null +++ b/packages/ui/src/highlighter/index.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import Prism from 'prismjs'; +import Highlight from 'prism-react-renderer'; +import { Box } from '../box'; +import { Flex } from '../flex'; +import { useTheme } from '../theme-provider'; + +import { GrammaticalToken, GetGrammaticalTokenProps, RenderProps, Language } from './types'; +import { theme } from './prism-theme'; +import { startPad } from '../utils'; + +const lineNumberWidth = 60; +const getLineNumber = (n: number, length: number) => startPad(n, length.toString().length + 1); + +const Tokens = ({ + tokens, + getTokenProps, + ...rest +}: { + tokens: GrammaticalToken[]; + getTokenProps: GetGrammaticalTokenProps; +}) => { + const bsTheme = useTheme(); + const pl = `calc(${lineNumberWidth}px + ${(bsTheme as any).sizes['base'] || '16px'})`; + + return ( + + {tokens.map((token, key) => ( + + ))} + + ); +}; +const LineNumber = ({ number, length, ...rest }: { number: number; length: number }) => ( + + {getLineNumber(number, length)} + +); + +const Line = ({ + tokens, + getTokenProps, + index, + length, + showLineNumbers, + ...rest +}: { + tokens: GrammaticalToken[]; + index: number; + length: number; + getTokenProps: GetGrammaticalTokenProps; + showLineNumbers?: boolean; +}) => { + return ( + + {showLineNumbers ? : null} + + + ); +}; + +const Lines = ({ + tokens: lines, + getLineProps, + getTokenProps, + className, + showLineNumbers, +}: { showLineNumbers?: boolean } & RenderProps) => { + return ( + + + {lines.map((tokens, i) => ( + + ))} + + + ); +}; + +interface HighlighterProps { + code: string; + language?: Language; + showLineNumbers?: boolean; +} + +export const Highlighter = ({ code, language = 'lisp', showLineNumbers }: HighlighterProps) => { + return ( + + {props => } + + ); +}; diff --git a/packages/ui/src/highlighter/language-definition.tsx b/packages/ui/src/highlighter/language-definition.tsx new file mode 100644 index 0000000..c2bc683 --- /dev/null +++ b/packages/ui/src/highlighter/language-definition.tsx @@ -0,0 +1,197 @@ +// @ts-nocheck +// prismjs/components/prism-lisp.js +// this file is here to fix a bug with ssr and prism not loading/refreshing after client hydration +// todo: convert to ts, and define clarity language + +import Prism from 'prismjs'; + +(function (Prism) { + // Functions to construct regular expressions + // simple form + // e.g. (interactive ... or (interactive) + function simple_form(name) { + return RegExp('(\\()' + name + '(?=[\\s\\)])'); + } + // booleans and numbers + function primitive(pattern) { + return RegExp('([\\s([])' + pattern + '(?=[\\s)])'); + } + + // Patterns in regular expressions + + // Symbol name. See https://www.gnu.org/software/emacs/manual/html_node/elisp/Symbol-Type.html + // & and : are excluded as they are usually used for special purposes + const symbol = '[-+*/_~!@$%^=<>{}\\w]+'; + // symbol starting with & used in function arguments + const marker = '&' + symbol; + // Open parenthesis for look-behind + const par = '(\\()'; + const endpar = '(?=\\))'; + // End the pattern with look-ahead space + const space = '(?=\\s)'; + + const language = { + // Three or four semicolons are considered a heading. + // See https://www.gnu.org/software/emacs/manual/html_node/elisp/Comment-Tips.html + heading: { + pattern: /;;;.*/, + alias: ['comment', 'title'], + }, + comment: /;.*/, + string: { + pattern: /"(?:[^"\\]|\\.)*"/, + greedy: true, + inside: { + argument: /[-A-Z]+(?=[.,\s])/, + symbol: RegExp('`' + symbol + "'"), + }, + }, + 'quoted-symbol': { + pattern: RegExp("#?'" + symbol), + alias: ['variable', 'symbol'], + }, + 'lisp-property': { + pattern: RegExp(':' + symbol), + alias: 'property', + }, + splice: { + pattern: RegExp(',@?' + symbol), + alias: ['symbol', 'variable'], + }, + keyword: [ + { + pattern: RegExp( + par + + '(?:(?:lexical-)?let\\*?|(?:cl-)?letf|if|when|while|unless|cons|cl-loop|and|or|not|cond|setq|error|message|null|require|provide|use-package)' + + space + ), + lookbehind: true, + }, + { + pattern: RegExp(par + '(?:for|do|collect|return|finally|append|concat|in|by)' + space), + lookbehind: true, + }, + ], + declare: { + pattern: simple_form('declare'), + lookbehind: true, + alias: 'keyword', + }, + interactive: { + pattern: simple_form('interactive'), + lookbehind: true, + alias: 'keyword', + }, + boolean: { + pattern: primitive('(?:t|nil)'), + lookbehind: true, + }, + number: { + pattern: primitive('[-+]?\\d+(?:\\.\\d*)?'), + lookbehind: true, + }, + defvar: { + pattern: RegExp(par + 'def(?:var|const|custom|group)\\s+' + symbol), + lookbehind: true, + inside: { + keyword: /^def[a-z]+/, + variable: RegExp(symbol), + }, + }, + defun: { + pattern: RegExp(par + '(?:cl-)?(?:defun\\*?|defmacro)\\s+' + symbol + '\\s+\\([\\s\\S]*?\\)'), + lookbehind: true, + inside: { + keyword: /^(?:cl-)?def\S+/, + // See below, this property needs to be defined later so that it can + // reference the language object. + arguments: null, + function: { + pattern: RegExp('(^\\s)' + symbol), + lookbehind: true, + }, + punctuation: /[()]/, + }, + }, + lambda: { + pattern: RegExp(par + 'lambda\\s+\\((?:&?' + symbol + '\\s*)*\\)'), + lookbehind: true, + inside: { + keyword: /^lambda/, + // See below, this property needs to be defined later so that it can + // reference the language object. + arguments: null, + punctuation: /[()]/, + }, + }, + car: { + pattern: RegExp(par + symbol), + lookbehind: true, + }, + punctuation: [ + // open paren, brackets, and close paren + /(?:['`,]?\(|[)\[\]])/, + // cons + { + pattern: /(\s)\.(?=\s)/, + lookbehind: true, + }, + ], + }; + + const arg = { + 'lisp-marker': RegExp(marker), + rest: { + argument: { + pattern: RegExp(symbol), + alias: 'variable', + }, + varform: { + pattern: RegExp(par + symbol + '\\s+\\S[\\s\\S]*' + endpar), + lookbehind: true, + inside: { + string: language.string, + boolean: language.boolean, + number: language.number, + symbol: language.symbol, + punctuation: /[()]/, + }, + }, + }, + }; + + const forms = '\\S+(?:\\s+\\S+)*'; + + const arglist = { + pattern: RegExp(par + '[\\s\\S]*' + endpar), + lookbehind: true, + inside: { + 'rest-vars': { + pattern: RegExp('&(?:rest|body)\\s+' + forms), + inside: arg, + }, + 'other-marker-vars': { + pattern: RegExp('&(?:optional|aux)\\s+' + forms), + inside: arg, + }, + keys: { + pattern: RegExp('&key\\s+' + forms + '(?:\\s+&allow-other-keys)?'), + inside: arg, + }, + argument: { + pattern: RegExp(symbol), + alias: 'variable', + }, + punctuation: /[()]/, + }, + }; + + language['lambda'].inside.arguments = arglist; + language['defun'].inside.arguments = Prism.util.clone(arglist); + language['defun'].inside.arguments.inside.sublist = arglist; + + Prism.languages.lisp = language; + Prism.languages.elisp = language; + Prism.languages.emacs = language; + Prism.languages['emacs-lisp'] = language; +})(Prism); diff --git a/packages/ui/src/highlighter/prism-theme.ts b/packages/ui/src/highlighter/prism-theme.ts new file mode 100644 index 0000000..1f6f1ff --- /dev/null +++ b/packages/ui/src/highlighter/prism-theme.ts @@ -0,0 +1,76 @@ +import { PrismTheme } from 'prism-react-renderer'; + +export const theme: PrismTheme = { + plain: { + color: '#fff', + backgroundColor: 'transparent', + }, + styles: [ + { + types: ['prolog'], + style: { + color: 'rgb(0, 0, 128)', + }, + }, + { + types: ['comment', 'punctuation'], + style: { + color: 'rgb(106, 153, 85)', + }, + }, + { + types: ['builtin', 'tag', 'changed', 'function', 'keyword'], + style: { + color: 'rgb(86, 156, 214)', + }, + }, + { + types: ['number', 'variable', 'inserted'], + style: { + color: '#A58FFF', + }, + }, + { + types: ['operator'], + style: { + color: 'rgb(212, 212, 212)', + }, + }, + { + types: ['constant'], + style: { + color: 'rgb(100, 102, 149)', + }, + }, + { + types: ['attr-name'], + style: { + color: 'rgb(156, 220, 254)', + }, + }, + { + types: ['car'], + style: { + color: 'rgb(156, 220, 254)', + }, + }, + { + types: ['deleted', 'string'], + style: { + color: '#FF7B48', + }, + }, + { + types: ['class-name'], + style: { + color: 'rgb(78, 201, 176)', + }, + }, + { + types: ['char'], + style: { + color: '#FF7B48', + }, + }, + ], +}; diff --git a/packages/ui/src/highlighter/types.ts b/packages/ui/src/highlighter/types.ts new file mode 100644 index 0000000..326c09b --- /dev/null +++ b/packages/ui/src/highlighter/types.ts @@ -0,0 +1,89 @@ +import * as React from 'react'; + +export interface GrammaticalToken { + types: string[]; + content: string; + empty?: boolean; +} + +export interface StyleObj { + [key: string]: string | number | null; +} + +export interface GrammaticalTokenOutputProps { + key?: React.Key; + style?: StyleObj; + className: string; + children: string; + [otherProp: string]: any; +} + +export interface GrammaticalTokenInputProps { + key?: React.Key; + style?: StyleObj; + className?: string; + token: GrammaticalToken; + [otherProp: string]: any; +} + +export interface LineInputProps { + key?: React.Key; + style?: StyleObj; + className?: string; + line: GrammaticalToken[]; + [otherProp: string]: any; +} + +export interface LineOutputProps { + key?: React.Key; + style?: StyleObj; + className: string; + [otherProps: string]: any; +} + +export interface RenderProps { + tokens: GrammaticalToken[][]; + className: string; + style: StyleObj; + getLineProps: (input: LineInputProps) => LineOutputProps; + getTokenProps: (input: GrammaticalTokenInputProps) => GrammaticalTokenOutputProps; +} + +export type GetGrammaticalTokenProps = ( + input: GrammaticalTokenInputProps +) => GrammaticalTokenOutputProps; + +export type Language = + | 'markup' + | 'bash' + | 'clike' + | 'c' + | 'cpp' + | 'css' + | 'javascript' + | 'jsx' + | 'coffeescript' + | 'actionscript' + | 'css-extr' + | 'diff' + | 'git' + | 'go' + | 'graphql' + | 'handlebars' + | 'json' + | 'less' + | 'lisp' + | 'makefile' + | 'markdown' + | 'objectivec' + | 'ocaml' + | 'python' + | 'reason' + | 'sass' + | 'scss' + | 'sql' + | 'stylus' + | 'tsx' + | 'typescript' + | 'wasm' + | 'yaml'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 85f1e8c..1d2acb6 100755 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -24,3 +24,5 @@ export * from './tooltip'; export * from './popper'; export * from './portal'; export * from './hooks'; +export * from './highlighter'; +export * from './codebox'; diff --git a/packages/ui/src/tooltip/hooks.tsx b/packages/ui/src/tooltip/hooks.tsx index 40a27eb..6e303ea 100644 --- a/packages/ui/src/tooltip/hooks.tsx +++ b/packages/ui/src/tooltip/hooks.tsx @@ -84,6 +84,10 @@ export interface UseTooltipProps { * @default 10 ( = 10px ) */ arrowSize?: UsePopperProps['arrowSize']; + /** + * The label, we check if it changes to refresh the position of the tooltip + */ + label?: string; } export function useTooltip(props: UseTooltipProps = {}) { diff --git a/packages/ui/src/utils/index.tsx b/packages/ui/src/utils/index.tsx index 1427859..a4ec421 100755 --- a/packages/ui/src/utils/index.tsx +++ b/packages/ui/src/utils/index.tsx @@ -377,3 +377,6 @@ export function mapResponsive(prop: any, mapper: (val: any) => any) { return null; } + +export const startPad = (n: number, z = 2, s = '0') => + (n + '').length <= z ? ['', '-'][+(n < 0)] + (s.repeat(z) + Math.abs(n)).slice(-1 * z) : n + ''; diff --git a/yarn.lock b/yarn.lock index 7b869aa..bc7740f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3744,6 +3744,11 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== +"@types/prismjs@^1.16.0": + version "1.16.0" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.0.tgz#4328c9f65698e59f4feade8f4e5d928c748fd643" + integrity sha512-mEyuziLrfDCQ4juQP1k706BUU/c8OGn/ZFl69AXXY6dStHClKX4P+N8+rhqpul1vRDA2VOygzMRSJJZHyDEOfw== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -6389,6 +6394,15 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== +clipboard@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" + integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -7410,6 +7424,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -9528,6 +9547,13 @@ globrex@^0.1.1: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" @@ -15126,6 +15152,11 @@ prettier@^1.18.2, prettier@^1.19.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" @@ -15176,6 +15207,13 @@ prism-react-renderer@^1.0.1, prism-react-renderer@^1.0.2: resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.0.2.tgz#3bb9a6a42f76fc049b03266298c7068fdd4b7ea9" integrity sha512-0++pJyRfu4v2OxI/Us/5RLui9ESDkTiLkVCtKuPZYdpB8UQWJpnJQhPrWab053XtsKW3oM0sD69uJ6N9exm1Ag== +prismjs@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03" + integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ== + optionalDependencies: + clipboard "^2.0.0" + private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -16762,6 +16800,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + selfsigned@^1.10.7: version "1.10.7" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" @@ -17971,6 +18014,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tiny-glob@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.6.tgz#9e056e169d9788fe8a734dfa1ff02e9b92ed7eda"