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
|
/*** IMPORT ------------------------------------------- ***/
import { dirname, resolve } from "@std/path";
/*** UTILITY ------------------------------------------ ***/
const { readFileSync } = Deno;
const DYNAMIC_IMPORT_MARKER = "# DYNAMIC_IMPORTS";
const DYNAMIC_IMPORT_REGEX = /^#\s(DYNAMIC_IMPORTS)/gm;
const ENCODING = "utf-8";
const IMPORT_PATH_REGEX = /"([^"]+\.graphql)"/;
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 the entry file’s directory is
* inlined at that marker. Useful for feature-folder layouts.
*
* All paths are resolved relative to the entry file itself, so consumers are
* free to place their schema wherever they like.
*
* Errors are logged and an empty string is returned so boot doesn’t crash.
*
* @param path - Path to the entry `.graphql` file. Absolute paths are used
* as-is; relative paths are resolved against `Deno.cwd()`.
* @returns The fully-expanded schema string.
*/
export async function importQL(path: string): Promise<string> {
try {
const decoder = new TextDecoder(ENCODING);
const entryPath = resolve(String(path));
const entryDir = dirname(entryPath);
const mainContent = decoder.decode(readFileSync(entryPath));
const imports = mainContent.match(IMPORT_REGEX) || [];
const shouldTryDynamicallyImporting = DYNAMIC_IMPORT_REGEX.test(mainContent);
let parsedFile = mainContent;
/*** `import` statements in the supplied schema file
are parsed to dynamically bring in linked files. ***/
for (const imp of imports) {
const matched = imp.match(IMPORT_PATH_REGEX);
if (!matched || !matched[1])
continue;
const importedFile = readFileSync(resolve(entryDir, matched[1]));
const decodedFile = decoder.decode(importedFile);
parsedFile = parsedFile.replace(imp, decodedFile);
}
/*** With dynamic importing, we look one level below the entry
file’s directory to find `.graphql` files and automatically
bring them in, if `# DYNAMIC_IMPORTS` exists in the entry. ***/
if (shouldTryDynamicallyImporting) {
const graphqlFiles: string[] = [];
for await (const dirEntry of Deno.readDir(entryDir)) {
if (!dirEntry.isDirectory)
continue;
const subDir = resolve(entryDir, dirEntry.name);
for await (const sub of Deno.readDir(subDir)) {
if (sub.isFile && sub.name.endsWith(".graphql"))
graphqlFiles.push(resolve(subDir, sub.name));
}
}
for (const file of graphqlFiles) {
const decodedFile = decoder.decode(readFileSync(file));
const insertPosition = parsedFile.indexOf(DYNAMIC_IMPORT_MARKER);
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 "";
}
}
|