-
Notifications
You must be signed in to change notification settings - Fork 5
/
configuration.ts
82 lines (74 loc) · 2.59 KB
/
configuration.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
import * as v from "@badrap/valita";
import fs from "node:fs";
const zoektUrlSchema = v.string().map((u) => new URL(u));
const fileConfigurationSchema = v.object({
zoektUrl: zoektUrlSchema.optional(),
openGrokProjectMappings: v
.record(v.string())
.map((o) => new Map(Object.entries(o)))
.optional(),
});
type FileConfiguration = v.Infer<typeof fileConfigurationSchema>;
const defaultConfigFilePath = "/etc/neogrok/config.json";
const environmentConfigurationSchema = v.object({
ZOEKT_URL: zoektUrlSchema.optional(),
});
type Configuration = {
readonly zoektUrl: URL;
readonly openGrokProjectMappings: ReadonlyMap<string, string>;
};
// We have to export a not-yet-bound `configuration` at module eval time because
// either SvelteKit or Vite is _executing_ this module during the build, for
// reasons that I do not understand. We do not want to enforce required
// configuration options being defined at build time; neogrok does not prerender
// anything.
//
// So, we do not want to resolve the configuration at module eval time.
//
// So, we rely on live export bindings plus a call to `resolveConfiguration`
// from something else at server startup. Anything consuming the actual
// configuration will have to avoid dereferencing it in the module scope.
export let configuration: Configuration;
export const resolveConfiguration: () => Promise<void> = async () => {
if (configuration !== undefined) {
return;
}
const configFilePath =
process.env.NEOGROK_CONFIG_FILE ?? defaultConfigFilePath;
let fileConfig: FileConfiguration | undefined;
try {
fileConfig = fileConfigurationSchema.parse(
JSON.parse(await fs.promises.readFile(configFilePath, "utf8")),
{ mode: "strict" },
);
} catch (e) {
// Swallow errors related to the default config file being missing.
if (
!(
e &&
typeof e === "object" &&
"code" in e &&
e.code === "ENOENT" &&
configFilePath === defaultConfigFilePath
)
) {
throw new Error(`Configuration file at ${configFilePath} is invalid`, {
cause: e,
});
}
}
const environmentConfig = environmentConfigurationSchema.parse(process.env, {
mode: "strip",
});
const zoektUrl = environmentConfig.ZOEKT_URL ?? fileConfig?.zoektUrl;
if (zoektUrl === undefined) {
throw new Error(
`"ZOEKT_URL" must be defined in the environment, or "zoektUrl" must be defined in the configuration file at ${configFilePath}`,
);
}
configuration = {
zoektUrl,
openGrokProjectMappings:
fileConfig?.openGrokProjectMappings ?? new Map<string, string>(),
};
};