Skip to content

Commit e74caad

Browse files
committed
feat: add option to automatically add schema and document files referenced in the codegen config to the vite file watcher
resolves #37
1 parent 885191d commit e74caad

6 files changed

Lines changed: 137 additions & 1 deletion

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ codegen({
5858
* @default true
5959
*/
6060
enableWatcher: boolean,
61+
/**
62+
* Automatically add schemas and documents referenced in the codegen config
63+
* to the file watcher.
64+
*
65+
* @default true
66+
*/
67+
watchCodegenConfigFiles: boolean,
6168
/**
6269
* Throw an error if codegen fails on server start.
6370
*

src/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export interface Options {
3030
* @default true
3131
*/
3232
enableWatcher?: boolean;
33+
/**
34+
* Automatically add schemas and documents referenced in the codegen config
35+
* to the Vite file watcher.
36+
*
37+
* @default true
38+
*/
39+
watchCodegenConfigFiles?: boolean;
3340
/**
3441
* Throw an error if codegen fails on server start.
3542
*
@@ -98,6 +105,7 @@ export function GraphQLCodegen(options?: Options): Plugin {
98105
runOnStart = true,
99106
runOnBuild = true,
100107
enableWatcher = true,
108+
watchCodegenConfigFiles = true,
101109
throwOnStart = false,
102110
throwOnBuild = true,
103111
matchOnDocuments = true,
@@ -135,7 +143,7 @@ export function GraphQLCodegen(options?: Options): Plugin {
135143

136144
return {
137145
name: "graphql-codegen",
138-
async config(_config, env) {
146+
async config(_userConfig, env) {
139147
try {
140148
if (config) {
141149
log("Manual config passed, creating codegen context");
@@ -219,6 +227,11 @@ export function GraphQLCodegen(options?: Options): Plugin {
219227
try {
220228
log("Match cache initialing");
221229
await matchCache.init();
230+
231+
if (watchCodegenConfigFiles) {
232+
log("Adding codegen config files to watcher", matchCache.entries());
233+
server.watcher.add(matchCache.entries());
234+
}
222235
log("Match cache initialized");
223236
} catch (error) {
224237
log("Match cache initialization failed", error);

src/utils/matchCache.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ export function createMatchCache(
2929
init: refresh,
3030
refresh,
3131
has: (filePath: string) => cache.has(normalizePath(filePath)),
32+
entries: () => Array.from(cache),
3233
};
3334
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`watch-outside-cwd > generates on schema change outside of cwd 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+
foo?: Maybe<Scalars['String']['output']>;
24+
};
25+
26+
export type FooQueryVariables = Exact<{ [key: string]: never; }>;
27+
28+
29+
export type FooQuery = { __typename?: 'Query', foo?: string | null };
30+
"
31+
`;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
schema: ./test/watch-outside-cwd/schema.graphql
2+
documents: ./test/watch-outside-cwd/vite-root/graphql/**/*.graphql
3+
generates:
4+
./test/watch-outside-cwd/vite-root/generated/graphql.ts:
5+
plugins:
6+
- typescript
7+
- typescript-operations
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { promises as fs } from "node:fs";
2+
import { join } from "node:path";
3+
import { createServer, type UserConfig, type ViteDevServer } from "vite";
4+
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
5+
import codegen from "../../src/index";
6+
7+
const TEST_PATH = "./test/watch-outside-cwd" as const;
8+
const SCHEMA_PATH = `${TEST_PATH}/schema.graphql` as const;
9+
const DOCUMENT_PATH = `${TEST_PATH}/vite-root/graphql` as const;
10+
const OUTPUT_PATH = `${TEST_PATH}/vite-root/generated` as const;
11+
const OUTPUT_FILE_NAME = "graphql.ts" as const;
12+
const OUTPUT_FILE = `${OUTPUT_PATH}/${OUTPUT_FILE_NAME}` as const;
13+
14+
const viteConfig = {
15+
root: join(import.meta.dirname, "./vite-root"),
16+
plugins: [
17+
codegen({
18+
runOnStart: false,
19+
matchOnDocuments: false,
20+
matchOnSchemas: true,
21+
watchCodegenConfigFiles: true,
22+
configFilePathOverride: `${TEST_PATH}/vite-root/codegen.yml`,
23+
}),
24+
],
25+
} satisfies UserConfig;
26+
27+
describe("watch-outside-cwd", () => {
28+
let viteServer: ViteDevServer | null = null;
29+
30+
const isFileGenerated = async (): Promise<boolean> => {
31+
try {
32+
await new Promise((resolve) => setTimeout(resolve, 200));
33+
await fs.access(OUTPUT_FILE);
34+
return true;
35+
} catch {
36+
// ignore
37+
}
38+
39+
return new Promise((resolve, reject) => {
40+
if (!viteServer) reject("Vite server not started");
41+
42+
viteServer?.watcher.on("add", (path: string) => {
43+
if (path.includes(OUTPUT_FILE_NAME)) resolve(true);
44+
});
45+
46+
setTimeout(() => reject("Generated file not found"), 5000);
47+
});
48+
};
49+
50+
beforeAll(async () => {
51+
await fs.writeFile(SCHEMA_PATH, "type Query { foo: String }");
52+
await fs.mkdir(DOCUMENT_PATH, { recursive: true });
53+
await fs.writeFile(`${DOCUMENT_PATH}/Foo.graphql`, "query Foo { foo }");
54+
viteServer = await createServer(viteConfig).then((s) => s.listen());
55+
});
56+
57+
afterAll(async () => {
58+
await viteServer?.close();
59+
viteServer = null;
60+
await fs.rm(SCHEMA_PATH, { recursive: true });
61+
await fs.rm(DOCUMENT_PATH, { recursive: true });
62+
});
63+
64+
afterEach(async () => {
65+
await fs.rm(OUTPUT_PATH, { recursive: true });
66+
});
67+
68+
it("generates on schema change outside of cwd", async () => {
69+
await fs.writeFile(SCHEMA_PATH, "type Query { foo: String bar: Int }");
70+
71+
await isFileGenerated();
72+
73+
const file = await fs.readFile(OUTPUT_FILE, "utf-8");
74+
75+
expect(file).toMatchSnapshot();
76+
});
77+
});

0 commit comments

Comments
 (0)