From c664c824f97d8712f811554a665a91a871d797bb Mon Sep 17 00:00:00 2001 From: Nick Oliver Date: Tue, 8 Jun 2021 03:08:34 -0700 Subject: [PATCH] feat(git-loader): add glob support (#3012) * feat(git-loader): add glob support * refactor(loaders): format ignore globs in the loaders so they can handle the specific format * fix(git-loader): unixify globs * fix(git-loader): use OS's EOL Co-authored-by: Arda TANRIKULU --- packages/load/package.json | 2 - .../load/src/load-typedefs/collect-sources.ts | 143 +++++++++++++----- packages/loaders/code-file/package.json | 8 +- packages/loaders/code-file/src/index.ts | 40 ++++- packages/loaders/git/package.json | 9 +- packages/loaders/git/src/index.ts | 62 +++++++- packages/loaders/git/src/load-git.ts | 67 ++++++-- packages/loaders/graphql-file/package.json | 6 + packages/loaders/graphql-file/src/index.ts | 34 +++++ .../src => loaders/graphql-file}/typings.d.ts | 0 packages/utils/src/loaders.ts | 7 + yarn.lock | 14 +- 12 files changed, 331 insertions(+), 61 deletions(-) rename packages/{load/src => loaders/graphql-file}/typings.d.ts (100%) diff --git a/packages/load/package.json b/packages/load/package.json index eec6774f27d..18a0c1639c9 100644 --- a/packages/load/package.json +++ b/packages/load/package.json @@ -28,12 +28,10 @@ "dependencies": { "@graphql-tools/utils": "^7.5.0", "@graphql-tools/merge": "^6.2.12", - "globby": "11.0.3", "import-from": "4.0.0", "is-glob": "4.0.1", "p-limit": "3.1.0", "tslib": "~2.2.0", - "unixify": "1.0.0", "valid-url": "1.0.9" }, "publishConfig": { diff --git a/packages/load/src/load-typedefs/collect-sources.ts b/packages/load/src/load-typedefs/collect-sources.ts index 06b5c205f4b..ebdba0b69b8 100644 --- a/packages/load/src/load-typedefs/collect-sources.ts +++ b/packages/load/src/load-typedefs/collect-sources.ts @@ -1,4 +1,12 @@ -import { Source, isDocumentString, parseGraphQLSDL, asArray, getDocumentNodeFromSchema } from '@graphql-tools/utils'; +import { + Source, + isDocumentString, + parseGraphQLSDL, + asArray, + getDocumentNodeFromSchema, + Loader, + ResolverGlobs, +} from '@graphql-tools/utils'; import { isSchema, Kind } from 'graphql'; import isGlob from 'is-glob'; import { LoadTypedefsOptions } from '../load-typedefs'; @@ -6,8 +14,6 @@ import { loadFile, loadFileSync } from './load-file'; import { stringToHash, useStack, StackNext, StackFn } from '../utils/helpers'; import { useCustomLoader, useCustomLoaderSync } from '../utils/custom-loader'; import { useQueue, useSyncQueue } from '../utils/queue'; -import unixify from 'unixify'; -import globby, { sync as globbySync } from 'globby'; type AddSource = (data: { pointer: string; source: Source; noCache?: boolean }) => void; type AddGlob = (data: { pointer: string; pointerOptions: any }) => void; @@ -38,10 +44,7 @@ export async function collectSources({ }); for (const pointer in pointerOptionMap) { - const pointerOptions = { - ...(pointerOptionMap[pointer] ?? {}), - unixify, - }; + const pointerOptions = pointerOptionMap[pointer]; collect({ pointer, @@ -55,12 +58,8 @@ export async function collectSources({ } if (globs.length) { - includeIgnored({ - options, - globs, - }); - - const paths = await globby(globs, createGlobbyOptions(options)); + // TODO: use the queue? + const paths = await collectPathsFromGlobs(globs, options); collectSourcesFromGlobals({ filepaths: paths, @@ -100,10 +99,7 @@ export function collectSourcesSync({ }); for (const pointer in pointerOptionMap) { - const pointerOptions = { - ...(pointerOptionMap[pointer] ?? {}), - unixify, - }; + const pointerOptions = pointerOptionMap[pointer]; collect({ pointer, @@ -117,12 +113,7 @@ export function collectSourcesSync({ } if (globs.length) { - includeIgnored({ - options, - globs, - }); - - const paths = globbySync(globs, createGlobbyOptions(options)); + const paths = collectPathsFromGlobsSync(globs, options); collectSourcesFromGlobalsSync({ filepaths: paths, @@ -139,8 +130,6 @@ export function collectSourcesSync({ return sources; } -// - function createHelpers({ sources, globs, @@ -184,24 +173,98 @@ function createHelpers({ }; } -function includeIgnored< - T extends { - ignore?: string | string[]; +async function addGlobsToLoaders({ + options, + loadersForGlobs, + globs, + type, +}: { + options: LoadTypedefsOptions; + loadersForGlobs: Map; + globs: string[]; + type: 'globs' | 'ignores'; +}) { + for (const glob of globs) { + let loader; + for await (const candidateLoader of options.loaders) { + if (candidateLoader.resolveGlobs && (await candidateLoader.canLoad(glob, options))) { + loader = candidateLoader; + break; + } + } + if (!loader) { + throw new Error(`unable to find loader for glob "${glob}"`); + } + if (!loadersForGlobs.has(loader)) { + loadersForGlobs.set(loader, { globs: [], ignores: [] }); + } + loadersForGlobs.get(loader)[type].push(glob); } ->({ options, globs }: { options: T; globs: string[] }) { - if (options.ignore) { - const ignoreList = asArray(options.ignore) - .map(g => `!(${g})`) - .map(unixify); - - if (ignoreList.length > 0) { - globs.push(...ignoreList); +} + +function addGlobsToLoadersSync({ + options, + loadersForGlobs, + globs, + type, +}: { + options: LoadTypedefsOptions; + loadersForGlobs: Map; + globs: string[]; + type: 'globs' | 'ignores'; +}) { + for (const glob of globs) { + let loader; + for (const candidateLoader of options.loaders) { + if (candidateLoader.resolveGlobs && candidateLoader.canLoadSync(glob, options)) { + loader = candidateLoader; + break; + } + } + if (!loader) { + throw new Error(`unable to find loader for glob "${glob}"`); + } + if (!loadersForGlobs.has(loader)) { + loadersForGlobs.set(loader, { globs: [], ignores: [] }); + } + loadersForGlobs.get(loader)[type].push(glob); + } +} + +async function collectPathsFromGlobs(globs: string[], options: LoadTypedefsOptions): Promise { + const paths: string[] = []; + + const loadersForGlobs: Map = new Map(); + + await addGlobsToLoaders({ options, loadersForGlobs, globs, type: 'globs' }); + await addGlobsToLoaders({ options, loadersForGlobs, globs: asArray(options.ignore), type: 'ignores' }); + + for await (const [loader, globsAndIgnores] of loadersForGlobs.entries()) { + const resolvedPaths = await loader.resolveGlobs(globsAndIgnores, options); + if (resolvedPaths) { + paths.push(...resolvedPaths); } } + + return paths; } -function createGlobbyOptions(options: any): any { - return { absolute: true, ...options, ignore: [] }; +function collectPathsFromGlobsSync(globs: string[], options: LoadTypedefsOptions): string[] { + const paths: string[] = []; + + const loadersForGlobs: Map = new Map(); + + addGlobsToLoadersSync({ options, loadersForGlobs, globs, type: 'globs' }); + addGlobsToLoadersSync({ options, loadersForGlobs, globs: asArray(options.ignore), type: 'ignores' }); + + for (const [loader, globsAndIgnores] of loadersForGlobs.entries()) { + const resolvedPaths = loader.resolveGlobsSync(globsAndIgnores, options); + if (resolvedPaths) { + paths.push(...resolvedPaths); + } + } + + return paths; } function collectSourcesFromGlobals({ @@ -342,9 +405,9 @@ function collectDocumentString( } function collectGlob({ pointer, pointerOptions, addGlob }: CollectOptions, next: StackNext) { - if (isGlob(pointerOptions.unixify(pointer))) { + if (isGlob(pointer)) { return addGlob({ - pointer: pointerOptions.unixify(pointer), + pointer, pointerOptions, }); } diff --git a/packages/loaders/code-file/package.json b/packages/loaders/code-file/package.json index 691a4a5048a..f86d9e045a5 100644 --- a/packages/loaders/code-file/package.json +++ b/packages/loaders/code-file/package.json @@ -16,13 +16,19 @@ "typescript": { "definition": "dist/index.d.ts" }, + "devDependencies": { + "@types/is-glob": "4.0.1" + }, "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0" }, "dependencies": { "@graphql-tools/utils": "^7.0.0", "@graphql-tools/graphql-tag-pluck": "^6.5.1", - "tslib": "~2.2.0" + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "tslib": "~2.2.0", + "unixify": "^1.0.0" }, "publishConfig": { "access": "public", diff --git a/packages/loaders/code-file/src/index.ts b/packages/loaders/code-file/src/index.ts index 62e87d246d6..9ac6f0c9845 100644 --- a/packages/loaders/code-file/src/index.ts +++ b/packages/loaders/code-file/src/index.ts @@ -1,3 +1,5 @@ +import type { GlobbyOptions } from 'globby'; + import { isSchema, GraphQLSchema, DocumentNode } from 'graphql'; import { SchemaPointerSingle, @@ -10,12 +12,16 @@ import { isValidPath, parseGraphQLSDL, isDocumentNode, + ResolverGlobs, } from '@graphql-tools/utils'; import { GraphQLTagPluckOptions, gqlPluckFromCodeString, gqlPluckFromCodeStringSync, } from '@graphql-tools/graphql-tag-pluck'; +import globby from 'globby'; +import isGlob from 'is-glob'; +import unixify from 'unixify'; import { tryToLoadFromExport, tryToLoadFromExportSync } from './load-from-module'; import { isAbsolute, resolve } from 'path'; import { cwd } from 'process'; @@ -35,6 +41,10 @@ export type CodeFileLoaderOptions = { const FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.vue']; +function createGlobbyOptions(options: CodeFileLoaderOptions): GlobbyOptions { + return { absolute: true, ...options, ignore: [] }; +} + /** * This loader loads GraphQL documents and type definitions from code files * using `graphql-tag-pluck`. @@ -58,6 +68,11 @@ export class CodeFileLoader implements UniversalLoader { pointer: SchemaPointerSingle | DocumentPointerSingle, options: CodeFileLoaderOptions ): Promise { + if (isGlob(pointer)) { + // FIXME: parse to find and check the file extensions? + return true; + } + if (isValidPath(pointer)) { if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer); @@ -74,6 +89,11 @@ export class CodeFileLoader implements UniversalLoader { } canLoadSync(pointer: SchemaPointerSingle | DocumentPointerSingle, options: CodeFileLoaderOptions): boolean { + if (isGlob(pointer)) { + // FIXME: parse to find and check the file extensions? + return true; + } + if (isValidPath(pointer)) { if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer); @@ -84,6 +104,20 @@ export class CodeFileLoader implements UniversalLoader { return false; } + async resolveGlobs({ globs, ignores }: ResolverGlobs, options: CodeFileLoaderOptions) { + return globby( + globs.concat(ignores.map(v => `!(${v})`)).map(v => unixify(v)), + createGlobbyOptions(options) + ); + } + + resolveGlobsSync({ globs, ignores }: ResolverGlobs, options: CodeFileLoaderOptions) { + return globby.sync( + globs.concat(ignores.map(v => `!(${v})`)).map(v => unixify(v)), + createGlobbyOptions(options) + ); + } + async load(pointer: SchemaPointerSingle | DocumentPointerSingle, options: CodeFileLoaderOptions): Promise { const normalizedFilePath = ensureAbsolutePath(pointer, options); @@ -171,7 +205,11 @@ export class CodeFileLoader implements UniversalLoader { } } -function resolveSource(pointer: string, value: GraphQLSchema | DocumentNode | string, options: CodeFileLoaderOptions): Source | null { +function resolveSource( + pointer: string, + value: GraphQLSchema | DocumentNode | string, + options: CodeFileLoaderOptions +): Source | null { if (typeof value === 'string') { return parseGraphQLSDL(pointer, value, options); } else if (isSchema(value)) { diff --git a/packages/loaders/git/package.json b/packages/loaders/git/package.json index a897f560150..4c10e035ccf 100644 --- a/packages/loaders/git/package.json +++ b/packages/loaders/git/package.json @@ -20,9 +20,14 @@ "graphql": "^14.0.0 || ^15.0.0" }, "dependencies": { - "@graphql-tools/utils": "^7.0.0", "@graphql-tools/graphql-tag-pluck": "^6.2.6", - "tslib": "~2.2.0" + "@graphql-tools/utils": "^7.0.0", + "micromatch": "^4.0.4", + "tslib": "~2.2.0", + "unixify": "^1.0.0" + }, + "devDependencies": { + "@types/micromatch": "latest" }, "publishConfig": { "access": "public", diff --git a/packages/loaders/git/src/index.ts b/packages/loaders/git/src/index.ts index c42ecc95cf3..c6d4384eced 100644 --- a/packages/loaders/git/src/index.ts +++ b/packages/loaders/git/src/index.ts @@ -1,17 +1,17 @@ -import { UniversalLoader, SingleFileOptions } from '@graphql-tools/utils'; +import { UniversalLoader, SingleFileOptions, ResolverGlobs } from '@graphql-tools/utils'; import { GraphQLTagPluckOptions, gqlPluckFromCodeString, gqlPluckFromCodeStringSync, } from '@graphql-tools/graphql-tag-pluck'; +import micromatch from 'micromatch'; +import unixify from 'unixify'; -import { loadFromGit, loadFromGitSync } from './load-git'; +import { loadFromGit, loadFromGitSync, readTreeAtRef, readTreeAtRefSync } from './load-git'; import { parse } from './parse'; // git:branch:path/to/file -function extractData( - pointer: string -): { +function extractData(pointer: string): { ref: string; path: string; } { @@ -59,6 +59,58 @@ export class GitLoader implements UniversalLoader { return typeof pointer === 'string' && pointer.toLowerCase().startsWith('git:'); } + async resolveGlobs({ globs, ignores }: ResolverGlobs) { + const refsForPaths = new Map(); + + for (const glob of globs) { + const { ref, path } = extractData(glob); + if (!refsForPaths.has(ref)) { + refsForPaths.set(ref, []); + } + refsForPaths.get(ref).push(unixify(path)); + } + + for (const ignore of ignores) { + const { ref, path } = extractData(ignore); + if (!refsForPaths.has(ref)) { + refsForPaths.set(ref, []); + } + refsForPaths.get(ref).push(`!(${unixify(path)})`); + } + + const resolved: string[] = []; + for await (const [ref, paths] of refsForPaths.entries()) { + resolved.push(...micromatch(await readTreeAtRef(ref), paths).map(filePath => `git:${ref}:${filePath}`)); + } + return resolved; + } + + resolveGlobsSync({ globs, ignores }: ResolverGlobs) { + const refsForPaths = new Map(); + + for (const glob of globs) { + const { ref, path } = extractData(glob); + if (!refsForPaths.has(ref)) { + refsForPaths.set(ref, []); + } + refsForPaths.get(ref).push(unixify(path)); + } + + for (const ignore of ignores) { + const { ref, path } = extractData(ignore); + if (!refsForPaths.has(ref)) { + refsForPaths.set(ref, []); + } + refsForPaths.get(ref).push(`!(${unixify(path)})`); + } + + const resolved: string[] = []; + for (const [ref, paths] of refsForPaths.entries()) { + resolved.push(...micromatch(readTreeAtRefSync(ref), paths).map(filePath => `git:${ref}:${filePath}`)); + } + return resolved; + } + async load(pointer: string, options: GitLoaderOptions) { const { ref, path } = extractData(pointer); const content = await loadFromGit({ ref, path }); diff --git a/packages/loaders/git/src/load-git.ts b/packages/loaders/git/src/load-git.ts index 0f008dff899..2f390681138 100644 --- a/packages/loaders/git/src/load-git.ts +++ b/packages/loaders/git/src/load-git.ts @@ -1,25 +1,74 @@ import { execFile, execFileSync } from 'child_process'; +import os from 'os'; -type Input = { ref: string; path: string }; +type PartialInput = { ref: string }; +type Input = PartialInput & { path: string }; const createLoadError = (error: any) => new Error('Unable to load file from git: ' + error); -const createCommand = ({ ref, path }: Input): string[] => { +const createShowCommand = ({ ref, path }: Input): string[] => { return ['show', `${ref}:${path}`]; }; +const createTreeError = (error: Error) => new Error('Unable to load the file tree from git: ' + error); +const createTreeCommand = ({ ref }: PartialInput): string[] => { + return ['ls-tree', '-r', '--name-only', ref]; +}; + +/** + * @internal + */ +export async function readTreeAtRef(ref: string): Promise { + try { + return await new Promise((resolve, reject) => { + execFile( + 'git', + createTreeCommand({ ref }), + { encoding: 'utf-8', maxBuffer: 1024 * 1024 * 1024 }, + (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout.split(os.EOL).map(line => line.trim())); + } + } + ); + }); + } catch (error) { + throw createTreeError(error); + } +} + +/** + * @internal + */ +export function readTreeAtRefSync(ref: string): string[] | never { + try { + return execFileSync('git', createTreeCommand({ ref }), { encoding: 'utf-8' }) + .split(os.EOL) + .map(line => line.trim()); + } catch (error) { + throw createTreeError(error); + } +} + /** * @internal */ export async function loadFromGit(input: Input): Promise { try { return await new Promise((resolve, reject) => { - execFile('git', createCommand(input), { encoding: 'utf-8', maxBuffer: 1024 * 1024 * 1024 }, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); + execFile( + 'git', + createShowCommand(input), + { encoding: 'utf-8', maxBuffer: 1024 * 1024 * 1024 }, + (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout); + } } - }); + ); }); } catch (error) { throw createLoadError(error); @@ -31,7 +80,7 @@ export async function loadFromGit(input: Input): Promise { */ export function loadFromGitSync(input: Input): string | never { try { - return execFileSync('git', createCommand(input), { encoding: 'utf-8' }); + return execFileSync('git', createShowCommand(input), { encoding: 'utf-8' }); } catch (error) { throw createLoadError(error); } diff --git a/packages/loaders/graphql-file/package.json b/packages/loaders/graphql-file/package.json index 4258ae9558e..e06e356cc4f 100644 --- a/packages/loaders/graphql-file/package.json +++ b/packages/loaders/graphql-file/package.json @@ -22,9 +22,15 @@ "buildOptions": { "input": "./src/index.ts" }, + "devDependencies": { + "@types/is-glob": "4.0.1" + }, "dependencies": { "@graphql-tools/import": "^6.2.6", "@graphql-tools/utils": "^7.0.0", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "unixify": "^1.0.0", "tslib": "~2.2.0" }, "publishConfig": { diff --git a/packages/loaders/graphql-file/src/index.ts b/packages/loaders/graphql-file/src/index.ts index b0eb00c602a..c6d5ebe67e4 100644 --- a/packages/loaders/graphql-file/src/index.ts +++ b/packages/loaders/graphql-file/src/index.ts @@ -1,3 +1,5 @@ +import type { GlobbyOptions } from 'globby'; + import { Source, UniversalLoader, @@ -6,11 +8,15 @@ import { isValidPath, parseGraphQLSDL, SingleFileOptions, + ResolverGlobs, } from '@graphql-tools/utils'; import { isAbsolute, resolve } from 'path'; import { readFileSync, promises as fsPromises, existsSync } from 'fs'; import { cwd as processCwd } from 'process'; import { processImport } from '@graphql-tools/import'; +import globby from 'globby'; +import isGlob from 'is-glob'; +import unixify from 'unixify'; const { readFile, access } = fsPromises; @@ -31,6 +37,10 @@ function isGraphQLImportFile(rawSDL: string) { return trimmedRawSDL.startsWith('# import') || trimmedRawSDL.startsWith('#import'); } +function createGlobbyOptions(options: GraphQLFileLoaderOptions): GlobbyOptions { + return { absolute: true, ...options, ignore: [] }; +} + /** * This loader loads documents and type definitions from `.graphql` files. * @@ -63,6 +73,11 @@ export class GraphQLFileLoader implements UniversalLoader { + if (isGlob(pointer)) { + // FIXME: parse to find and check the file extensions? + return true; + } + if (isValidPath(pointer)) { if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || processCwd(), pointer); @@ -79,6 +94,11 @@ export class GraphQLFileLoader implements UniversalLoader pointer.endsWith(extension))) { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || processCwd(), pointer); @@ -89,6 +109,20 @@ export class GraphQLFileLoader implements UniversalLoader `!(${v})`)).map(v => unixify(v)), + createGlobbyOptions(options) + ); + } + + resolveGlobsSync({ globs, ignores }: ResolverGlobs, options: GraphQLFileLoaderOptions) { + return globby.sync( + globs.concat(ignores.map(v => `!(${v})`)).map(v => unixify(v)), + createGlobbyOptions(options) + ); + } + async load(pointer: SchemaPointerSingle | DocumentPointerSingle, options: GraphQLFileLoaderOptions): Promise { const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || processCwd(), pointer); const rawSDL: string = await readFile(normalizedFilePath, { encoding: 'utf8' }); diff --git a/packages/load/src/typings.d.ts b/packages/loaders/graphql-file/typings.d.ts similarity index 100% rename from packages/load/src/typings.d.ts rename to packages/loaders/graphql-file/typings.d.ts diff --git a/packages/utils/src/loaders.ts b/packages/utils/src/loaders.ts index 42ca6abe8a1..51f7ee46821 100644 --- a/packages/utils/src/loaders.ts +++ b/packages/utils/src/loaders.ts @@ -27,6 +27,8 @@ export interface Loader; canLoadSync?(pointer: TPointer, options?: TOptions): boolean; + resolveGlobs?(globs: ResolverGlobs, options?: TOptions): Promise; + resolveGlobsSync?(globs: ResolverGlobs, options?: TOptions): TPointer[]; load(pointer: TPointer, options?: TOptions): Promise; loadSync?(pointer: TPointer, options?: TOptions): Source | never; } @@ -45,3 +47,8 @@ export type UniversalLoader; + +export type ResolverGlobs = { + globs: string[]; + ignores: string[]; +}; diff --git a/yarn.lock b/yarn.lock index 9b307bc2687..d731b3d7f2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2490,6 +2490,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/braces@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" + integrity sha1-faHA1E/xx+tmCjbsB46mG6frQss= + "@types/connect@*": version "3.4.34" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" @@ -2730,6 +2735,13 @@ dependencies: "@types/unist" "*" +"@types/micromatch@latest": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" + integrity sha1-k4FEndZZ/Dgj/SpBkM6syYUIO8c= + dependencies: + "@types/braces" "*" + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -13035,7 +13047,7 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unixify@1.0.0: +unixify@1.0.0, unixify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" integrity sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA=