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
|
/*** IMPORT ------------------------------------------- ***/
import { dirname, join } from "@std/path";
/*** UTILITY ------------------------------------------ ***/
const { cwd, readFileSync } = Deno;
const DYNAMIC_IMPORT_REGEX = /^#\s(DYNAMIC_IMPORTS)/gm;
const ENCODING = "utf-8";
const FILE_REGEX = /\w*(.graphql)/g;
const IMPORT_REGEX = /^#\s(import)\s.*(.graphql")/gm;
/*** EXPORT ------------------------------------------- ***/
/**
* Reads a `.graphql` file and resolves its imports into a single SDL string.
*
* Two mechanisms are supported, and both can appear in the same file:
*
* 1. **Explicit imports** — lines like `# import "./user.graphql"` are replaced
* inline with the contents of the referenced file, resolved relative to the
* entry file.
* 2. **Dynamic imports** — if the file contains `# DYNAMIC_IMPORTS`, every
* `.graphql` file found one level below `<cwd>/schema/` is inlined at that
* marker. Useful for feature-folder layouts.
*
* Errors are logged and an empty string is returned so boot doesn't crash.
*
* @param path - Path to the entry `.graphql` file, relative to `Deno.cwd()`.
* @returns The fully-expanded schema string.
*/
export async function importQL(path: string): Promise<string> {
const SCHEMA_DIRECTORY = join(cwd(), "schema");
try {
const decoder = new TextDecoder(ENCODING);
const file = readFileSync(join(cwd(), String(path)));
const imports = decoder.decode(file).match(IMPORT_REGEX) || [];
const shouldTryDynamicallyImporting = decoder.decode(file).match(DYNAMIC_IMPORT_REGEX) ? true : false;
let parsedFile = decoder.decode(file);
/*** `import` statements in the supplied schema file
are parsed to dynamically bring in linked files. ***/
imports.map((imp: string) => {
const matchedFilename: null | Array<string> = imp.match(FILE_REGEX);
if (!matchedFilename || !matchedFilename.length || matchedFilename.length < 1)
return;
const filename = matchedFilename[0];
const importedFileDecoder = new TextDecoder(ENCODING);
const importedFile = Deno.readFileSync(join(dirname(String(path)), filename));
const decodedFile = importedFileDecoder.decode(importedFile);
parsedFile = parsedFile.replace(imp, decodedFile);
});
/*** With dynamic importing, we just look inside the `program`
directory to find `.graphql` files and automatically bring
them in, if `# DYNAMIC_IMPORTS` exists in `schema.graphql`. ***/
if (shouldTryDynamicallyImporting) {
const graphqlFiles = [];
for await (const dirEntry of Deno.readDir(SCHEMA_DIRECTORY)) {
const { isDirectory } = dirEntry;
if (isDirectory) {
const DIR = join(SCHEMA_DIRECTORY, dirEntry.name);
for await (const dirEntry of Deno.readDir(DIR)) {
const { isFile } = dirEntry;
if (isFile && dirEntry.name.match(FILE_REGEX))
graphqlFiles.push(join(DIR, dirEntry.name));
}
}
}
for (const file of graphqlFiles) {
const importedFileDecoder = new TextDecoder(ENCODING);
const importedFile = Deno.readFileSync(file);
const decodedFile = importedFileDecoder.decode(importedFile);
const insertPosition = parsedFile.indexOf("# DYNAMIC_IMPORTS");
if (insertPosition !== -1) {
parsedFile =
parsedFile.substring(0, insertPosition) +
decodedFile +
parsedFile.substring(insertPosition);
}
}
}
return parsedFile;
} catch(parseError) {
console.error(new Error(`error parsing file [${String(path)}]`), parseError);
return "";
}
}
|