UNPKG

@graphql-tools/load-files

Version:

A set of utils for faster development of GraphQL tools

203 lines (202 loc) • 7.81 kB
import { promises as fsPromises, readFileSync, statSync } from 'fs'; import { createRequire } from 'module'; import { extname, isAbsolute, join } from 'path'; import { cwd } from 'process'; import { parse } from 'graphql'; import unixify from 'unixify'; import { asArray } from '@graphql-tools/utils'; import { matchFiles, matchFilesSync } from './matchFiles.js'; export { matchFiles, matchFilesSync } from './matchFiles.js'; const { readFile, stat } = fsPromises; const DEFAULT_IGNORED_EXTENSIONS = ['spec', 'test', 'd', 'map']; const DEFAULT_EXTENSIONS = ['gql', 'graphql', 'graphqls', 'ts', 'js']; const DEFAULT_EXPORT_NAMES = ['schema', 'typeDef', 'typeDefs', 'resolver', 'resolvers']; const DEFAULT_EXTRACT_EXPORTS_FACTORY = (exportNames) => (fileExport) => { if (!fileExport) { return null; } if (fileExport.default) { for (const exportName of exportNames) { if (fileExport.default[exportName]) { return fileExport.default[exportName]; } } return fileExport.default; } for (const exportName of exportNames) { if (fileExport[exportName]) { return fileExport[exportName]; } } return fileExport; }; function resolveAgainstCwd(path, baseCwd) { if (isAbsolute(path)) { return path; } return join(baseCwd || cwd(), path); } function isDirectorySync(path, baseCwd) { try { const pathStat = statSync(resolveAgainstCwd(path, baseCwd)); return pathStat.isDirectory(); } catch (e) { return false; } } async function isDirectory(path, baseCwd) { try { const pathStat = await stat(resolveAgainstCwd(path, baseCwd)); return pathStat.isDirectory(); } catch (e) { return false; } } function scanForFilesSync(globStr, globOptions = {}) { return matchFilesSync(globStr, { absolute: true, ...globOptions }); } function formatExtension(extension) { return extension.charAt(0) === '.' ? extension : `.${extension}`; } function buildGlob(basePath, extensions = [], ignoredExtensions = [], recursive) { const ignored = ignoredExtensions.length > 0 ? `!(${ignoredExtensions.map(e => `*${formatExtension(e)}`).join('|')})` : '*'; const ext = extensions.map(e => `*${formatExtension(e)}`).join('|'); return `${basePath}${recursive ? '/**' : ''}/${ignored}+(${ext})`; } const LoadFilesDefaultOptions = { ignoredExtensions: DEFAULT_IGNORED_EXTENSIONS, extensions: DEFAULT_EXTENSIONS, useRequire: false, requireMethod: null, globOptions: { absolute: true, cwd: cwd(), }, exportNames: DEFAULT_EXPORT_NAMES, recursive: true, ignoreIndex: false, }; /** * Synchronously loads files using the provided glob pattern. * @param pattern Glob pattern or patterns to use when loading files * @param options Additional options */ export function loadFilesSync(pattern, options = LoadFilesDefaultOptions) { const execOptions = { ...LoadFilesDefaultOptions, ...options }; const baseCwd = options.globOptions?.cwd || execOptions.globOptions?.cwd || cwd(); const relevantPaths = scanForFilesSync(asArray(pattern).map(path => isDirectorySync(path, baseCwd) ? buildGlob(unixify(path), execOptions.extensions, execOptions.ignoredExtensions, execOptions.recursive) : unixify(path)), options.globOptions); const extractExports = execOptions.extractExports || DEFAULT_EXTRACT_EXPORTS_FACTORY(execOptions.exportNames ?? []); const requireMethod = execOptions.requireMethod || createRequire(join(options?.globOptions?.cwd || cwd(), 'noop.js')); return relevantPaths .map(path => { if (!checkExtension(path, options)) { return null; } if (isIndex(path, execOptions.extensions) && options.ignoreIndex) { return false; } const extension = extname(path); if (extension === formatExtension('js') || extension === formatExtension('ts') || execOptions.useRequire) { const fileExports = requireMethod(path); const extractedExport = extractExports(fileExports); return extractedExport; } else { const maybeSDL = readFileSync(path, { encoding: 'utf-8' }); return tryToParse(maybeSDL); } }) .filter(v => v); } async function scanForFiles(globStr, globOptions = {}) { return matchFiles(globStr, { absolute: true, ...globOptions }); } const checkExtension = (path, { extensions, ignoredExtensions }) => { if (ignoredExtensions) { for (const ignoredExtension of ignoredExtensions) { if (path.endsWith(formatExtension(ignoredExtension))) { return false; } } } if (!extensions) { return true; } for (const extension of extensions) { const formattedExtension = formatExtension(extension); if (path.endsWith(formattedExtension)) { if (ignoredExtensions) { for (const ignoredExtension of ignoredExtensions) { const formattedIgnoredExtension = formatExtension(ignoredExtension); if (path.endsWith(formattedIgnoredExtension + formattedExtension)) { return false; } } } return true; } } return false; }; /** * Asynchronously loads files using the provided glob pattern. * @param pattern Glob pattern or patterns to use when loading files * @param options Additional options */ export async function loadFiles(pattern, options = LoadFilesDefaultOptions) { const execOptions = { ...LoadFilesDefaultOptions, ...options }; const baseCwd = options.globOptions?.cwd || execOptions.globOptions?.cwd || cwd(); const relevantPaths = await scanForFiles(await Promise.all(asArray(pattern).map(async (path) => (await isDirectory(path, baseCwd)) ? buildGlob(unixify(path), execOptions.extensions, execOptions.ignoredExtensions, execOptions.recursive) : unixify(path))), options.globOptions); const extractExports = execOptions.extractExports || DEFAULT_EXTRACT_EXPORTS_FACTORY(execOptions.exportNames ?? []); const defaultRequireMethod = (path) => import(path) .catch(importError => { const cwdRequire = createRequire(join(options?.globOptions?.cwd || cwd(), 'noop.js')); try { return cwdRequire(path); } catch (e) { throw importError; } }) .then(m => m.default || m); const requireMethod = execOptions.requireMethod || defaultRequireMethod; return Promise.all(relevantPaths .filter(path => checkExtension(path, options) && !(isIndex(path, execOptions.extensions) && options.ignoreIndex)) .map(async (path) => { const extension = extname(path); if (extension === formatExtension('js') || extension === formatExtension('ts') || execOptions.useRequire) { const fileExports = await requireMethod(path); const extractedExport = extractExports(fileExports); return extractedExport; } else { const maybeSDL = await readFile(path, { encoding: 'utf-8' }); return tryToParse(maybeSDL); } })); } function isIndex(path, extensions = []) { const IS_INDEX = /(\/|\\)index\.[^\/\\]+$/i; // (/ or \) AND `index.` AND (everything except \ and /)(end of line) return IS_INDEX.test(path) && extensions.some(ext => path.endsWith(formatExtension(ext))); } function tryToParse(maybeSDL) { try { return parse(maybeSDL); } catch (e) { return maybeSDL; } }