-
Notifications
You must be signed in to change notification settings - Fork 463
Expand file tree
/
Copy pathloadConfig.ts
More file actions
220 lines (195 loc) · 7.14 KB
/
loadConfig.ts
File metadata and controls
220 lines (195 loc) · 7.14 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import cosmiconfig from "cosmiconfig";
import { LoaderEntry } from "cosmiconfig";
import TypeScriptLoader from "@endemolshinegroup/cosmiconfig-typescript-loader";
import { resolve } from "path";
import { readFileSync, existsSync, lstatSync } from "fs";
import merge from "lodash.merge";
import {
ApolloConfig,
ApolloConfigFormat,
DefaultConfigBase,
DefaultClientConfig,
DefaultServiceConfig,
DefaultEngineConfig
} from "./config";
import { getServiceFromKey } from "./utils";
import URI from "vscode-uri";
import { Debug } from "../utilities";
// config settings
const MODULE_NAME = "apollo";
const defaultFileNames = [
"package.json",
`${MODULE_NAME}.config.js`,
`${MODULE_NAME}.config.ts`,
`${MODULE_NAME}.config.cjs`
];
const envFileNames = [".env", ".env.local"];
const loaders = {
// XXX improve types for config
".json": (cosmiconfig as any).loadJson as LoaderEntry,
".js": (cosmiconfig as any).loadJs as LoaderEntry,
".cjs": (cosmiconfig as any).loadJs as LoaderEntry,
".ts": {
async: TypeScriptLoader
}
};
export const legacyKeyEnvVar = "ENGINE_API_KEY";
export const keyEnvVar = "APOLLO_KEY";
export interface LoadConfigSettings {
// the current working directory to start looking for the config
// config loading only works on node so we default to
// process.cwd()
// configPath and fileName are used in conjunction with one another.
// i.e. /User/myProj/my.config.js
// => { configPath: '/User/myProj/', configFileName: 'my.config.js' }
configPath?: string;
// if a configFileName is passed in, loadConfig won't accept any other
// configs as a fallback.
configFileName?: string;
// used when run by a `Workspace` where we _know_ a config file should be present.
requireConfig?: boolean;
// for CLI usage, we don't _require_ a config file for everything. This allows us to pass in
// options to build one at runtime
name?: string;
type?: "service" | "client";
}
export type ConfigResult<T> = {
config: T;
filepath: string;
} | null;
// XXX load .env files automatically
export async function loadConfig({
configPath,
configFileName,
requireConfig = false,
name,
type
}: LoadConfigSettings) {
const explorer = cosmiconfig(MODULE_NAME, {
searchPlaces: configFileName ? [configFileName] : defaultFileNames,
loaders
});
// search can fail if a file can't be parsed (ex: a nonsense js file) so we wrap in a try/catch
let loadedConfig;
try {
loadedConfig = (await explorer.search(configPath)) as ConfigResult<
ApolloConfigFormat
>;
} catch (error) {
return Debug.error(`A config file failed to load with options: ${JSON.stringify(
arguments[0]
)}.
The error was: ${error}`);
}
if (configPath && !loadedConfig) {
return Debug.error(
`A config file failed to load at '${configPath}'. This is likely because this file is empty or malformed. For more information, please refer to: https://go.apollo.dev/t/config`
);
}
if (loadedConfig && loadedConfig.filepath.endsWith("package.json")) {
Debug.warning(
'The "apollo" package.json configuration key will no longer be supported in Apollo v3. Please use the apollo.config.js file for Apollo project configuration. For more information, see: https://go.apollo.dev/t/config'
);
}
if (requireConfig && !loadedConfig) {
return Debug.error(
`No Apollo config found for project. For more information, please refer to: https://go.apollo.dev/t/config`
);
}
// add API key from the env
let engineConfig = {},
apiKey,
nameFromKey;
// loop over the list of possible .env files and try to parse for key
// and service name. Files are scanned and found values are preferred
// in order of appearance in `envFileNames`.
envFileNames.forEach(envFile => {
const dotEnvPath = configPath
? resolve(configPath, envFile)
: resolve(process.cwd(), envFile);
if (existsSync(dotEnvPath) && lstatSync(dotEnvPath).isFile()) {
const env: { [key: string]: string } = require("dotenv").parse(
readFileSync(dotEnvPath)
);
const legacyKey = env[legacyKeyEnvVar];
const key = env[keyEnvVar];
if (legacyKey && key) {
Debug.warning(
`Both ${legacyKeyEnvVar} and ${keyEnvVar} were found. ${keyEnvVar} will take precedence.`
);
}
if (legacyKey) {
Debug.warning(
`[Deprecation warning] Setting the key via ${legacyKeyEnvVar} is deprecated and will not be supported in future versions. Please use ${keyEnvVar} instead.`
);
}
apiKey = key || legacyKey;
}
});
if (apiKey) {
engineConfig = { engine: { apiKey } };
nameFromKey = getServiceFromKey(apiKey);
}
// DETERMINE PROJECT TYPE
// The CLI passes in a type when loading config. The editor extension
// does not. So we determine the type of the config here, and use it if
// the type wasn't explicitly passed in.
let projectType: "client" | "service";
if (type) projectType = type;
else if (loadedConfig && loadedConfig.config.client) projectType = "client";
else if (loadedConfig && loadedConfig.config.service) projectType = "service";
else
return Debug.error(
"Unable to resolve project type. Please add either a client or service config. For more information, please refer to https://go.apollo.dev/t/config"
);
// DETERMINE SERVICE NAME
// precedence: 1. (highest) config.js (client only) 2. name passed into loadConfig 3. name from api key
let serviceName = name || nameFromKey;
if (
projectType === "client" &&
loadedConfig &&
loadedConfig.config.client &&
typeof loadedConfig.config.client.service === "string"
) {
serviceName = loadedConfig.config.client.service;
}
// if there wasn't a config loaded from a file, build one.
// if there was a service name found in the env, merge it with the new/existing config object.
// if the config loaded doesn't have a client/service key, add one based on projectType
if (
!loadedConfig ||
serviceName ||
!(loadedConfig.config.client || loadedConfig.config.service)
) {
loadedConfig = {
filepath: configPath || process.cwd(),
config: {
...(loadedConfig && loadedConfig.config),
...(projectType === "client"
? {
client: {
...DefaultConfigBase,
...(loadedConfig && loadedConfig.config.client),
service: serviceName
}
}
: {
service: {
...DefaultConfigBase,
...(loadedConfig && loadedConfig.config.service),
name: serviceName
}
})
}
};
}
let { config, filepath } = loadedConfig;
// selectively apply defaults when loading the config
// this is just the includes/excludes defaults.
// These need to go on _all_ configs. That's why this is last.
if (config.client) config = merge({ client: DefaultClientConfig }, config);
if (config.service) config = merge({ service: DefaultServiceConfig }, config);
if (engineConfig) config = merge(engineConfig, config);
config = merge({ engine: DefaultEngineConfig }, config);
return new ApolloConfig(config, URI.file(resolve(filepath)));
}