Skip to content

Commit 2efef2e

Browse files
authored
feat: support glob paths in schema (#33)
closes #34
1 parent 863a61a commit 2efef2e

File tree

6 files changed

+149
-8
lines changed

6 files changed

+149
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ codegen({
7777
*/
7878
matchOnDocuments?: boolean;
7979
/**
80-
* Run codegen when a schema matches. Only supports file path based schemas.
80+
* Run codegen when a schema matches.
8181
*
8282
* @defaultValue `false`
8383
*/

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export interface Options {
5252
*/
5353
matchOnDocuments?: boolean;
5454
/**
55-
* Run codegen when a schema matches. Only supports file path based schemas.
55+
* Run codegen when a schema matches.
5656
*
5757
* @defaultValue `false`
5858
*/

src/utils/configPaths.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { normalizePath } from 'vite';
2-
import { resolve } from 'node:path';
32
import type { CodegenContext } from '@graphql-codegen/cli';
3+
import type { Types } from '@graphql-codegen/plugin-helpers/typings/types';
44

55
export async function getDocumentPaths(
66
context: CodegenContext,
@@ -44,15 +44,19 @@ export async function getSchemaPaths(
4444
sourceSchemas.unshift(config.schema);
4545
}
4646

47-
const schemas = sourceSchemas
47+
const normalized = sourceSchemas
4848
.filter((item): item is NonNullable<typeof item> => !!item)
4949
.flat();
5050

51-
if (!schemas.length) return [];
51+
if (!normalized.length) return [];
52+
53+
const schemas = await context.loadSchema(
54+
// loadSchema supports array of string, but typings are wrong
55+
normalized as unknown as Types.Schema,
56+
);
5257

53-
return schemas
54-
.filter((schema): schema is string => typeof schema === 'string')
58+
return (schemas.extensions.sources as { name: string }[])
59+
.map(({ name = '' }) => name)
5560
.filter(Boolean)
56-
.map((schema) => resolve(schema))
5761
.map(normalizePath);
5862
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`match-on-glob-schema > generates on schema change 1`] = `
4+
"export type Maybe<T> = T | null;
5+
export type InputMaybe<T> = Maybe<T>;
6+
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
7+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
8+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
9+
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
10+
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
11+
/** All built-in and custom scalars, mapped to their actual values */
12+
export type Scalars = {
13+
ID: { input: string; output: string; }
14+
String: { input: string; output: string; }
15+
Boolean: { input: boolean; output: boolean; }
16+
Int: { input: number; output: number; }
17+
Float: { input: number; output: number; }
18+
};
19+
20+
export type Query = {
21+
__typename?: 'Query';
22+
bar?: Maybe<Scalars['Int']['output']>;
23+
baz?: Maybe<Scalars['Int']['output']>;
24+
foo?: Maybe<Scalars['Int']['output']>;
25+
qux?: Maybe<Scalars['Int']['output']>;
26+
};
27+
28+
export type FooQueryVariables = Exact<{ [key: string]: never; }>;
29+
30+
31+
export type FooQuery = { __typename?: 'Query', foo?: number | null };
32+
"
33+
`;

test/match-on-glob-schema/codegen.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
schema:
2+
- ./test/match-on-glob-schema/dir-1/**/*.graphql
3+
- ./test/match-on-glob-schema/dir-2/**/*.graphql
4+
documents: ./test/match-on-glob-schema/graphql/**/*.graphql
5+
generates:
6+
./test/match-on-glob-schema/generated/graphql.ts:
7+
plugins:
8+
- typescript
9+
- typescript-operations
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
2+
import { createServer, UserConfig, ViteDevServer } from 'vite';
3+
import { promises as fs } from 'node:fs';
4+
import codegen from '../../src/index';
5+
6+
const TEST_PATH = './test/match-on-glob-schema' as const;
7+
const DIR_PATH_1 = `${TEST_PATH}/dir-1` as const;
8+
const DIR_PATH_2 = `${TEST_PATH}/dir-2` as const;
9+
const SCHEMA_PATH_1 = `${DIR_PATH_1}/schema-1.graphql` as const;
10+
const SCHEMA_PATH_2 = `${DIR_PATH_1}/schema-2.graphql` as const;
11+
const SCHEMA_PATH_3 = `${DIR_PATH_2}/schema-1.graphql` as const;
12+
const SCHEMA_PATH_4 = `${DIR_PATH_2}/schema-2.graphql` as const;
13+
const DOCUMENT_PATH = `${TEST_PATH}/graphql` as const;
14+
const OUTPUT_PATH = `${TEST_PATH}/generated` as const;
15+
const OUTPUT_FILE_NAME = 'graphql.ts' as const;
16+
const OUTPUT_FILE = `${OUTPUT_PATH}/${OUTPUT_FILE_NAME}` as const;
17+
18+
const viteConfig = {
19+
plugins: [
20+
codegen({
21+
runOnStart: false,
22+
matchOnDocuments: false,
23+
matchOnSchemas: true,
24+
configFilePathOverride: `${TEST_PATH}/codegen.yml`,
25+
}),
26+
],
27+
} satisfies UserConfig;
28+
29+
describe('match-on-glob-schema', () => {
30+
let viteServer: ViteDevServer | null = null;
31+
32+
const isFileGenerated = async (): Promise<boolean> => {
33+
try {
34+
await fs.access(OUTPUT_FILE);
35+
return true;
36+
} catch (error) {
37+
// ignore
38+
}
39+
40+
return new Promise((resolve, reject) => {
41+
if (!viteServer) reject('Vite server not started');
42+
43+
viteServer?.watcher.on('add', (path: string) => {
44+
if (path.includes(OUTPUT_FILE_NAME)) resolve(true);
45+
});
46+
47+
setTimeout(() => reject('Generated file not found'), 5000);
48+
});
49+
};
50+
51+
beforeAll(async () => {
52+
// Files in dir1
53+
await fs.mkdir(DIR_PATH_1, { recursive: true });
54+
await fs.writeFile(SCHEMA_PATH_1, 'type Query { foo: String }');
55+
await fs.writeFile(SCHEMA_PATH_2, 'type Query { bar: String }');
56+
57+
// Files in dir2
58+
await fs.mkdir(DIR_PATH_2, { recursive: true });
59+
await fs.writeFile(SCHEMA_PATH_3, 'type Query { baz: String }');
60+
await fs.writeFile(SCHEMA_PATH_4, 'type Query { qux: String }');
61+
62+
await fs.mkdir(DOCUMENT_PATH, { recursive: true });
63+
await fs.writeFile(`${DOCUMENT_PATH}/Foo.graphql`, 'query Foo { foo }');
64+
viteServer = await createServer(viteConfig).then((s) => s.listen());
65+
});
66+
67+
afterAll(async () => {
68+
await viteServer?.close();
69+
viteServer = null;
70+
await fs.rm(DIR_PATH_1, { recursive: true });
71+
await fs.rm(DIR_PATH_2, { recursive: true });
72+
73+
await fs.rm(DOCUMENT_PATH, { recursive: true });
74+
});
75+
76+
afterEach(async () => {
77+
await fs.rm(OUTPUT_PATH, { recursive: true });
78+
});
79+
80+
it('generates on schema change', async () => {
81+
// Files in dir1
82+
await fs.writeFile(SCHEMA_PATH_1, 'type Query { foo: Int }');
83+
await fs.writeFile(SCHEMA_PATH_2, 'type Query { bar: Int }');
84+
85+
// Files in dir2
86+
await fs.writeFile(SCHEMA_PATH_3, 'type Query { baz: Int }');
87+
await fs.writeFile(SCHEMA_PATH_4, 'type Query { qux: Int }');
88+
89+
await isFileGenerated();
90+
91+
const file = await fs.readFile(OUTPUT_FILE, 'utf-8');
92+
93+
expect(file).toMatchSnapshot();
94+
});
95+
});

0 commit comments

Comments
 (0)