├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── declaration.d.ts ├── demo.gif ├── index.ts ├── package.json ├── src ├── ClassNameDiagnostic.ts ├── TailwindClient.ts ├── TailwindErrorChecker.ts ├── extractClassNames.ts └── getTailwindConfigPath.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | 12 | # CSS - https://developer.mozilla.org/ja/docs/Web/CSS 13 | # Sass(SCSS) - http://sass-lang.com/ 14 | # Stylus - https://learnboost.github.io/stylus/ 15 | # PostCSS - https://postcss.org/ 16 | [*.{css,scss,sass,styl,pcss}] 17 | indent_style = space 18 | indent_size = 2 19 | trim_trailing_whitespace = true 20 | 21 | # Handlebars.js - http://handlebarsjs.com/ 22 | [*.hbs] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | # JavaScript - https://developer.mozilla.org/ja/docs/Web/JavaScript 27 | # TypeScript - https://www.typescriptlang.org/ 28 | # React - https://reactjs.org/ 29 | # Vue - https://vuejs.org/ 30 | [*.{js,ts,jsx,tsx,vue}] 31 | indent_style = space 32 | indent_size = 2 33 | 34 | # JSON - http://json.org/ 35 | # Composer - https://getcomposer.org/doc/04-schema.md 36 | [{*.json,composer.lock}] 37 | indent_style = space 38 | indent_size = 4 39 | 40 | # NPM - https://docs.npmjs.com/files/package.json 41 | # TypeScript - https://www.typescriptlang.org/ 42 | # Stylint - https://rosspatton.github.io/stylint/ 43 | # prettier - https://prettier.io/ 44 | # ESlint - https://eslint.org/ 45 | # VSCode Settings - https://code.visualstudio.com/docs/getstarted/settings 46 | [{package.json,tsconfig.json,.stylintrc,.prettierrc,.eslintrc,.vscode/settings.json}] 47 | indent_style = space 48 | indent_size = 2 49 | 50 | # PHP - http://php.net/ 51 | [*.php] 52 | indent_style = space 53 | indent_size = 4 54 | 55 | # Python - https://www.python.org/ 56 | [*.py] 57 | indent_style = space 58 | indent_size = 4 59 | 60 | # Ruby - https://www.ruby-lang.org/ 61 | # Rake - https://github.com/ruby/rake 62 | [{*.rb,*.rake,Rakefile}] 63 | indent_style = space 64 | indent_size = 2 65 | 66 | # Shell script (bash) - https://www.gnu.org/software/bash/manual/bash.html 67 | [*.sh] 68 | indent_style = space 69 | indent_size = 4 70 | 71 | # SQL (MySQL) - https://www.mysql.com/ 72 | [*.sql] 73 | indent_style = space 74 | indent_size = 2 75 | 76 | # Smarty 2 -http://www.smarty.net/docsv2/en/ 77 | [*.tpl] 78 | indent_style = space 79 | indent_size = 4 80 | 81 | # XHTML - http://www.w3.org/TR/xhtml1/ 82 | [*.xhtml] 83 | indent_style = space 84 | indent_size = 4 85 | 86 | # YAML - http://yaml.org/ 87 | [{*.yml,*.yaml}] 88 | indent_style = space 89 | indent_size = 2 90 | 91 | # p(ixi)v 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | yarn-error.log 2 | node_modules 3 | **/*.js 4 | tmp 5 | dist 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # irontail 2 | 3 | [TypeScript Language Service Plugin](https://github.com/microsoft/TypeScript/wiki/Using-the-Language-Service-API) to make classNames() type-safe for Tailwind.css 4 | 5 | ![PoC](demo.gif) 6 | 7 | ## How to use 8 | 9 | ``` 10 | // npm 11 | npm i --save-dev irontail 12 | 13 | // yarn 14 | yarn add -D irontail 15 | ``` 16 | 17 | Just add to tsconfig.json 18 | 19 | ``` 20 | "plugins": [ 21 | { 22 | "name": "irontail", 23 | } 24 | ] 25 | ``` 26 | 27 | ## Why ? 28 | 29 | Q. There is already [muhammadsammy/tailwindcss-classnames](https://github.com/muhammadsammy/tailwindcss-classnames). Why is this plugin useful? 30 | 31 | A. 32 | `irontail` just provides error checker out of the box, instead of type generator. 33 | 34 | When you use type generator, 35 | 36 | - You have to pre-generate typings with CLI 37 | - Intellisense [becomes inferior](https://twitter.com/f_subal/status/1292106949161414656) to existing extensions (like [bradlc.vscode-tailwindcss](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)) 38 | 39 | ## Limitation 40 | 41 | - Currently, **JIT mode is not supported** ( it will break ). 42 | 43 | ## References 44 | 45 | - [muhammadsammy/tailwindcss-classnames](https://github.com/muhammadsammy/tailwindcss-classnames): The prior art 46 | - [Quramy/ts-graphql-plugin](https://github.com/Quramy/ts-graphql-plugin): Used as reference for the project structure 47 | -------------------------------------------------------------------------------- /declaration.d.ts: -------------------------------------------------------------------------------- 1 | declare module "dset"; 2 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsubal/irontail/bb35cce8abf5fde5e644e7039988cc560de3b1f4/demo.gif -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as ts from "typescript/lib/tsserverlibrary"; 3 | import { TailwindErrorChecker } from "./src/TailwindErrorChecker"; 4 | import { getTailwindConfigPath } from "./src/getTailwindConfigPath"; 5 | 6 | const factory: ts.server.PluginModuleFactory = () => ({ 7 | create({ project, languageService: parent }) { 8 | const checker = new TailwindErrorChecker(project); 9 | checker.loadCss(); 10 | 11 | fs.watch(getTailwindConfigPath(project), () => { 12 | checker.loadCss(); 13 | }); 14 | 15 | return { 16 | ...parent, 17 | 18 | getSemanticDiagnostics(fileName: string) { 19 | const diagnostics = parent.getSemanticDiagnostics(fileName); 20 | if (TailwindErrorChecker.isPending) { 21 | return diagnostics; 22 | } 23 | 24 | const program = parent.getProgram(); 25 | if (!program) { 26 | throw new Error("language service host does not have program!"); 27 | } 28 | 29 | const source = program.getSourceFile(fileName); 30 | if (!source) { 31 | throw new Error("No source file: " + fileName); 32 | } 33 | 34 | const tailwindDiagnostics = checker.getTailwindDiagnostics(source); 35 | 36 | return [...tailwindDiagnostics, ...diagnostics]; 37 | }, 38 | 39 | dispose() { 40 | checker.stop(); 41 | }, 42 | }; 43 | }, 44 | }); 45 | 46 | export = factory; 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "irontail", 3 | "version": "0.1.4", 4 | "main": "dist/index.js", 5 | "author": "fsubal ", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "tsc -p .", 9 | "prepublishOnly": "yarn build" 10 | }, 11 | "directories": { 12 | "dist": "dist" 13 | }, 14 | "files": [ 15 | "README.md", 16 | "demo.gif", 17 | "dist", 18 | "package.json", 19 | "yarn.lock" 20 | ], 21 | "devDependencies": { 22 | "@types/classnames": "^2.2.10", 23 | "@types/dlv": "^1.1.2", 24 | "@types/glob": "^7.1.3", 25 | "@types/node": "^14.0.27", 26 | "@types/serialize-javascript": "^4.0.0", 27 | "tailwindcss": "^1.6.2", 28 | "typescript": "^3.9.7" 29 | }, 30 | "dependencies": { 31 | "dlv": "^1.1.3", 32 | "dset": "^2.0.1", 33 | "glob": "^7.1.6", 34 | "import-from": "^3.0.0", 35 | "postcss": "^7.0.32", 36 | "resolve-from": "^5.0.0", 37 | "serialize-javascript": "^4.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ClassNameDiagnostic.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript"; 2 | import { CallExpression, JsxAttribute } from "typescript/lib/tsserverlibrary"; 3 | 4 | export interface EachDiagnostic { 5 | start: number; 6 | length: number; 7 | messageText: string; 8 | } 9 | 10 | interface SuspiciousChildren { 11 | pos: number; 12 | end: number; 13 | className: string; 14 | } 15 | 16 | const FUNCTION_NAMES = ["classNames", "classnames", "clsx"] as ts.__String[]; 17 | 18 | function isClassNameCall(node: ts.Node): node is CallExpression { 19 | if (!ts.isCallExpression(node)) { 20 | return false; 21 | } 22 | 23 | if (!ts.isIdentifier(node.expression)) { 24 | return false; 25 | } 26 | 27 | return FUNCTION_NAMES.includes(node.expression.escapedText); 28 | } 29 | 30 | function isJsxClassNameAttribute(node: ts.Node): node is JsxAttribute { 31 | if (!ts.isJsxAttribute(node)) { 32 | return false; 33 | } 34 | 35 | return node.name.escapedText === "className"; 36 | } 37 | 38 | export class ClassNameDiagnostic { 39 | private diagnostics: EachDiagnostic[] = []; 40 | 41 | constructor( 42 | private readonly sourceFile: ts.SourceFile, 43 | private readonly extractedClassNames: Record 44 | ) {} 45 | 46 | toArray(): EachDiagnostic[] { 47 | ts.transform(this.sourceFile, [this.transformer]); 48 | 49 | return this.diagnostics; 50 | } 51 | 52 | private transformer = (context: ts.TransformationContext) => ( 53 | rootNode: ts.Node 54 | ): ts.Node => { 55 | const visit = (node: ts.Node) => { 56 | if (isClassNameCall(node)) { 57 | this.getSuspiciousArguments(node).forEach(this.handleFound); 58 | 59 | return node; 60 | } else if (isJsxClassNameAttribute(node)) { 61 | this.getSuspiciousAttributes(node).forEach(this.handleFound); 62 | 63 | return node; 64 | } 65 | 66 | return ts.visitEachChild(node, visit, context); 67 | }; 68 | 69 | return ts.visitNode(rootNode, visit); 70 | }; 71 | 72 | private handleFound = ({ className, pos, end }: SuspiciousChildren) => { 73 | this.diagnostics.push({ 74 | start: pos, 75 | length: end - pos, 76 | messageText: `Unknown tailwind class: "${className}"`, 77 | }); 78 | }; 79 | 80 | private getSuspiciousArguments(node: ts.CallExpression) { 81 | const children: SuspiciousChildren[] = []; 82 | 83 | /** 84 | * @see https://github.com/JedWatson/classnames/blob/master/tests/index.js 85 | * 86 | * NOTICE: Not all cases are supported. 87 | * For example, object/function using .toString() is not supported. 88 | * @see https://github.com/JedWatson/classnames/blob/bbf03f73f30/tests/index.js#L94 89 | */ 90 | node.arguments.forEach(function walk(argument) { 91 | /** 92 | * classNames('hoge') 93 | */ 94 | if (ts.isStringLiteral(argument)) { 95 | children.push({ className: argument.text, ...argument }); 96 | } 97 | 98 | /** 99 | * classNames(true && 'hoge') 100 | */ 101 | if (ts.isBinaryExpression(argument)) { 102 | if (ts.isStringLiteral(argument.right)) { 103 | children.push({ 104 | className: argument.right.text, 105 | ...argument.right, 106 | }); 107 | } 108 | } 109 | 110 | /** 111 | * classNames(true ? 'hoge' : 'moge') 112 | */ 113 | if (ts.isConditionalExpression(argument)) { 114 | /** 115 | * classNames(true ? 'hoge' : 'moge') 116 | * ^^^^^^ 117 | */ 118 | if (ts.isStringLiteral(argument.whenTrue)) { 119 | children.push({ 120 | className: argument.whenTrue.text, 121 | ...argument.whenTrue, 122 | }); 123 | } 124 | 125 | /** 126 | * classNames(true ? 'hoge' : 'moge') 127 | * ^^^^^^ 128 | */ 129 | if (ts.isStringLiteral(argument.whenFalse)) { 130 | children.push({ 131 | className: argument.whenFalse.text, 132 | ...argument.whenFalse, 133 | }); 134 | } 135 | } 136 | 137 | /** 138 | * classNames({ hoge: true }) 139 | * 140 | * NOTICE: followings are not supported 141 | * - computed property ( { ['hoge']: true } ) 142 | * - private property ( { #hoge: true } ) 143 | * - numeric literal ( { 1: true } ) 144 | */ 145 | if (ts.isObjectLiteralExpression(argument)) { 146 | argument.properties.forEach((property) => { 147 | /** 148 | * classNames({ 'hoge': true }) 149 | */ 150 | if (property.name && ts.isStringLiteral(property.name)) { 151 | children.push({ 152 | className: property.name.text, 153 | ...property.name, 154 | }); 155 | } 156 | 157 | /** 158 | * classNames({ hoge: true }) 159 | * or 160 | * classNames({ hoge }) 161 | */ 162 | if (property.name && ts.isIdentifier(property.name)) { 163 | children.push({ 164 | className: property.name.escapedText.toString(), 165 | ...property.name, 166 | }); 167 | } 168 | }); 169 | } 170 | 171 | /** 172 | * classNames(['hoge'], [{ moge: true }]) 173 | */ 174 | if (ts.isArrayLiteralExpression(argument)) { 175 | argument.elements.forEach(walk); 176 | } 177 | }); 178 | 179 | return children.filter( 180 | ({ className }) => !this.extractedClassNames.hasOwnProperty(className) 181 | ); 182 | } 183 | 184 | private getSuspiciousAttributes(node: ts.JsxAttribute) { 185 | const children: SuspiciousChildren[] = []; 186 | 187 | if (node.initializer) { 188 | /** 189 | *
190 | */ 191 | if (ts.isStringLiteral(node.initializer)) { 192 | children.push({ 193 | className: node.initializer.text, 194 | ...node.initializer, 195 | }); 196 | } 197 | 198 | /** 199 | *
200 | */ 201 | if (ts.isJsxExpression(node.initializer)) { 202 | if ( 203 | node.initializer.expression && 204 | ts.isStringLiteral(node.initializer.expression) 205 | ) { 206 | children.push({ 207 | className: node.initializer.expression.text, 208 | ...node.initializer.expression, 209 | }); 210 | } 211 | } 212 | } 213 | 214 | return children.filter( 215 | ({ className }) => !this.extractedClassNames.hasOwnProperty(className) 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/TailwindClient.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript/lib/tsserverlibrary"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import extractClassNames from "./extractClassNames"; 5 | import importFrom = require("import-from"); 6 | import resolveFrom = require("resolve-from"); 7 | import type { Result } from "postcss"; 8 | import { getTailwindConfigPath } from "./getTailwindConfigPath"; 9 | 10 | export class TailwindClient { 11 | static currentClasses?: Record; 12 | 13 | static lastUpdatedAt?: number; 14 | 15 | getLastUpdatedAt() { 16 | return fs.statSync(this.getConfigPath()).mtime.getTime(); 17 | } 18 | 19 | isFresh() { 20 | return TailwindClient.lastUpdatedAt === this.getLastUpdatedAt(); 21 | } 22 | 23 | constructor(private readonly project: ts.server.Project) {} 24 | 25 | getClassNames() { 26 | return TailwindClient.currentClasses ?? {}; 27 | } 28 | 29 | getConfigPath() { 30 | return getTailwindConfigPath(this.project); 31 | } 32 | 33 | getConfig() { 34 | const configPath = this.getConfigPath(); 35 | 36 | return { 37 | ...require(configPath), 38 | 39 | // NOTICE: extractClassNames would not work when { mode: "jit" } 40 | mode: "aot", 41 | }; 42 | } 43 | 44 | async requestCompileCss() { 45 | const postcss = this.requirePostCss(); 46 | const tailwindcss = this.requireTailwindCss(); 47 | const config = this.getConfig(); 48 | 49 | return Promise.all( 50 | ["base", "components", "utilities"].map((group) => 51 | postcss([tailwindcss(config)]).process(`@tailwind ${group};`, { 52 | from: undefined, 53 | to: undefined, 54 | map: false, 55 | }) 56 | ) 57 | ).then((result) => { 58 | TailwindClient.lastUpdatedAt = this.getLastUpdatedAt(); 59 | TailwindClient.currentClasses = this.extractClassNames(result); 60 | 61 | this.project.projectService.logger.info("compiled tailwind.css"); 62 | }); 63 | } 64 | 65 | private requirePostCss() { 66 | const configPath = this.getConfigPath(); 67 | const tailwindRootPath = path.dirname( 68 | resolveFrom(configPath, "tailwindcss/package.json") 69 | ); 70 | 71 | return importFrom(tailwindRootPath, "postcss") as typeof import("postcss"); 72 | } 73 | 74 | private extractClassNames([base, components, utilities]: Result[]) { 75 | const { classNames } = extractClassNames([ 76 | { root: base.root, source: "base" }, 77 | { root: components.root, source: "components" }, 78 | { root: utilities.root, source: "utilities" }, 79 | ]); 80 | 81 | return classNames; 82 | } 83 | 84 | private requireTailwindCss() { 85 | return importFrom( 86 | path.dirname(this.getConfigPath()), 87 | "tailwindcss" 88 | ) as Function; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/TailwindErrorChecker.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript/lib/tsserverlibrary"; 2 | import { ClassNameDiagnostic } from "./ClassNameDiagnostic"; 3 | import { TailwindClient } from "./TailwindClient"; 4 | 5 | export class TailwindErrorChecker { 6 | private readonly tailwind = new TailwindClient(this.project); 7 | 8 | static isPending = false; 9 | 10 | constructor(private readonly project: ts.server.Project) {} 11 | 12 | /** 13 | * It's SLOW. Call only when really needed 14 | */ 15 | loadCss() { 16 | if (TailwindClient.currentClasses && this.tailwind.isFresh()) { 17 | return; 18 | } 19 | 20 | if (TailwindErrorChecker.isPending) { 21 | return; 22 | } 23 | 24 | TailwindErrorChecker.isPending = true; 25 | this.project.projectService.logger.info( 26 | "enqueuing to load css definitions..." 27 | ); 28 | 29 | void this.tailwind.requestCompileCss().finally(() => { 30 | this.stop(); 31 | }); 32 | } 33 | 34 | stop() { 35 | TailwindErrorChecker.isPending = false; 36 | } 37 | 38 | getTailwindDiagnostics(sourceFile: ts.SourceFile) { 39 | const classNameDiagnostic = new ClassNameDiagnostic( 40 | sourceFile, 41 | this.tailwind.getClassNames() 42 | ); 43 | 44 | this.project.projectService.logger.info("checking classnames usages..."); 45 | 46 | return classNameDiagnostic 47 | .toArray() 48 | .map(({ start, length, messageText }) => ({ 49 | source: "irontail", 50 | category: ts.DiagnosticCategory.Warning, 51 | code: 0, 52 | file: sourceFile, 53 | start, 54 | length, 55 | messageText, 56 | })); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/extractClassNames.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copied from https://github.com/tailwindlabs/tailwindcss-intellisense/blob/6659228975393fb583268b50c2274d082a159072/src/class-names/extractClassNames.js 3 | */ 4 | 5 | // @ts-nocheck 6 | 7 | import selectorParser = require("postcss-selector-parser"); 8 | import dset = require("dset"); 9 | import dlv = require("dlv"); 10 | import postcss = require("postcss"); 11 | 12 | function createSelectorFromNodes(nodes: string | any[]) { 13 | if (nodes.length === 0) return null; 14 | const selector = selectorParser.selector(undefined as any); 15 | for (let i = 0; i < nodes.length; i++) { 16 | selector.append(nodes[i]); 17 | } 18 | return String(selector).trim(); 19 | } 20 | 21 | function getClassNamesFromSelector(selector: selectorParser.Selectors) { 22 | const classNames = []; 23 | const { nodes: subSelectors } = selectorParser().astSync(selector); 24 | 25 | for (let i = 0; i < subSelectors.length; i++) { 26 | let scope = []; 27 | 28 | for (let j = 0; j < subSelectors[i].nodes.length; j++) { 29 | let node = subSelectors[i].nodes[j]; 30 | let pseudo = []; 31 | 32 | if (node.type === "class") { 33 | let next = subSelectors[i].nodes[j + 1]; 34 | 35 | while (next && next.type === "pseudo") { 36 | pseudo.push(next); 37 | j++; 38 | next = subSelectors[i].nodes[j + 1]; 39 | } 40 | 41 | classNames.push({ 42 | className: node.value.trim(), 43 | scope: createSelectorFromNodes(scope), 44 | __rule: j === subSelectors[i].nodes.length - 1, 45 | __pseudo: pseudo.map(String), 46 | }); 47 | } 48 | scope.push(node, ...pseudo); 49 | } 50 | } 51 | 52 | return classNames; 53 | } 54 | 55 | interface Group { 56 | root?: postcss.Root; 57 | source: string; 58 | } 59 | 60 | function process(groups: Group[]) { 61 | const tree: Record = {}; 62 | const commonContext: Record = {}; 63 | 64 | groups.forEach((group) => { 65 | group.root?.walkRules((rule) => { 66 | const classNames = getClassNamesFromSelector(rule.selector); 67 | 68 | const decls: any = {}; 69 | postcss 70 | .rule(rule) 71 | .walkDecls((decl: { prop: string | number; value: any }) => { 72 | if (decls[decl.prop]) { 73 | decls[decl.prop] = [ 74 | ...(Array.isArray(decls[decl.prop]) 75 | ? decls[decl.prop] 76 | : [decls[decl.prop]]), 77 | decl.value, 78 | ]; 79 | } else { 80 | decls[decl.prop] = decl.value; 81 | } 82 | }); 83 | 84 | let p: postcss.Rule | postcss.AtRule = rule; 85 | const keys = []; 86 | while (p?.parent?.type !== "root") { 87 | p = p.parent; 88 | if (p?.type === "atrule") { 89 | keys.push(`@${p.name} ${p.params}`); 90 | } 91 | } 92 | 93 | for (let i = 0; i < classNames.length; i++) { 94 | const context = keys.concat([]); 95 | const baseKeys = classNames[i].className.split( 96 | "__TAILWIND_SEPARATOR__" 97 | ); 98 | const contextKeys = baseKeys.slice(0, baseKeys.length - 1); 99 | const index = []; 100 | 101 | const existing = dlv(tree, baseKeys); 102 | if (typeof existing !== "undefined") { 103 | if (Array.isArray(existing)) { 104 | const scopeIndex = existing.findIndex( 105 | (x) => 106 | x.__scope === classNames[i].scope && 107 | arraysEqual(existing.__context, context) 108 | ); 109 | if (scopeIndex > -1) { 110 | keys.unshift(scopeIndex); 111 | index.push(scopeIndex); 112 | } else { 113 | keys.unshift(existing.length); 114 | index.push(existing.length); 115 | } 116 | } else { 117 | if ( 118 | existing.__scope !== classNames[i].scope || 119 | !arraysEqual(existing.__context, context) 120 | ) { 121 | dset(tree, baseKeys, [existing]); 122 | keys.unshift(1); 123 | index.push(1); 124 | } 125 | } 126 | } 127 | if (classNames[i].__rule) { 128 | dset(tree, [...baseKeys, ...index, "__rule"], true); 129 | dset(tree, [...baseKeys, ...index, "__source"], group.source); 130 | 131 | dsetEach(tree, [...baseKeys, ...index], decls); 132 | } 133 | dset(tree, [...baseKeys, ...index, "__pseudo"], classNames[i].__pseudo); 134 | dset(tree, [...baseKeys, ...index, "__scope"], classNames[i].scope); 135 | dset( 136 | tree, 137 | [...baseKeys, ...index, "__context"], 138 | context.concat([]).reverse() 139 | ); 140 | 141 | // common context 142 | context.push(...classNames[i].__pseudo); 143 | 144 | for (let i = 0; i < contextKeys.length; i++) { 145 | if (typeof commonContext[contextKeys[i]] === "undefined") { 146 | commonContext[contextKeys[i]] = context; 147 | } else { 148 | commonContext[contextKeys[i]] = intersection( 149 | commonContext[contextKeys[i]], 150 | context 151 | ); 152 | } 153 | } 154 | } 155 | }); 156 | }); 157 | 158 | return { classNames: tree, context: commonContext }; 159 | } 160 | 161 | function intersection(arr1: any[], arr2: string | any[]) { 162 | return arr1.filter((value: any) => arr2.indexOf(value) !== -1); 163 | } 164 | 165 | function dsetEach(obj: {}, keys: any[], values: { [x: string]: any }) { 166 | const k = Object.keys(values); 167 | for (let i = 0; i < k.length; i++) { 168 | dset(obj, [...keys, k[i]], values[k[i]]); 169 | } 170 | } 171 | 172 | function arraysEqual(a: string | any[] | null, b: string | any[] | null) { 173 | if (a === b) return true; 174 | if (a == null || b == null) return false; 175 | if (a.length !== b.length) return false; 176 | 177 | for (let i = 0; i < a.length; ++i) { 178 | if (a[i] !== b[i]) return false; 179 | } 180 | return true; 181 | } 182 | 183 | export default process; 184 | -------------------------------------------------------------------------------- /src/getTailwindConfigPath.ts: -------------------------------------------------------------------------------- 1 | import * as ts from "typescript/lib/tsserverlibrary"; 2 | import * as glob from "glob"; 3 | import * as path from "path"; 4 | 5 | /** 6 | * @see https://github.com/tailwindlabs/tailwindcss-intellisense/blob/eb28c540c3cff7cf4c625cf44a81e8a44164a9ed/src/class-names/index.js#L36 7 | */ 8 | const CONFIG_GLOB = 9 | "**/{tailwind,tailwind.config,tailwind-config,.tailwindrc}.js"; 10 | 11 | export function getTailwindConfigPath(project: ts.server.Project) { 12 | const projectRootPath = project.getCurrentDirectory(); 13 | const configGlob = path.join(projectRootPath, CONFIG_GLOB); 14 | const [configPath] = glob.sync(configGlob); 15 | 16 | if (!configPath) { 17 | throw new Error("Cannot find tailwind config"); 18 | } 19 | 20 | if (!project.fileExists(configPath)) { 21 | throw new Error("Cannot find tailwind config"); 22 | } 23 | 24 | return configPath; 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2015", 6 | "outDir": "dist", 7 | "sourceMap": true, 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "lib": ["es2019", "esnext.asynciterable", "dom"] 14 | }, 15 | "include": ["index.ts", "declaration.d.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@fullhuman/postcss-purgecss@^2.1.2": 6 | version "2.3.0" 7 | resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz#50a954757ec78696615d3e118e3fee2d9291882e" 8 | integrity sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw== 9 | dependencies: 10 | postcss "7.0.32" 11 | purgecss "^2.3.0" 12 | 13 | "@types/classnames@^2.2.10": 14 | version "2.2.10" 15 | resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" 16 | integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== 17 | 18 | "@types/color-name@^1.1.1": 19 | version "1.1.1" 20 | resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" 21 | integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== 22 | 23 | "@types/dlv@^1.1.2": 24 | version "1.1.2" 25 | resolved "https://registry.yarnpkg.com/@types/dlv/-/dlv-1.1.2.tgz#02d4fcc41c5f707753427867c64fdae543031fb9" 26 | integrity sha512-OyiZ3jEKu7RtGO1yp9oOdK0cTwZ/10oE9PDJ6fyN3r9T5wkyOcvr6awdugjYdqF6KVO5eUvt7jx7rk2Eylufow== 27 | 28 | "@types/glob@^7.1.3": 29 | version "7.1.3" 30 | resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" 31 | integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== 32 | dependencies: 33 | "@types/minimatch" "*" 34 | "@types/node" "*" 35 | 36 | "@types/minimatch@*": 37 | version "3.0.3" 38 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" 39 | integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== 40 | 41 | "@types/node@*", "@types/node@^14.0.27": 42 | version "14.0.27" 43 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" 44 | integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== 45 | 46 | "@types/serialize-javascript@^4.0.0": 47 | version "4.0.0" 48 | resolved "https://registry.yarnpkg.com/@types/serialize-javascript/-/serialize-javascript-4.0.0.tgz#ab9c47edf71f6a4590221118d1dffc37b50d71a2" 49 | integrity sha512-y9UO8ozDGF30EVRizmsXuCdZzAxHmYpEEiL+/SQjX+7bnxslkvZQU8rLydixJv7yVae6bWyrNDFHsh6fYHkFMA== 50 | 51 | acorn-node@^1.6.1: 52 | version "1.8.2" 53 | resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" 54 | integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== 55 | dependencies: 56 | acorn "^7.0.0" 57 | acorn-walk "^7.0.0" 58 | xtend "^4.0.2" 59 | 60 | acorn-walk@^7.0.0: 61 | version "7.2.0" 62 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" 63 | integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== 64 | 65 | acorn@^7.0.0: 66 | version "7.4.0" 67 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" 68 | integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== 69 | 70 | ansi-styles@^3.2.1: 71 | version "3.2.1" 72 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 73 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 74 | dependencies: 75 | color-convert "^1.9.0" 76 | 77 | ansi-styles@^4.1.0: 78 | version "4.2.1" 79 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" 80 | integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== 81 | dependencies: 82 | "@types/color-name" "^1.1.1" 83 | color-convert "^2.0.1" 84 | 85 | autoprefixer@^9.4.5: 86 | version "9.8.6" 87 | resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" 88 | integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== 89 | dependencies: 90 | browserslist "^4.12.0" 91 | caniuse-lite "^1.0.30001109" 92 | colorette "^1.2.1" 93 | normalize-range "^0.1.2" 94 | num2fraction "^1.2.2" 95 | postcss "^7.0.32" 96 | postcss-value-parser "^4.1.0" 97 | 98 | balanced-match@^1.0.0: 99 | version "1.0.0" 100 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 101 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 102 | 103 | brace-expansion@^1.1.7: 104 | version "1.1.11" 105 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 106 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 107 | dependencies: 108 | balanced-match "^1.0.0" 109 | concat-map "0.0.1" 110 | 111 | browserslist@^4.12.0: 112 | version "4.14.0" 113 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.0.tgz#2908951abfe4ec98737b72f34c3bcedc8d43b000" 114 | integrity sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ== 115 | dependencies: 116 | caniuse-lite "^1.0.30001111" 117 | electron-to-chromium "^1.3.523" 118 | escalade "^3.0.2" 119 | node-releases "^1.1.60" 120 | 121 | bytes@^3.0.0: 122 | version "3.1.0" 123 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 124 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 125 | 126 | camelcase-css@^2.0.1: 127 | version "2.0.1" 128 | resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" 129 | integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== 130 | 131 | caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001111: 132 | version "1.0.30001114" 133 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001114.tgz#2e88119afb332ead5eaa330e332e951b1c4bfea9" 134 | integrity sha512-ml/zTsfNBM+T1+mjglWRPgVsu2L76GAaADKX5f4t0pbhttEp0WMawJsHDYlFkVZkoA+89uvBRrVrEE4oqenzXQ== 135 | 136 | chalk@^2.4.1, chalk@^2.4.2: 137 | version "2.4.2" 138 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 139 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 140 | dependencies: 141 | ansi-styles "^3.2.1" 142 | escape-string-regexp "^1.0.5" 143 | supports-color "^5.3.0" 144 | 145 | "chalk@^3.0.0 || ^4.0.0": 146 | version "4.1.0" 147 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" 148 | integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== 149 | dependencies: 150 | ansi-styles "^4.1.0" 151 | supports-color "^7.1.0" 152 | 153 | color-convert@^1.9.0, color-convert@^1.9.1: 154 | version "1.9.3" 155 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 156 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 157 | dependencies: 158 | color-name "1.1.3" 159 | 160 | color-convert@^2.0.1: 161 | version "2.0.1" 162 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 163 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 164 | dependencies: 165 | color-name "~1.1.4" 166 | 167 | color-name@1.1.3: 168 | version "1.1.3" 169 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 170 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 171 | 172 | color-name@^1.0.0, color-name@~1.1.4: 173 | version "1.1.4" 174 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 175 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 176 | 177 | color-string@^1.5.2: 178 | version "1.5.3" 179 | resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" 180 | integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== 181 | dependencies: 182 | color-name "^1.0.0" 183 | simple-swizzle "^0.2.2" 184 | 185 | color@^3.1.2: 186 | version "3.1.2" 187 | resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" 188 | integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== 189 | dependencies: 190 | color-convert "^1.9.1" 191 | color-string "^1.5.2" 192 | 193 | colorette@^1.2.1: 194 | version "1.2.1" 195 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" 196 | integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== 197 | 198 | commander@^5.0.0: 199 | version "5.1.0" 200 | resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" 201 | integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== 202 | 203 | concat-map@0.0.1: 204 | version "0.0.1" 205 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 206 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 207 | 208 | css-unit-converter@^1.1.1: 209 | version "1.1.2" 210 | resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" 211 | integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== 212 | 213 | cssesc@^3.0.0: 214 | version "3.0.0" 215 | resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" 216 | integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== 217 | 218 | defined@^1.0.0: 219 | version "1.0.0" 220 | resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" 221 | integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= 222 | 223 | detective@^5.2.0: 224 | version "5.2.0" 225 | resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" 226 | integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== 227 | dependencies: 228 | acorn-node "^1.6.1" 229 | defined "^1.0.0" 230 | minimist "^1.1.1" 231 | 232 | dlv@^1.1.3: 233 | version "1.1.3" 234 | resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" 235 | integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== 236 | 237 | dset@^2.0.1: 238 | version "2.0.1" 239 | resolved "https://registry.yarnpkg.com/dset/-/dset-2.0.1.tgz#a15fff3d1e4d60ac0c95634625cbd5441a76deb1" 240 | integrity sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ== 241 | 242 | electron-to-chromium@^1.3.523: 243 | version "1.3.533" 244 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.533.tgz#d7e5ca4d57e9bc99af87efbe13e7be5dde729b0f" 245 | integrity sha512-YqAL+NXOzjBnpY+dcOKDlZybJDCOzgsq4koW3fvyty/ldTmsb4QazZpOWmVvZ2m0t5jbBf7L0lIGU3BUipwG+A== 246 | 247 | escalade@^3.0.2: 248 | version "3.0.2" 249 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" 250 | integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== 251 | 252 | escape-string-regexp@^1.0.5: 253 | version "1.0.5" 254 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 255 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 256 | 257 | fs-extra@^8.0.0: 258 | version "8.1.0" 259 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 260 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 261 | dependencies: 262 | graceful-fs "^4.2.0" 263 | jsonfile "^4.0.0" 264 | universalify "^0.1.0" 265 | 266 | fs.realpath@^1.0.0: 267 | version "1.0.0" 268 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 269 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 270 | 271 | glob@^7.0.0, glob@^7.1.2, glob@^7.1.6: 272 | version "7.1.6" 273 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 274 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 275 | dependencies: 276 | fs.realpath "^1.0.0" 277 | inflight "^1.0.4" 278 | inherits "2" 279 | minimatch "^3.0.4" 280 | once "^1.3.0" 281 | path-is-absolute "^1.0.0" 282 | 283 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 284 | version "4.2.4" 285 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 286 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== 287 | 288 | has-flag@^3.0.0: 289 | version "3.0.0" 290 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 291 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 292 | 293 | has-flag@^4.0.0: 294 | version "4.0.0" 295 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 296 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 297 | 298 | import-from@^3.0.0: 299 | version "3.0.0" 300 | resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" 301 | integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== 302 | dependencies: 303 | resolve-from "^5.0.0" 304 | 305 | indexes-of@^1.0.1: 306 | version "1.0.1" 307 | resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" 308 | integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= 309 | 310 | inflight@^1.0.4: 311 | version "1.0.6" 312 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 313 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 314 | dependencies: 315 | once "^1.3.0" 316 | wrappy "1" 317 | 318 | inherits@2: 319 | version "2.0.4" 320 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 321 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 322 | 323 | is-arrayish@^0.3.1: 324 | version "0.3.2" 325 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" 326 | integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== 327 | 328 | jsonfile@^4.0.0: 329 | version "4.0.0" 330 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 331 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 332 | optionalDependencies: 333 | graceful-fs "^4.1.6" 334 | 335 | lodash.toarray@^4.4.0: 336 | version "4.4.0" 337 | resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" 338 | integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= 339 | 340 | lodash@^4.17.15: 341 | version "4.17.20" 342 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" 343 | integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== 344 | 345 | minimatch@^3.0.4: 346 | version "3.0.4" 347 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 348 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 349 | dependencies: 350 | brace-expansion "^1.1.7" 351 | 352 | minimist@^1.1.1: 353 | version "1.2.5" 354 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 355 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 356 | 357 | node-emoji@^1.8.1: 358 | version "1.10.0" 359 | resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.10.0.tgz#8886abd25d9c7bb61802a658523d1f8d2a89b2da" 360 | integrity sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw== 361 | dependencies: 362 | lodash.toarray "^4.4.0" 363 | 364 | node-releases@^1.1.60: 365 | version "1.1.60" 366 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" 367 | integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== 368 | 369 | normalize-range@^0.1.2: 370 | version "0.1.2" 371 | resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" 372 | integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= 373 | 374 | normalize.css@^8.0.1: 375 | version "8.0.1" 376 | resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" 377 | integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== 378 | 379 | num2fraction@^1.2.2: 380 | version "1.2.2" 381 | resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" 382 | integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= 383 | 384 | object-assign@^4.1.1: 385 | version "4.1.1" 386 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 387 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 388 | 389 | once@^1.3.0: 390 | version "1.4.0" 391 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 392 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 393 | dependencies: 394 | wrappy "1" 395 | 396 | path-is-absolute@^1.0.0: 397 | version "1.0.1" 398 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 399 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 400 | 401 | path-parse@^1.0.6: 402 | version "1.0.6" 403 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" 404 | integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== 405 | 406 | postcss-functions@^3.0.0: 407 | version "3.0.0" 408 | resolved "https://registry.yarnpkg.com/postcss-functions/-/postcss-functions-3.0.0.tgz#0e94d01444700a481de20de4d55fb2640564250e" 409 | integrity sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4= 410 | dependencies: 411 | glob "^7.1.2" 412 | object-assign "^4.1.1" 413 | postcss "^6.0.9" 414 | postcss-value-parser "^3.3.0" 415 | 416 | postcss-js@^2.0.0: 417 | version "2.0.3" 418 | resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-2.0.3.tgz#a96f0f23ff3d08cec7dc5b11bf11c5f8077cdab9" 419 | integrity sha512-zS59pAk3deu6dVHyrGqmC3oDXBdNdajk4k1RyxeVXCrcEDBUBHoIhE4QTsmhxgzXxsaqFDAkUZfmMa5f/N/79w== 420 | dependencies: 421 | camelcase-css "^2.0.1" 422 | postcss "^7.0.18" 423 | 424 | postcss-nested@^4.1.1: 425 | version "4.2.3" 426 | resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-4.2.3.tgz#c6f255b0a720549776d220d00c4b70cd244136f6" 427 | integrity sha512-rOv0W1HquRCamWy2kFl3QazJMMe1ku6rCFoAAH+9AcxdbpDeBr6k968MLWuLjvjMcGEip01ak09hKOEgpK9hvw== 428 | dependencies: 429 | postcss "^7.0.32" 430 | postcss-selector-parser "^6.0.2" 431 | 432 | postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: 433 | version "6.0.2" 434 | resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" 435 | integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== 436 | dependencies: 437 | cssesc "^3.0.0" 438 | indexes-of "^1.0.1" 439 | uniq "^1.0.1" 440 | 441 | postcss-value-parser@^3.3.0: 442 | version "3.3.1" 443 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" 444 | integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== 445 | 446 | postcss-value-parser@^4.1.0: 447 | version "4.1.0" 448 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" 449 | integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== 450 | 451 | postcss@7.0.32, postcss@^7.0.11, postcss@^7.0.18, postcss@^7.0.32: 452 | version "7.0.32" 453 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" 454 | integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== 455 | dependencies: 456 | chalk "^2.4.2" 457 | source-map "^0.6.1" 458 | supports-color "^6.1.0" 459 | 460 | postcss@^6.0.9: 461 | version "6.0.23" 462 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" 463 | integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== 464 | dependencies: 465 | chalk "^2.4.1" 466 | source-map "^0.6.1" 467 | supports-color "^5.4.0" 468 | 469 | pretty-hrtime@^1.0.3: 470 | version "1.0.3" 471 | resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" 472 | integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= 473 | 474 | purgecss@^2.3.0: 475 | version "2.3.0" 476 | resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.3.0.tgz#5327587abf5795e6541517af8b190a6fb5488bb3" 477 | integrity sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ== 478 | dependencies: 479 | commander "^5.0.0" 480 | glob "^7.0.0" 481 | postcss "7.0.32" 482 | postcss-selector-parser "^6.0.2" 483 | 484 | randombytes@^2.1.0: 485 | version "2.1.0" 486 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 487 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 488 | dependencies: 489 | safe-buffer "^5.1.0" 490 | 491 | reduce-css-calc@^2.1.6: 492 | version "2.1.7" 493 | resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" 494 | integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== 495 | dependencies: 496 | css-unit-converter "^1.1.1" 497 | postcss-value-parser "^3.3.0" 498 | 499 | resolve-from@^5.0.0: 500 | version "5.0.0" 501 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" 502 | integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== 503 | 504 | resolve@^1.14.2: 505 | version "1.17.0" 506 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" 507 | integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== 508 | dependencies: 509 | path-parse "^1.0.6" 510 | 511 | safe-buffer@^5.1.0: 512 | version "5.2.1" 513 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 514 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 515 | 516 | serialize-javascript@^4.0.0: 517 | version "4.0.0" 518 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" 519 | integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== 520 | dependencies: 521 | randombytes "^2.1.0" 522 | 523 | simple-swizzle@^0.2.2: 524 | version "0.2.2" 525 | resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" 526 | integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= 527 | dependencies: 528 | is-arrayish "^0.3.1" 529 | 530 | source-map@^0.6.1: 531 | version "0.6.1" 532 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 533 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 534 | 535 | supports-color@^5.3.0, supports-color@^5.4.0: 536 | version "5.5.0" 537 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 538 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 539 | dependencies: 540 | has-flag "^3.0.0" 541 | 542 | supports-color@^6.1.0: 543 | version "6.1.0" 544 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" 545 | integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== 546 | dependencies: 547 | has-flag "^3.0.0" 548 | 549 | supports-color@^7.1.0: 550 | version "7.1.0" 551 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" 552 | integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== 553 | dependencies: 554 | has-flag "^4.0.0" 555 | 556 | tailwindcss@^1.6.2: 557 | version "1.6.2" 558 | resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-1.6.2.tgz#352da9e1b0d9154c95ce12483daa1c2fa1f1eea8" 559 | integrity sha512-Cpa0kElG8Sg5sJSvTYi2frmIQZq0w37RLNNrYyy/W6HIWKspqSdTfb9tIN6X1gm4KV5a+TE/n7EKmn5Q9C7EUQ== 560 | dependencies: 561 | "@fullhuman/postcss-purgecss" "^2.1.2" 562 | autoprefixer "^9.4.5" 563 | browserslist "^4.12.0" 564 | bytes "^3.0.0" 565 | chalk "^3.0.0 || ^4.0.0" 566 | color "^3.1.2" 567 | detective "^5.2.0" 568 | fs-extra "^8.0.0" 569 | lodash "^4.17.15" 570 | node-emoji "^1.8.1" 571 | normalize.css "^8.0.1" 572 | postcss "^7.0.11" 573 | postcss-functions "^3.0.0" 574 | postcss-js "^2.0.0" 575 | postcss-nested "^4.1.1" 576 | postcss-selector-parser "^6.0.0" 577 | pretty-hrtime "^1.0.3" 578 | reduce-css-calc "^2.1.6" 579 | resolve "^1.14.2" 580 | 581 | typescript@^3.9.7: 582 | version "3.9.7" 583 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" 584 | integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== 585 | 586 | uniq@^1.0.1: 587 | version "1.0.1" 588 | resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" 589 | integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= 590 | 591 | universalify@^0.1.0: 592 | version "0.1.2" 593 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 594 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 595 | 596 | wrappy@1: 597 | version "1.0.2" 598 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 599 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 600 | 601 | xtend@^4.0.2: 602 | version "4.0.2" 603 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 604 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 605 | --------------------------------------------------------------------------------