UNPKG

@graphql-tools/git-loader

Version:

A set of utils for faster development of GraphQL tools

220 lines (219 loc) • 7.79 kB
import { env } from 'process'; import { parse } from 'graphql'; import isGlob from 'is-glob'; import micromatch from 'micromatch'; import unixify from 'unixify'; import { gqlPluckFromCodeString, gqlPluckFromCodeStringSync, } from '@graphql-tools/graphql-tag-pluck'; import { asArray } from '@graphql-tools/utils'; import { loadFromGit, loadFromGitSync, readTreeAtRef, readTreeAtRefSync } from './load-git.js'; import { parse as handleStuff } from './parse.js'; // git:branch:path/to/file function extractData(pointer) { const parts = pointer.replace(/^git\:/i, '').split(':'); if (!parts || parts.length !== 2) { return null; } return { ref: parts[0], path: parts[1], }; } /** * This loader loads a file from git. * * ```js * const typeDefs = await loadTypedefs('git:someBranch:some/path/to/file.js', { * loaders: [new GitLoader()], * }) * ``` */ export class GitLoader { async canLoad(pointer) { return this.canLoadSync(pointer); } canLoadSync(pointer) { return typeof pointer === 'string' && pointer.toLowerCase().startsWith('git:'); } async resolveGlobs(glob, ignores) { const data = extractData(glob); if (data === null) { return []; } const refsForPaths = new Map(); const { ref, path } = data; if (!refsForPaths.has(ref)) { refsForPaths.set(ref, []); } refsForPaths.get(ref).push(unixify(path)); for (const ignore of ignores) { const data = extractData(ignore); if (data === null) { continue; } const { ref, path } = data; if (!refsForPaths.has(ref)) { refsForPaths.set(ref, []); } refsForPaths.get(ref).push(`!${unixify(path)}`); } const maybeLeadingDotSlash = path.startsWith('./') ? './' : ''; const resolved = []; await Promise.all([...refsForPaths.entries()].map(async ([ref, paths]) => { resolved.push(...micromatch(await readTreeAtRef(ref), paths).map(filePath => `git:${ref}:${maybeLeadingDotSlash}${filePath}`)); })); return resolved; } resolveGlobsSync(glob, ignores) { const data = extractData(glob); if (data === null) { return []; } const { ref, path } = data; const refsForPaths = new Map(); if (!refsForPaths.has(ref)) { refsForPaths.set(ref, []); } refsForPaths.get(ref).push(unixify(path)); for (const ignore of ignores) { const data = extractData(ignore); if (data === null) { continue; } const { ref, path } = data; if (!refsForPaths.has(ref)) { refsForPaths.set(ref, []); } refsForPaths.get(ref).push(`!${unixify(path)}`); } const maybeLeadingDotSlash = path.startsWith('./') ? './' : ''; const resolved = []; for (const [ref, paths] of refsForPaths.entries()) { resolved.push(...micromatch(readTreeAtRefSync(ref), paths).map(filePath => `git:${ref}:${maybeLeadingDotSlash}${filePath}`)); } return resolved; } async handleSingularPointerAsync(pointer, options) { const result = extractData(pointer); if (result === null) { return []; } const { ref, path } = result; const content = await loadFromGit({ ref, path }); const parsed = handleStuff({ path, options, pointer, content }); if (parsed) { return [parsed]; } const sources = await gqlPluckFromCodeString(pointer, content, options.pluckConfig); return sources.map(source => ({ location: pointer, document: parse(source, options), })); } async load(pointer, options) { const result = extractData(pointer); if (result === null) { return []; } const { path } = result; const finalResult = []; const errors = []; try { if (isGlob(path)) { const resolvedPaths = await this.resolveGlobs(pointer, asArray(options.ignore || [])); await Promise.all(resolvedPaths.map(async (path) => { const results = await this.load(path, options); results?.forEach(result => finalResult.push(result)); })); } else if (await this.canLoad(pointer)) { const results = await this.handleSingularPointerAsync(pointer, options); results?.forEach(result => finalResult.push(result)); } } catch (error) { if (env['DEBUG']) { console.error(error); } if (error instanceof AggregateError) { for (const errorElement of error.errors) { errors.push(errorElement); } } else { errors.push(error); } } if (finalResult.length === 0 && errors.length > 0) { if (errors.length === 1) { throw errors[0]; } throw new AggregateError(errors, `Reading from ${pointer} failed ; \n ` + errors.map((e) => e.message).join('\n')); } return finalResult; } handleSingularPointerSync(pointer, options) { const result = extractData(pointer); if (result === null) { return []; } const { ref, path } = result; const content = loadFromGitSync({ ref, path }); const parsed = handleStuff({ path, options, pointer, content }); if (parsed) { return [parsed]; } const sources = gqlPluckFromCodeStringSync(pointer, content, options.pluckConfig); return sources.map(source => ({ location: pointer, document: parse(source, options), })); } loadSync(pointer, options) { const result = extractData(pointer); if (result === null) { return []; } const { path } = result; const finalResult = []; const errors = []; try { if (isGlob(path)) { const resolvedPaths = this.resolveGlobsSync(pointer, asArray(options.ignore || [])); for (const path of resolvedPaths) { if (this.canLoadSync(path)) { const results = this.loadSync(path, options); for (const result of results) { finalResult.push(result); } } } } else if (this.canLoadSync(pointer)) { const results = this.handleSingularPointerSync(pointer, options); for (const result of results) { finalResult.push(result); } } } catch (error) { if (env['DEBUG']) { console.error(error); } if (error instanceof AggregateError) { for (const errorElement of error.errors) { errors.push(errorElement); } } else { errors.push(error); } } if (finalResult.length === 0 && errors.length > 0) { if (errors.length === 1) { throw errors[0]; } throw new AggregateError(errors, `Reading from ${pointer} failed ; \n ` + errors.map((e) => e.message).join('\n')); } return finalResult; } }