-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathgetSchema.ts
171 lines (150 loc) · 4.68 KB
/
getSchema.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import type { Stats, PathLike } from 'node:fs';
import fs from 'node:fs/promises';
import path from 'path';
import type { IntrospectionQuery } from 'graphql';
import {
type SchemaLoaderResult,
type SchemaRef as _SchemaRef,
type GraphQLSPConfig,
loadRef,
minifyIntrospection,
outputIntrospectionFile,
resolveTypeScriptRootDir,
} from '@gql.tada/internal';
import { ts } from '../ts';
import { Logger } from '../index';
const statFile = (
file: PathLike,
predicate: (stat: Stats) => boolean
): Promise<boolean> => {
return fs
.stat(file)
.then(predicate)
.catch(() => false);
};
const touchFile = async (file: PathLike): Promise<void> => {
try {
const now = new Date();
await fs.utimes(file, now, now);
} catch (_error) {}
};
/** Writes a file to a swapfile then moves it into place to prevent excess change events. */
export const swapWrite = async (
target: PathLike,
contents: string
): Promise<void> => {
if (!(await statFile(target, stat => stat.isFile()))) {
// If the file doesn't exist, we can write directly, and not
// try-catch so the error falls through
await fs.writeFile(target, contents);
} else {
// If the file exists, we write to a swap-file, then rename (i.e. move)
// the file into place. No try-catch around `writeFile` for proper
// directory/permission errors
const tempTarget = target + '.tmp';
await fs.writeFile(tempTarget, contents);
try {
await fs.rename(tempTarget, target);
} catch (error) {
await fs.unlink(tempTarget);
throw error;
} finally {
// When we move the file into place, we also update its access and
// modification time manually, in case the rename doesn't trigger
// a change event
await touchFile(target);
}
}
};
async function saveTadaIntrospection(
introspection: IntrospectionQuery,
tadaOutputLocation: string,
disablePreprocessing: boolean,
logger: Logger
) {
const minified = minifyIntrospection(introspection);
const contents = outputIntrospectionFile(minified, {
fileType: tadaOutputLocation,
shouldPreprocess: !disablePreprocessing,
});
let output = tadaOutputLocation;
if (await statFile(output, stat => stat.isDirectory())) {
output = path.join(output, 'introspection.d.ts');
} else if (
!(await statFile(path.dirname(output), stat => stat.isDirectory()))
) {
logger(`Output file is not inside a directory @ ${output}`);
return;
}
try {
await swapWrite(output, contents);
logger(`Introspection saved to path @ ${output}`);
} catch (error) {
logger(`Failed to write introspection @ ${error}`);
}
}
export type SchemaRef = _SchemaRef<SchemaLoaderResult | null>;
export const loadSchema = (
// TODO: abstract info away
info: ts.server.PluginCreateInfo,
origin: GraphQLSPConfig,
logger: Logger
): _SchemaRef<SchemaLoaderResult | null> => {
const ref = loadRef(origin);
(async () => {
const rootPath =
(await resolveTypeScriptRootDir(info.project.getProjectName())) ||
path.dirname(info.project.getProjectName());
const tadaDisablePreprocessing =
info.config.tadaDisablePreprocessing ?? false;
const tadaOutputLocation =
info.config.tadaOutputLocation &&
path.resolve(rootPath, info.config.tadaOutputLocation);
logger('Got root-directory to resolve schema from: ' + rootPath);
logger('Resolving schema from "schema" config: ' + JSON.stringify(origin));
try {
logger(`Loading schema...`);
await ref.load({ rootPath });
} catch (error) {
logger(`Failed to load schema: ${error}`);
}
if (ref.current) {
if (ref.current && ref.current.tadaOutputLocation !== undefined) {
saveTadaIntrospection(
ref.current.introspection,
tadaOutputLocation,
tadaDisablePreprocessing,
logger
);
}
} else if (ref.multi) {
Object.values(ref.multi).forEach(value => {
if (!value) return;
if (value.tadaOutputLocation) {
saveTadaIntrospection(
value.introspection,
path.resolve(rootPath, value.tadaOutputLocation),
tadaDisablePreprocessing,
logger
);
}
});
}
ref.autoupdate({ rootPath }, (schemaRef, value) => {
if (!value) return;
if (value.tadaOutputLocation) {
const found = schemaRef.multi
? schemaRef.multi[value.name as string]
: schemaRef.current;
if (!found) return;
saveTadaIntrospection(
found.introspection,
path.resolve(rootPath, value.tadaOutputLocation),
tadaDisablePreprocessing,
logger
);
}
});
})();
return ref as any;
};