Skip to content

Support glob paths in schema #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 23, 2024
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ codegen({
*/
matchOnDocuments?: boolean;
/**
* Run codegen when a schema matches. Only supports file path based schemas.
* Run codegen when a schema matches.
*
* @defaultValue `false`
*/
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface Options {
*/
matchOnDocuments?: boolean;
/**
* Run codegen when a schema matches. Only supports file path based schemas.
* Run codegen when a schema matches.
*
* @defaultValue `false`
*/
Expand Down
16 changes: 10 additions & 6 deletions src/utils/configPaths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { normalizePath } from 'vite';
import { resolve } from 'node:path';
import type { CodegenContext } from '@graphql-codegen/cli';
import type { Types } from '@graphql-codegen/plugin-helpers/typings/types';

export async function getDocumentPaths(
context: CodegenContext,
Expand Down Expand Up @@ -44,15 +44,19 @@ export async function getSchemaPaths(
sourceSchemas.unshift(config.schema);
}

const schemas = sourceSchemas
const normalized = sourceSchemas
.filter((item): item is NonNullable<typeof item> => !!item)
.flat();

if (!schemas.length) return [];
if (!normalized.length) return [];

const schemas = await context.loadSchema(
// loadSchema supports array of string, but typings are wrong
normalized as unknown as Types.Schema,
);

return schemas
.filter((schema): schema is string => typeof schema === 'string')
return (schemas.extensions.sources as { name: string }[])
.map(({ name = '' }) => name)
.filter(Boolean)
.map((schema) => resolve(schema))
.map(normalizePath);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`match-on-glob-schema > generates on schema change 1`] = `
"export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
};

export type Query = {
__typename?: 'Query';
bar?: Maybe<Scalars['Int']['output']>;
baz?: Maybe<Scalars['Int']['output']>;
foo?: Maybe<Scalars['Int']['output']>;
qux?: Maybe<Scalars['Int']['output']>;
};

export type FooQueryVariables = Exact<{ [key: string]: never; }>;


export type FooQuery = { __typename?: 'Query', foo?: number | null };
"
`;
9 changes: 9 additions & 0 deletions test/match-on-glob-schema/codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
schema:
- ./test/match-on-glob-schema/dir-1/**/*.graphql
- ./test/match-on-glob-schema/dir-2/**/*.graphql
documents: ./test/match-on-glob-schema/graphql/**/*.graphql
generates:
./test/match-on-glob-schema/generated/graphql.ts:
plugins:
- typescript
- typescript-operations
95 changes: 95 additions & 0 deletions test/match-on-glob-schema/match-on-glob-schema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
import { createServer, UserConfig, ViteDevServer } from 'vite';
import { promises as fs } from 'node:fs';
import codegen from '../../src/index';

const TEST_PATH = './test/match-on-glob-schema' as const;
const DIR_PATH_1 = `${TEST_PATH}/dir-1` as const;
const DIR_PATH_2 = `${TEST_PATH}/dir-2` as const;
const SCHEMA_PATH_1 = `${DIR_PATH_1}/schema-1.graphql` as const;
const SCHEMA_PATH_2 = `${DIR_PATH_1}/schema-2.graphql` as const;
const SCHEMA_PATH_3 = `${DIR_PATH_2}/schema-1.graphql` as const;
const SCHEMA_PATH_4 = `${DIR_PATH_2}/schema-2.graphql` as const;
const DOCUMENT_PATH = `${TEST_PATH}/graphql` as const;
const OUTPUT_PATH = `${TEST_PATH}/generated` as const;
const OUTPUT_FILE_NAME = 'graphql.ts' as const;
const OUTPUT_FILE = `${OUTPUT_PATH}/${OUTPUT_FILE_NAME}` as const;

const viteConfig = {
plugins: [
codegen({
runOnStart: false,
matchOnDocuments: false,
matchOnSchemas: true,
configFilePathOverride: `${TEST_PATH}/codegen.yml`,
}),
],
} satisfies UserConfig;

describe('match-on-glob-schema', () => {
let viteServer: ViteDevServer | null = null;

const isFileGenerated = async (): Promise<boolean> => {
try {
await fs.access(OUTPUT_FILE);
return true;
} catch (error) {
// ignore
}

return new Promise((resolve, reject) => {
if (!viteServer) reject('Vite server not started');

viteServer?.watcher.on('add', (path: string) => {
if (path.includes(OUTPUT_FILE_NAME)) resolve(true);
});

setTimeout(() => reject('Generated file not found'), 5000);
});
};

beforeAll(async () => {
// Files in dir1
await fs.mkdir(DIR_PATH_1, { recursive: true });
await fs.writeFile(SCHEMA_PATH_1, 'type Query { foo: String }');
await fs.writeFile(SCHEMA_PATH_2, 'type Query { bar: String }');

// Files in dir2
await fs.mkdir(DIR_PATH_2, { recursive: true });
await fs.writeFile(SCHEMA_PATH_3, 'type Query { baz: String }');
await fs.writeFile(SCHEMA_PATH_4, 'type Query { qux: String }');

await fs.mkdir(DOCUMENT_PATH, { recursive: true });
await fs.writeFile(`${DOCUMENT_PATH}/Foo.graphql`, 'query Foo { foo }');
viteServer = await createServer(viteConfig).then((s) => s.listen());
});

afterAll(async () => {
await viteServer?.close();
viteServer = null;
await fs.rm(DIR_PATH_1, { recursive: true });
await fs.rm(DIR_PATH_2, { recursive: true });

await fs.rm(DOCUMENT_PATH, { recursive: true });
});

afterEach(async () => {
await fs.rm(OUTPUT_PATH, { recursive: true });
});

it('generates on schema change', async () => {
// Files in dir1
await fs.writeFile(SCHEMA_PATH_1, 'type Query { foo: Int }');
await fs.writeFile(SCHEMA_PATH_2, 'type Query { bar: Int }');

// Files in dir2
await fs.writeFile(SCHEMA_PATH_3, 'type Query { baz: Int }');
await fs.writeFile(SCHEMA_PATH_4, 'type Query { qux: Int }');

await isFileGenerated();

const file = await fs.readFile(OUTPUT_FILE, 'utf-8');

expect(file).toMatchSnapshot();
});
});