Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/graphql/lib/graphql-definitions.factory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { makeExecutableSchema } from '@graphql-tools/schema';
import { isEmpty } from '@nestjs/common/utils/shared.utils';
import chokidar from 'chokidar';
import { glob } from 'fast-glob';
import { printSchema } from 'graphql';
import { gql } from 'graphql-tag';
import {
Expand All @@ -10,6 +9,7 @@ import {
} from './graphql-ast.explorer';
import { GraphQLTypesLoader } from './graphql-types.loader';
import { extend, removeTempField } from './utils';
import { globPaths } from './utils/glob.util';

export type GenerateOptions = DefinitionsGeneratorOptions & {
typePaths: string[];
Expand Down Expand Up @@ -47,7 +47,7 @@ export class GraphQLDefinitionsFactory {
'GraphQL factory is watching your files...',
isDebugEnabled,
);
const watcher = chokidar.watch(await glob(options.typePaths));
const watcher = chokidar.watch(await globPaths(options.typePaths));
// eslint-disable-next-line @typescript-eslint/no-misused-promises
watcher.on('change', async (file) => {
this.printMessage(
Expand Down
4 changes: 2 additions & 2 deletions packages/graphql/lib/graphql-types.loader.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { mergeTypeDefs } from '@graphql-tools/merge';
import { Injectable } from '@nestjs/common';
import glob from 'fast-glob';
import * as fs from 'fs';
import { flatten } from 'lodash';
import * as util from 'util';
import { globPaths } from './utils/glob.util';

const normalize = require('normalize-path');
const readFile = util.promisify(fs.readFile);
Expand Down Expand Up @@ -31,7 +31,7 @@ export class GraphQLTypesLoader {
? paths.map((path) => normalize(path))
: normalize(paths);

const filePaths = await glob(paths, {
const filePaths = await globPaths(paths, {
ignore: includeNodeModules ? [] : ['node_modules'],
});
if (filePaths.length === 0) {
Expand Down
42 changes: 42 additions & 0 deletions packages/graphql/lib/utils/glob.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { isAbsolute, win32 } from 'path';
import { glob } from 'tinyglobby';
import type { GlobOptions } from 'tinyglobby';

type GlobPattern = string | string[];

export async function globPaths(
patterns: GlobPattern,
options: Omit<
GlobOptions,
'absolute' | 'expandDirectories' | 'patterns'
> = {},
): Promise<string[]> {
const patternsArray = Array.isArray(patterns) ? patterns : [patterns];
const negativePatterns = patternsArray.filter(isNegativePattern);
const positivePatterns = patternsArray.filter(
(pattern) => !isNegativePattern(pattern),
);
const globOptions = {
...options,
expandDirectories: false,
};

const matches = await Promise.all(
positivePatterns.map((pattern) =>
glob([pattern, ...negativePatterns], {
...globOptions,
absolute: isAbsoluteGlob(pattern),
}),
),
);

return [...new Set(matches.flat())];
}

function isNegativePattern(pattern: string): boolean {
return pattern[0] === '!' && pattern[1] !== '(';
}

function isAbsoluteGlob(pattern: string): boolean {
return isAbsolute(pattern) || win32.isAbsolute(pattern);
}
2 changes: 1 addition & 1 deletion packages/graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@
"@graphql-tools/utils": "11.1.0",
"@nestjs/mapped-types": "2.1.1",
"chokidar": "4.0.3",
"fast-glob": "3.3.3",
"graphql-tag": "2.12.6",
"graphql-ws": "6.0.8",
"lodash": "4.18.1",
"normalize-path": "3.0.0",
"subscriptions-transport-ws": "0.11.0",
"tinyglobby": "0.2.17",
"tslib": "2.8.1",
"ws": "8.20.1"
},
Expand Down
50 changes: 50 additions & 0 deletions packages/graphql/tests/graphql-definitions.factory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import chokidar from 'chokidar';
import { mkdtemp, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { GraphQLDefinitionsFactory } from '../lib/graphql-definitions.factory';

const watchMock = vi.hoisted(() =>
vi.fn(() => ({
on: vi.fn(),
})),
);

vi.mock('chokidar', () => ({
default: {
watch: watchMock,
},
}));

describe('GraphQLDefinitionsFactory', () => {
let directory: string;

beforeEach(async () => {
watchMock.mockClear();
directory = await mkdtemp(join(tmpdir(), 'nestjs-graphql-definitions-'));
});

afterEach(async () => {
await rm(directory, { force: true, recursive: true });
});

it('watches files resolved from type path glob patterns', async () => {
await writeFile(
join(directory, 'schema.graphql'),
'type Query { ok: Boolean }',
);

const factory = new GraphQLDefinitionsFactory();
await factory.generate({
typePaths: [join(directory, '*.graphql')],
path: join(directory, 'graphql.ts'),
outputAs: 'class',
watch: true,
debug: false,
});

expect(chokidar.watch).toHaveBeenCalledWith([
join(directory, 'schema.graphql'),
]);
});
});
35 changes: 35 additions & 0 deletions packages/graphql/tests/graphql-types.loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { mkdtemp, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { GraphQLTypesLoader } from '../lib/graphql-types.loader';

describe('GraphQLTypesLoader', () => {
let directory: string;

beforeEach(async () => {
directory = await mkdtemp(join(tmpdir(), 'nestjs-graphql-types-'));
});

afterEach(async () => {
await rm(directory, { force: true, recursive: true });
});

it('loads type definitions from glob patterns', async () => {
await writeFile(
join(directory, 'cat.graphql'),
'type Cat { id: ID!, name: String! }',
);
await writeFile(
join(directory, 'query.graphql'),
'type Query { cat: Cat }',
);

const loader = new GraphQLTypesLoader();
const typeDefs = await loader.mergeTypesByPaths(
join(directory, '*.graphql'),
);

expect(typeDefs).toContain('type Query');
expect(typeDefs).toContain('type Cat');
});
});
106 changes: 106 additions & 0 deletions packages/graphql/tests/utils/glob.util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { mkdtemp, rm, writeFile } from 'fs/promises';
import { join, relative } from 'path';
import { globPaths } from '../../lib/utils/glob.util';

describe('globPaths', () => {
let directory: string;
let absoluteDirectory: string;

beforeEach(async () => {
directory = await mkdtemp(join(process.cwd(), 'tmp-glob-'));
await writeFile(
join(directory, 'schema.graphql'),
'type Query { ok: Boolean }',
);
absoluteDirectory = await mkdtemp(join(process.cwd(), 'tmp-abs-glob-'));
await writeFile(
join(absoluteDirectory, 'schema.graphql'),
'type Query { absolute: Boolean }',
);
});

afterEach(async () => {
await rm(directory, { force: true, recursive: true });
await rm(absoluteDirectory, { force: true, recursive: true });
});

it('preserves absolute matches for absolute patterns', async () => {
await expect(globPaths(join(directory, '*.graphql'))).resolves.toEqual([
join(directory, 'schema.graphql'),
]);
});

it('preserves relative matches for relative patterns', async () => {
const relativePattern = relative(
process.cwd(),
join(directory, '*.graphql'),
);
const relativeSchemaPath = relative(
process.cwd(),
join(directory, 'schema.graphql'),
);

await expect(globPaths(relativePattern)).resolves.toEqual([
relativeSchemaPath,
]);
});

it('preserves pattern order when absolute and relative patterns are mixed', async () => {
const relativePattern = relative(
process.cwd(),
join(directory, '*.graphql'),
);
const relativeSchemaPath = relative(
process.cwd(),
join(directory, 'schema.graphql'),
);
const absolutePattern = join(absoluteDirectory, '*.graphql');
const absoluteSchemaPath = join(absoluteDirectory, 'schema.graphql');

await expect(
globPaths([relativePattern, absolutePattern]),
).resolves.toEqual([relativeSchemaPath, absoluteSchemaPath]);
});

it('applies negative patterns', async () => {
await writeFile(
join(directory, 'skip.graphql'),
'type Query { skip: Boolean }',
);
const relativePattern = relative(
process.cwd(),
join(directory, '*.graphql'),
);
const relativeSchemaPath = relative(
process.cwd(),
join(directory, 'schema.graphql'),
);
const relativeSkipPath = relative(
process.cwd(),
join(directory, 'skip.graphql'),
);

await expect(
globPaths([relativePattern, `!${relativeSkipPath}`]),
).resolves.toEqual([relativeSchemaPath]);
});

it('deduplicates matches from repeated patterns', async () => {
const relativePattern = relative(
process.cwd(),
join(directory, '*.graphql'),
);
const relativeSchemaPath = relative(
process.cwd(),
join(directory, 'schema.graphql'),
);

await expect(
globPaths([relativePattern, relativePattern]),
).resolves.toEqual([relativeSchemaPath]);
});

it('does not expand directory patterns', async () => {
await expect(globPaths(directory)).resolves.toEqual([]);
});
});
Loading