Skip to content

Commit

Permalink
feat(git-loader): add glob support (#3012)
Browse files Browse the repository at this point in the history
* 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 <ardatanrikulu@gmail.com>
  • Loading branch information
PixnBits and ardatan authored Jun 8, 2021
1 parent 5a67c63 commit c664c82
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 61 deletions.
2 changes: 0 additions & 2 deletions packages/load/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
143 changes: 103 additions & 40 deletions packages/load/src/load-typedefs/collect-sources.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
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';
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;
Expand Down Expand Up @@ -38,10 +44,7 @@ export async function collectSources<TOptions>({
});

for (const pointer in pointerOptionMap) {
const pointerOptions = {
...(pointerOptionMap[pointer] ?? {}),
unixify,
};
const pointerOptions = pointerOptionMap[pointer];

collect({
pointer,
Expand All @@ -55,12 +58,8 @@ export async function collectSources<TOptions>({
}

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,
Expand Down Expand Up @@ -100,10 +99,7 @@ export function collectSourcesSync<TOptions>({
});

for (const pointer in pointerOptionMap) {
const pointerOptions = {
...(pointerOptionMap[pointer] ?? {}),
unixify,
};
const pointerOptions = pointerOptionMap[pointer];

collect({
pointer,
Expand All @@ -117,12 +113,7 @@ export function collectSourcesSync<TOptions>({
}

if (globs.length) {
includeIgnored({
options,
globs,
});

const paths = globbySync(globs, createGlobbyOptions(options));
const paths = collectPathsFromGlobsSync(globs, options);

collectSourcesFromGlobalsSync({
filepaths: paths,
Expand All @@ -139,8 +130,6 @@ export function collectSourcesSync<TOptions>({
return sources;
}

//

function createHelpers<T>({
sources,
globs,
Expand Down Expand Up @@ -184,24 +173,98 @@ function createHelpers<T>({
};
}

function includeIgnored<
T extends {
ignore?: string | string[];
async function addGlobsToLoaders({
options,
loadersForGlobs,
globs,
type,
}: {
options: LoadTypedefsOptions;
loadersForGlobs: Map<Loader, ResolverGlobs>;
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<string>(unixify);

if (ignoreList.length > 0) {
globs.push(...ignoreList);
}

function addGlobsToLoadersSync({
options,
loadersForGlobs,
globs,
type,
}: {
options: LoadTypedefsOptions;
loadersForGlobs: Map<Loader, ResolverGlobs>;
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<string[]> {
const paths: string[] = [];

const loadersForGlobs: Map<Loader, ResolverGlobs> = 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<Loader, ResolverGlobs> = 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<T, P>({
Expand Down Expand Up @@ -342,9 +405,9 @@ function collectDocumentString<T>(
}

function collectGlob<T>({ pointer, pointerOptions, addGlob }: CollectOptions<T>, next: StackNext) {
if (isGlob(pointerOptions.unixify(pointer))) {
if (isGlob(pointer)) {
return addGlob({
pointer: pointerOptions.unixify(pointer),
pointer,
pointerOptions,
});
}
Expand Down
8 changes: 7 additions & 1 deletion packages/loaders/code-file/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
40 changes: 39 additions & 1 deletion packages/loaders/code-file/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { GlobbyOptions } from 'globby';

import { isSchema, GraphQLSchema, DocumentNode } from 'graphql';
import {
SchemaPointerSingle,
Expand All @@ -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';
Expand All @@ -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`.
Expand All @@ -58,6 +68,11 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
pointer: SchemaPointerSingle | DocumentPointerSingle,
options: CodeFileLoaderOptions
): Promise<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);
Expand All @@ -74,6 +89,11 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
}

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);
Expand All @@ -84,6 +104,20 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
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<Source> {
const normalizedFilePath = ensureAbsolutePath(pointer, options);

Expand Down Expand Up @@ -171,7 +205,11 @@ export class CodeFileLoader implements UniversalLoader<CodeFileLoaderOptions> {
}
}

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)) {
Expand Down
9 changes: 7 additions & 2 deletions packages/loaders/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit c664c82

Please sign in to comment.