@push.rocks/qenv 7.0.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/qenv',
6
- version: '7.0.0',
6
+ version: '8.0.0',
7
7
  description: 'A module for easily handling environment variables in Node.js projects with support for .yml and .json configuration.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHVIQUF1SDtDQUNySSxDQUFBIn0=
@@ -1 +1,2 @@
1
1
  export * from './qenv.classes.qenv.js';
2
+ export * from './qenv.classes.missingrequiredenvvarserror.js';
package/dist_ts/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './qenv.classes.qenv.js';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHdCQUF3QixDQUFDIn0=
2
+ export * from './qenv.classes.missingrequiredenvvarserror.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsK0NBQStDLENBQUMifQ==
@@ -0,0 +1,18 @@
1
+ export interface IQenvMissingRequiredEnvVarsErrorOptions {
2
+ missingEnvVars: string[];
3
+ requiredEnvVars: string[];
4
+ qenvFilePathAbsolute: string;
5
+ }
6
+ /**
7
+ * Thrown when a variable listed under `required:` in qenv.yml is not provided by any source.
8
+ * The names are carried as data so a caller can report or branch on them without parsing the
9
+ * message, and `code` identifies the refusal even when two copies of qenv end up in one tree,
10
+ * which makes `instanceof` unreliable.
11
+ */
12
+ export declare class QenvMissingRequiredEnvVarsError extends Error {
13
+ readonly code = "QENV_MISSING_REQUIRED_ENV_VARS";
14
+ readonly missingEnvVars: string[];
15
+ readonly requiredEnvVars: string[];
16
+ readonly qenvFilePathAbsolute: string;
17
+ constructor(optionsArg: IQenvMissingRequiredEnvVarsErrorOptions);
18
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Thrown when a variable listed under `required:` in qenv.yml is not provided by any source.
3
+ * The names are carried as data so a caller can report or branch on them without parsing the
4
+ * message, and `code` identifies the refusal even when two copies of qenv end up in one tree,
5
+ * which makes `instanceof` unreliable.
6
+ */
7
+ export class QenvMissingRequiredEnvVarsError extends Error {
8
+ constructor(optionsArg) {
9
+ super(`qenv is missing required environment variables: ${optionsArg.missingEnvVars.join(', ')}. ` +
10
+ `They are listed under "required:" in ${optionsArg.qenvFilePathAbsolute} and were found ` +
11
+ `neither in the process environment, nor in the env file, nor in the Docker secrets.`);
12
+ this.code = 'QENV_MISSING_REQUIRED_ENV_VARS';
13
+ this.name = 'QenvMissingRequiredEnvVarsError';
14
+ this.missingEnvVars = [...optionsArg.missingEnvVars];
15
+ this.requiredEnvVars = [...optionsArg.requiredEnvVars];
16
+ this.qenvFilePathAbsolute = optionsArg.qenvFilePathAbsolute;
17
+ }
18
+ }
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicWVudi5jbGFzc2VzLm1pc3NpbmdyZXF1aXJlZGVudnZhcnNlcnJvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3FlbnYuY2xhc3Nlcy5taXNzaW5ncmVxdWlyZWRlbnZ2YXJzZXJyb3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBTUE7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sK0JBQWdDLFNBQVEsS0FBSztJQU14RCxZQUFZLFVBQW1EO1FBQzdELEtBQUssQ0FDSCxtREFBbUQsVUFBVSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUk7WUFDekYsd0NBQXdDLFVBQVUsQ0FBQyxvQkFBb0Isa0JBQWtCO1lBQ3pGLHFGQUFxRixDQUN4RixDQUFDO1FBVlksU0FBSSxHQUFHLGdDQUFnQyxDQUFDO1FBV3RELElBQUksQ0FBQyxJQUFJLEdBQUcsaUNBQWlDLENBQUM7UUFDOUMsSUFBSSxDQUFDLGNBQWMsR0FBRyxDQUFDLEdBQUcsVUFBVSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQ3JELElBQUksQ0FBQyxlQUFlLEdBQUcsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUN2RCxJQUFJLENBQUMsb0JBQW9CLEdBQUcsVUFBVSxDQUFDLG9CQUFvQixDQUFDO0lBQzlELENBQUM7Q0FDRiJ9
@@ -1,32 +1,71 @@
1
1
  import * as plugins from './qenv.plugins.js';
2
+ /**
3
+ * a reference to an environment variable: its name, or an async function that produces the value.
4
+ * The function form is only honoured by the async getters; the synchronous getter accepts names.
5
+ */
2
6
  export type TEnvVarRef = string | (() => Promise<string>);
3
- type TKeyValueObject = Record<string, any>;
7
+ /** the resolved value of every available required env var, always a string */
8
+ export type TEnvVarValueMap = Record<string, string>;
4
9
  export declare class Qenv {
10
+ /** the names listed under `required:` in qenv.yml */
5
11
  requiredEnvVars: string[];
12
+ /** the required names that a source provided */
6
13
  availableEnvVars: string[];
14
+ /** the required names that no source provided */
7
15
  missingEnvVars: string[];
8
- keyValueObject: TKeyValueObject;
16
+ /** the resolved value of every available required name */
17
+ keyValueObject: TEnvVarValueMap;
9
18
  logger: plugins.smartlog.ConsoleLog;
10
19
  qenvFilePathAbsolute: string;
11
20
  envFilePathAbsolute?: string;
21
+ /**
22
+ * Resolves every name listed under `required:` in qenv.yml while constructing.
23
+ * @param qenvFileBasePathArg directory that holds qenv.yml
24
+ * @param envFileBasePathArg directory that holds env.json, env.yml or env.yaml
25
+ * @param failOnMissing throws a QenvMissingRequiredEnvVarsError when a variable listed under
26
+ * `required:` in qenv.yml is not provided by any source. Pass false to only record the names in
27
+ * `missingEnvVars` and continue.
28
+ * @throws QenvMissingRequiredEnvVarsError
29
+ */
12
30
  constructor(qenvFileBasePathArg?: string, envFileBasePathArg?: string, failOnMissing?: boolean);
13
31
  private initializeFilePaths;
14
32
  private loadRequiredEnvVars;
15
33
  private loadAvailableEnvVars;
16
34
  private checkForMissingEnvVars;
35
+ /**
36
+ * Resolves an env var from the process environment, the env file and the Docker secrets, in that
37
+ * order. An array is tried left to right and the first defined value wins.
38
+ * @param envVarNameOrNames a name, an async resolver function, or a list of either
39
+ */
17
40
  getEnvVarOnDemand(envVarNameOrNames: TEnvVarRef | TEnvVarRef[]): Promise<string | undefined>;
18
41
  /**
19
- * Like getEnvVarOnDemand, but throws an error if the env var is not set.
20
- * @param envVarNameOrNames
21
- * @returns
42
+ * Like getEnvVarOnDemand, but throws when no source provides a value.
43
+ * @param envVarNameOrNames a name, an async resolver function, or a list of either
22
44
  */
23
45
  getEnvVarOnDemandStrict(envVarNameOrNames: TEnvVarRef | TEnvVarRef[]): Promise<string>;
46
+ /**
47
+ * The synchronous counterpart of getEnvVarOnDemand. It resolves names only: an async resolver
48
+ * function cannot be awaited here, so the function form of TEnvVarRef is not accepted.
49
+ * @param envVarNameOrNames a name or a list of names
50
+ */
24
51
  getEnvVarOnDemandSync(envVarNameOrNames: string | string[]): string | undefined;
25
- getEnvVarOnDemandAsObject(envVarNameOrNames: string | string[]): Promise<any>;
52
+ /**
53
+ * Resolves an env var whose value was stored as a base64 encoded object and decodes it. A plain
54
+ * value is returned as the string it is, so the caller narrows what it gets.
55
+ * @param envVarNameOrNames a name or a list of names
56
+ */
57
+ getEnvVarOnDemandAsObject(envVarNameOrNames: string | string[]): Promise<unknown>;
26
58
  private tryGetEnvVar;
27
59
  private tryGetEnvVarSync;
60
+ /** renders env var references for an error message, naming a resolver function where it has one */
61
+ private describeEnvVarRefs;
28
62
  private getFromEnvironmentVariable;
29
63
  private getFromEnvYamlOrJsonFile;
64
+ /**
65
+ * the directory Docker mounts secrets into. It is a method so a test can point both secret
66
+ * readers at a directory it is allowed to create; a process can never write /run/secrets itself.
67
+ */
68
+ protected getDockerSecretsDirectoryPath(): string;
30
69
  private getFromDockerSecret;
31
70
  private getFromDockerSecretJson;
32
71
  private encodeBase64;
@@ -35,4 +74,3 @@ export declare class Qenv {
35
74
  private directoryExists;
36
75
  private readObjectFromFile;
37
76
  }
38
- export {};
@@ -1,9 +1,23 @@
1
1
  import * as plugins from './qenv.plugins.js';
2
+ import { QenvMissingRequiredEnvVarsError } from './qenv.classes.missingrequiredenvvarserror.js';
2
3
  export class Qenv {
4
+ /**
5
+ * Resolves every name listed under `required:` in qenv.yml while constructing.
6
+ * @param qenvFileBasePathArg directory that holds qenv.yml
7
+ * @param envFileBasePathArg directory that holds env.json, env.yml or env.yaml
8
+ * @param failOnMissing throws a QenvMissingRequiredEnvVarsError when a variable listed under
9
+ * `required:` in qenv.yml is not provided by any source. Pass false to only record the names in
10
+ * `missingEnvVars` and continue.
11
+ * @throws QenvMissingRequiredEnvVarsError
12
+ */
3
13
  constructor(qenvFileBasePathArg = process.cwd(), envFileBasePathArg, failOnMissing = true) {
14
+ /** the names listed under `required:` in qenv.yml */
4
15
  this.requiredEnvVars = [];
16
+ /** the required names that a source provided */
5
17
  this.availableEnvVars = [];
18
+ /** the required names that no source provided */
6
19
  this.missingEnvVars = [];
20
+ /** the resolved value of every available required name */
7
21
  this.keyValueObject = {};
8
22
  this.logger = new plugins.smartlog.ConsoleLog();
9
23
  this.qenvFilePathAbsolute = '';
@@ -38,20 +52,25 @@ export class Qenv {
38
52
  }
39
53
  }
40
54
  loadRequiredEnvVars() {
41
- if (this.fileExists(this.qenvFilePathAbsolute)) {
42
- const qenvFile = this.readObjectFromFile(this.qenvFilePathAbsolute);
43
- const requiredEnvVars = qenvFile.required;
44
- if (Array.isArray(requiredEnvVars)) {
45
- this.requiredEnvVars.push(...requiredEnvVars.filter((envVar) => typeof envVar === 'string'));
46
- }
47
- else {
48
- this.logger.log('warn', 'qenv.yml does not contain a "required" Array!');
49
- }
55
+ if (!this.fileExists(this.qenvFilePathAbsolute)) {
56
+ return;
50
57
  }
58
+ const qenvFile = this.readObjectFromFile(this.qenvFilePathAbsolute);
59
+ const declaredRequirements = qenvFile['required'];
60
+ if (!Array.isArray(declaredRequirements)) {
61
+ this.logger.log('warn', 'qenv.yml does not contain a "required" Array!');
62
+ return;
63
+ }
64
+ const declaredEntries = declaredRequirements;
65
+ this.requiredEnvVars.push(...declaredEntries.filter((entry) => typeof entry === 'string'));
51
66
  }
52
67
  loadAvailableEnvVars() {
68
+ // resolved synchronously: a required env var is always a name, and every source a name can come
69
+ // from - process environment, env file, Docker secrets - is a synchronous read. Resolving it
70
+ // here as a promise would store a pending promise instead of a value and make the check below
71
+ // pass for every name.
53
72
  for (const envVar of this.requiredEnvVars) {
54
- const value = this.getEnvVarOnDemand(envVar);
73
+ const value = this.tryGetEnvVarSync(envVar);
55
74
  if (value !== undefined) {
56
75
  this.availableEnvVars.push(envVar);
57
76
  this.keyValueObject[envVar] = value;
@@ -60,18 +79,24 @@ export class Qenv {
60
79
  }
61
80
  checkForMissingEnvVars(failOnMissing) {
62
81
  this.missingEnvVars = this.requiredEnvVars.filter((envVar) => !this.availableEnvVars.includes(envVar));
63
- if (this.missingEnvVars.length > 0) {
64
- console.info('Required Env Vars are:', this.requiredEnvVars);
65
- console.error('Missing Env Vars:', this.missingEnvVars);
66
- if (failOnMissing) {
67
- this.logger.log('error', 'Exiting due to missing env vars!');
68
- process.exit(1);
69
- }
70
- else {
71
- this.logger.log('warn', 'qenv is not set to fail on missing environment variables');
72
- }
82
+ if (this.missingEnvVars.length === 0) {
83
+ return;
73
84
  }
85
+ if (failOnMissing) {
86
+ // a library must not end the process: the caller decides what an incomplete environment means
87
+ throw new QenvMissingRequiredEnvVarsError({
88
+ missingEnvVars: this.missingEnvVars,
89
+ requiredEnvVars: this.requiredEnvVars,
90
+ qenvFilePathAbsolute: this.qenvFilePathAbsolute,
91
+ });
92
+ }
93
+ this.logger.log('warn', `qenv is not set to fail on missing environment variables. Missing: ${this.missingEnvVars.join(', ')}`);
74
94
  }
95
+ /**
96
+ * Resolves an env var from the process environment, the env file and the Docker secrets, in that
97
+ * order. An array is tried left to right and the first defined value wins.
98
+ * @param envVarNameOrNames a name, an async resolver function, or a list of either
99
+ */
75
100
  async getEnvVarOnDemand(envVarNameOrNames) {
76
101
  if (Array.isArray(envVarNameOrNames)) {
77
102
  for (const envVarName of envVarNameOrNames) {
@@ -87,19 +112,22 @@ export class Qenv {
87
112
  }
88
113
  }
89
114
  /**
90
- * Like getEnvVarOnDemand, but throws an error if the env var is not set.
91
- * @param envVarNameOrNames
92
- * @returns
115
+ * Like getEnvVarOnDemand, but throws when no source provides a value.
116
+ * @param envVarNameOrNames a name, an async resolver function, or a list of either
93
117
  */
94
118
  async getEnvVarOnDemandStrict(envVarNameOrNames) {
95
119
  const value = await this.getEnvVarOnDemand(envVarNameOrNames);
96
120
  if (value === undefined) {
97
- throw new Error(`Env var ${envVarNameOrNames} is not set!`);
121
+ throw new Error(`Env var ${this.describeEnvVarRefs(envVarNameOrNames)} is not set!`);
98
122
  }
99
123
  return value;
100
124
  }
125
+ /**
126
+ * The synchronous counterpart of getEnvVarOnDemand. It resolves names only: an async resolver
127
+ * function cannot be awaited here, so the function form of TEnvVarRef is not accepted.
128
+ * @param envVarNameOrNames a name or a list of names
129
+ */
101
130
  getEnvVarOnDemandSync(envVarNameOrNames) {
102
- console.warn('requesting env var sync leaves out potentially important async env sources.');
103
131
  if (Array.isArray(envVarNameOrNames)) {
104
132
  for (const envVarName of envVarNameOrNames) {
105
133
  const value = this.tryGetEnvVarSync(envVarName);
@@ -113,6 +141,11 @@ export class Qenv {
113
141
  return this.tryGetEnvVarSync(envVarNameOrNames);
114
142
  }
115
143
  }
144
+ /**
145
+ * Resolves an env var whose value was stored as a base64 encoded object and decodes it. A plain
146
+ * value is returned as the string it is, so the caller narrows what it gets.
147
+ * @param envVarNameOrNames a name or a list of names
148
+ */
116
149
  async getEnvVarOnDemandAsObject(envVarNameOrNames) {
117
150
  const rawValue = await this.getEnvVarOnDemand(envVarNameOrNames);
118
151
  if (rawValue && rawValue.startsWith('base64Object:')) {
@@ -125,33 +158,36 @@ export class Qenv {
125
158
  if (typeof envVarRefArg === 'function') {
126
159
  return await envVarRefArg();
127
160
  }
128
- const sources = [
129
- this.getFromEnvironmentVariable(envVarRefArg),
130
- this.getFromEnvYamlOrJsonFile(envVarRefArg),
131
- this.getFromDockerSecret(envVarRefArg),
132
- this.getFromDockerSecretJson(envVarRefArg)
133
- ];
134
- for (const value of sources) {
135
- if (value !== undefined) {
136
- return value;
137
- }
138
- }
139
- return undefined;
161
+ // a name resolves from synchronous sources only, so both getters share one resolution order
162
+ return this.tryGetEnvVarSync(envVarRefArg);
140
163
  }
141
164
  tryGetEnvVarSync(envVarName) {
165
+ // read lazily: a source is only touched once every earlier one came back undefined, so a name
166
+ // the process environment answers never opens a secret file, and a malformed secret.json only
167
+ // ever affects the names that actually reach it
142
168
  const sources = [
143
- this.getFromEnvironmentVariable(envVarName),
144
- this.getFromEnvYamlOrJsonFile(envVarName),
145
- this.getFromDockerSecret(envVarName),
146
- this.getFromDockerSecretJson(envVarName)
169
+ () => this.getFromEnvironmentVariable(envVarName),
170
+ () => this.getFromEnvYamlOrJsonFile(envVarName),
171
+ () => this.getFromDockerSecret(envVarName),
172
+ () => this.getFromDockerSecretJson(envVarName),
147
173
  ];
148
- for (const value of sources) {
174
+ for (const readSource of sources) {
175
+ const value = readSource();
149
176
  if (value !== undefined) {
150
177
  return value;
151
178
  }
152
179
  }
153
180
  return undefined;
154
181
  }
182
+ /** renders env var references for an error message, naming a resolver function where it has one */
183
+ describeEnvVarRefs(envVarNameOrNames) {
184
+ const envVarRefs = Array.isArray(envVarNameOrNames) ? envVarNameOrNames : [envVarNameOrNames];
185
+ return envVarRefs
186
+ .map((envVarRef) => typeof envVarRef === 'function'
187
+ ? `${envVarRef.name || 'anonymous'}()`
188
+ : envVarRef)
189
+ .join(', ');
190
+ }
155
191
  getFromEnvironmentVariable(envVarName) {
156
192
  return process.env[envVarName];
157
193
  }
@@ -174,19 +210,27 @@ export class Qenv {
174
210
  return undefined;
175
211
  }
176
212
  }
213
+ /**
214
+ * the directory Docker mounts secrets into. It is a method so a test can point both secret
215
+ * readers at a directory it is allowed to create; a process can never write /run/secrets itself.
216
+ */
217
+ getDockerSecretsDirectoryPath() {
218
+ return '/run/secrets';
219
+ }
177
220
  getFromDockerSecret(envVarName) {
178
- const secretPath = `/run/secrets/${envVarName}`;
221
+ const secretPath = plugins.path.join(this.getDockerSecretsDirectoryPath(), envVarName);
179
222
  if (this.fileExists(secretPath)) {
180
223
  return plugins.fs.readFileSync(secretPath, 'utf8');
181
224
  }
182
225
  return undefined;
183
226
  }
184
227
  getFromDockerSecretJson(envVarName) {
185
- if (this.directoryExists('/run/secrets')) {
186
- const availableSecrets = plugins.fs.readdirSync('/run/secrets');
228
+ const secretsDirectoryPath = this.getDockerSecretsDirectoryPath();
229
+ if (this.directoryExists(secretsDirectoryPath)) {
230
+ const availableSecrets = plugins.fs.readdirSync(secretsDirectoryPath);
187
231
  for (const secret of availableSecrets) {
188
232
  if (secret.includes('secret.json')) {
189
- const secretObject = this.readObjectFromFile(`/run/secrets/${secret}`);
233
+ const secretObject = this.readObjectFromFile(plugins.path.join(secretsDirectoryPath, secret));
190
234
  const value = secretObject[envVarName];
191
235
  if (value === undefined) {
192
236
  continue;
@@ -227,7 +271,9 @@ export class Qenv {
227
271
  const parsedObject = filePath.endsWith('.json')
228
272
  ? JSON.parse(fileString)
229
273
  : plugins.yaml.parse(fileString);
230
- return typeof parsedObject === 'object' && parsedObject !== null ? parsedObject : {};
274
+ return typeof parsedObject === 'object' && parsedObject !== null
275
+ ? parsedObject
276
+ : {};
231
277
  }
232
278
  }
233
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicWVudi5jbGFzc2VzLnFlbnYuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9xZW52LmNsYXNzZXMucWVudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLG1CQUFtQixDQUFDO0FBSzdDLE1BQU0sT0FBTyxJQUFJO0lBVWYsWUFDRSxzQkFBOEIsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUMzQyxrQkFBMkIsRUFDM0IsZ0JBQXlCLElBQUk7UUFaeEIsb0JBQWUsR0FBYSxFQUFFLENBQUM7UUFDL0IscUJBQWdCLEdBQWEsRUFBRSxDQUFDO1FBQ2hDLG1CQUFjLEdBQWEsRUFBRSxDQUFDO1FBQzlCLG1CQUFjLEdBQW9CLEVBQUUsQ0FBQztRQUNyQyxXQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBRTNDLHlCQUFvQixHQUFHLEVBQUUsQ0FBQztRQVEvQixJQUFJLENBQUMsbUJBQW1CLENBQUMsbUJBQW1CLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztRQUM1QixJQUFJLENBQUMsc0JBQXNCLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDN0MsQ0FBQztJQUVPLG1CQUFtQixDQUFDLG1CQUEyQixFQUFFLGtCQUEyQjtRQUNsRixJQUFJLENBQUMsb0JBQW9CLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQzNDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEVBQ3pDLFVBQVUsQ0FDWCxDQUFDO1FBRUYsSUFBSSxrQkFBa0IsRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sZUFBZSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDLENBQUM7WUFFakUsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1lBQ3ZFLE1BQU0sY0FBYyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsRUFBRSxTQUFTLENBQUMsQ0FBQztZQUNyRSxNQUFNLGVBQWUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxlQUFlLEVBQUUsVUFBVSxDQUFDLENBQUM7WUFFdkUsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1lBQzNELE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUN6RCxNQUFNLGlCQUFpQixHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsZUFBZSxDQUFDLENBQUM7WUFFM0QsSUFBSSxpQkFBaUIsSUFBSSxDQUFDLGdCQUFnQixJQUFJLGlCQUFpQixDQUFDLEVBQUUsQ0FBQztnQkFDakUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLHVEQUF1RCxDQUFDLENBQUM7Z0JBQ2pGLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxlQUFlLENBQUM7WUFDN0MsQ0FBQztpQkFBTSxJQUFJLGlCQUFpQixFQUFFLENBQUM7Z0JBQzdCLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxlQUFlLENBQUM7WUFDN0MsQ0FBQztpQkFBTSxJQUFJLGdCQUFnQixFQUFFLENBQUM7Z0JBQzVCLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxjQUFjLENBQUM7WUFDNUMsQ0FBQztpQkFBTSxJQUFJLGlCQUFpQixFQUFFLENBQUM7Z0JBQzdCLElBQUksQ0FBQyxtQkFBbUIsR0FBRyxlQUFlLENBQUM7WUFDN0MsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRU8sbUJBQW1CO1FBQ3pCLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsRUFBRSxDQUFDO1lBQy9DLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsQ0FBQztZQUNwRSxNQUFNLGVBQWUsR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDO1lBQzFDLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUMsRUFBRSxDQUFDO2dCQUNuQyxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FDdkIsR0FBRyxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxFQUFvQixFQUFFLENBQUMsT0FBTyxNQUFNLEtBQUssUUFBUSxDQUFDLENBQ3BGLENBQUM7WUFDSixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLCtDQUErQyxDQUFDLENBQUM7WUFDM0UsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRU8sb0JBQW9CO1FBQzFCLEtBQUssTUFBTSxNQUFNLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQzFDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUM3QyxJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDeEIsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDbkMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUM7WUFDdEMsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRU8sc0JBQXNCLENBQUMsYUFBc0I7UUFDbkQsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUMsZUFBZSxDQUFDLE1BQU0sQ0FDL0MsQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FDcEQsQ0FBQztRQUVGLElBQUksSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDbkMsT0FBTyxDQUFDLElBQUksQ0FBQyx3QkFBd0IsRUFBRSxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7WUFDN0QsT0FBTyxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsRUFBRSxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7WUFDeEQsSUFBSSxhQUFhLEVBQUUsQ0FBQztnQkFDbEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLGtDQUFrQyxDQUFDLENBQUM7Z0JBQzdELE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSwwREFBMEQsQ0FBQyxDQUFDO1lBQ3RGLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVNLEtBQUssQ0FBQyxpQkFBaUIsQ0FDNUIsaUJBQTRDO1FBRTVDLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxFQUFFLENBQUM7WUFDckMsS0FBSyxNQUFNLFVBQVUsSUFBSSxpQkFBaUIsRUFBRSxDQUFDO2dCQUMzQyxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDLENBQUM7Z0JBQ2xELElBQUksS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO29CQUN4QixPQUFPLEtBQUssQ0FBQztnQkFDZixDQUFDO1lBQ0gsQ0FBQztZQUNELE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7YUFBTSxDQUFDO1lBQ04sT0FBTyxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsaUJBQWlCLENBQUMsQ0FBQztRQUNwRCxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsdUJBQXVCLENBQ2xDLGlCQUE0QztRQUU1QyxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQzlELElBQUksS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ3hCLE1BQU0sSUFBSSxLQUFLLENBQUMsV0FBVyxpQkFBaUIsY0FBYyxDQUFDLENBQUM7UUFDOUQsQ0FBQztRQUNELE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVNLHFCQUFxQixDQUFDLGlCQUFvQztRQUMvRCxPQUFPLENBQUMsSUFBSSxDQUFDLDZFQUE2RSxDQUFDLENBQUM7UUFFNUYsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQztZQUNyQyxLQUFLLE1BQU0sVUFBVSxJQUFJLGlCQUFpQixFQUFFLENBQUM7Z0JBQzNDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLENBQUMsQ0FBQztnQkFDaEQsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7b0JBQ3hCLE9BQU8sS0FBSyxDQUFDO2dCQUNmLENBQUM7WUFDSCxDQUFDO1lBQ0QsT0FBTyxTQUFTLENBQUM7UUFDbkIsQ0FBQzthQUFNLENBQUM7WUFDTixPQUFPLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQ2xELENBQUM7SUFDSCxDQUFDO0lBRU0sS0FBSyxDQUFDLHlCQUF5QixDQUFDLGlCQUFvQztRQUN6RSxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQ2pFLElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxVQUFVLENBQUMsZUFBZSxDQUFDLEVBQUUsQ0FBQztZQUNyRCxNQUFNLFVBQVUsR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3RELE9BQU8sSUFBSSxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN2QyxDQUFDO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztJQUVPLEtBQUssQ0FBQyxZQUFZLENBQUMsWUFBd0I7UUFDakQsSUFBSSxPQUFPLFlBQVksS0FBSyxVQUFVLEVBQUUsQ0FBQztZQUN2QyxPQUFPLE1BQU0sWUFBWSxFQUFFLENBQUM7UUFDOUIsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHO1lBQ2QsSUFBSSxDQUFDLDBCQUEwQixDQUFDLFlBQVksQ0FBQztZQUM3QyxJQUFJLENBQUMsd0JBQXdCLENBQUMsWUFBWSxDQUFDO1lBQzNDLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxZQUFZLENBQUM7WUFDdEMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFlBQVksQ0FBQztTQUMzQyxDQUFDO1FBRUYsS0FBSyxNQUFNLEtBQUssSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUM1QixJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDeEIsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFTyxnQkFBZ0IsQ0FBQyxVQUFrQjtRQUN6QyxNQUFNLE9BQU8sR0FBRztZQUNkLElBQUksQ0FBQywwQkFBMEIsQ0FBQyxVQUFVLENBQUM7WUFDM0MsSUFBSSxDQUFDLHdCQUF3QixDQUFDLFVBQVUsQ0FBQztZQUN6QyxJQUFJLENBQUMsbUJBQW1CLENBQUMsVUFBVSxDQUFDO1lBQ3BDLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxVQUFVLENBQUM7U0FDekMsQ0FBQztRQUVGLEtBQUssTUFBTSxLQUFLLElBQUksT0FBTyxFQUFFLENBQUM7WUFDNUIsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ3hCLE9BQU8sS0FBSyxDQUFDO1lBQ2YsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRU8sMEJBQTBCLENBQUMsVUFBa0I7UUFDbkQsT0FBTyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFTyx3QkFBd0IsQ0FBQyxVQUFrQjtRQUNqRCxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsbUJBQW1CLENBQUMsRUFBRSxDQUFDO1lBQy9DLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7UUFDRCxJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLG1CQUFtQixDQUFDLENBQUM7WUFDbEUsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQ2xDLElBQUksS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUN4QixPQUFPLFNBQVMsQ0FBQztZQUNuQixDQUFDO1lBQ0QsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksRUFBRSxDQUFDO2dCQUNoRCxPQUFPLGVBQWUsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3BELENBQUM7WUFDRCxPQUFPLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN2QixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7SUFDSCxDQUFDO0lBRU8sbUJBQW1CLENBQUMsVUFBa0I7UUFDNUMsTUFBTSxVQUFVLEdBQUcsZ0JBQWdCLFVBQVUsRUFBRSxDQUFDO1FBQ2hELElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQ2hDLE9BQU8sT0FBTyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsVUFBVSxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3JELENBQUM7UUFDRCxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRU8sdUJBQXVCLENBQUMsVUFBa0I7UUFDaEQsSUFBSSxJQUFJLENBQUMsZUFBZSxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7WUFDekMsTUFBTSxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUNoRSxLQUFLLE1BQU0sTUFBTSxJQUFJLGdCQUFnQixFQUFFLENBQUM7Z0JBQ3RDLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsRUFBRSxDQUFDO29CQUNuQyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsZ0JBQWdCLE1BQU0sRUFBRSxDQUFDLENBQUM7b0JBQ3ZFLE1BQU0sS0FBSyxHQUFHLFlBQVksQ0FBQyxVQUFVLENBQUMsQ0FBQztvQkFDdkMsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7d0JBQ3hCLFNBQVM7b0JBQ1gsQ0FBQztvQkFDRCxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssSUFBSSxFQUFFLENBQUM7d0JBQ2hELE9BQU8sZUFBZSxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7b0JBQ3BELENBQUM7b0JBQ0QsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQ3ZCLENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUNELE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFTyxZQUFZLENBQUMsSUFBUztRQUM1QixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3hDLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDcEQsQ0FBQztJQUVPLFlBQVksQ0FBQyxhQUFxQjtRQUN4QyxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLGFBQWEsRUFBRSxRQUFRLENBQUMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDN0UsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFTyxVQUFVLENBQUMsUUFBNEI7UUFDN0MsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ2QsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBQ0QsT0FBTyxPQUFPLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBRU8sZUFBZSxDQUFDLGFBQXFCO1FBQzNDLElBQUksQ0FBQztZQUNILE9BQU8sT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDMUQsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztJQUNILENBQUM7SUFFTyxrQkFBa0IsQ0FBQyxRQUFnQjtRQUN6QyxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDN0QsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7WUFDN0MsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDO1lBQ3hCLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNuQyxPQUFPLE9BQU8sWUFBWSxLQUFLLFFBQVEsSUFBSSxZQUFZLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUN2RixDQUFDO0NBQ0YifQ==
279
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicWVudi5jbGFzc2VzLnFlbnYuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9xZW52LmNsYXNzZXMucWVudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLG1CQUFtQixDQUFDO0FBQzdDLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLCtDQUErQyxDQUFDO0FBY2hHLE1BQU0sT0FBTyxJQUFJO0lBa0JmOzs7Ozs7OztPQVFHO0lBQ0gsWUFDRSxzQkFBOEIsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUMzQyxrQkFBMkIsRUFDM0IsZ0JBQXlCLElBQUk7UUE3Qi9CLHFEQUFxRDtRQUM5QyxvQkFBZSxHQUFhLEVBQUUsQ0FBQztRQUV0QyxnREFBZ0Q7UUFDekMscUJBQWdCLEdBQWEsRUFBRSxDQUFDO1FBRXZDLGlEQUFpRDtRQUMxQyxtQkFBYyxHQUFhLEVBQUUsQ0FBQztRQUVyQywwREFBMEQ7UUFDbkQsbUJBQWMsR0FBb0IsRUFBRSxDQUFDO1FBRXJDLFdBQU0sR0FBRyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsVUFBVSxFQUFFLENBQUM7UUFFM0MseUJBQW9CLEdBQUcsRUFBRSxDQUFDO1FBaUIvQixJQUFJLENBQUMsbUJBQW1CLENBQUMsbUJBQW1CLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztRQUM1QixJQUFJLENBQUMsc0JBQXNCLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDN0MsQ0FBQztJQUVPLG1CQUFtQixDQUFDLG1CQUEyQixFQUFFLGtCQUEyQjtRQUNsRixJQUFJLENBQUMsb0JBQW9CLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQzNDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEVBQ3pDLFVBQVUsQ0FDWCxDQUFDO1FBRUYs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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/qenv",
3
- "version": "7.0.0",
3
+ "version": "8.0.0",
4
4
  "private": false,
5
5
  "description": "A module for easily handling environment variables in Node.js projects with support for .yml and .json configuration.",
6
6
  "main": "dist_ts/index.js",
@@ -22,14 +22,14 @@
22
22
  "author": "Task Venture Capital GmbH <hello@task.vc>",
23
23
  "license": "MIT",
24
24
  "bugs": {
25
- "url": "https://gitlab.com/pushrocks/qenv/issues"
25
+ "url": "https://code.foss.global/push.rocks/qenv/issues"
26
26
  },
27
27
  "homepage": "https://code.foss.global/push.rocks/qenv",
28
28
  "devDependencies": {
29
- "@git.zone/tsbuild": "^4.4.3",
29
+ "@git.zone/tsbuild": "^4.5.0",
30
30
  "@git.zone/tsrun": "^2.0.6",
31
- "@git.zone/tstest": "^6.1.0",
32
- "@types/node": "^26.5.0"
31
+ "@git.zone/tstest": "^6.1.1",
32
+ "@types/node": "^26.5.1"
33
33
  },
34
34
  "dependencies": {
35
35
  "@push.rocks/smartlog": "^3.2.2",
package/readme.md CHANGED
@@ -10,7 +10,8 @@
10
10
  ✅ **Flexible Formats** - Supports `.yml`, `.yaml`, and `.json` configuration files
11
11
  ✅ **Docker Ready** - Built-in support for Docker secrets and secret.json files
12
12
  ✅ **Async & Sync** - Both synchronous and asynchronous variable retrieval
13
- ✅ **Strict Mode** - Optional strict mode that throws errors for missing variables
13
+ ✅ **Enforced Requirements** - `required:` in `qenv.yml` is checked while constructing, with a typed error
14
+ ✅ **Strict Mode** - Optional strict getter that throws for a missing variable
14
15
  ✅ **Base64 Objects** - Handle complex configuration objects with automatic encoding/decoding
15
16
  ✅ **Dynamic Resolution** - Support for async functions as environment variable sources
16
17
 
@@ -32,7 +33,8 @@ yarn add @push.rocks/qenv
32
33
  ```typescript
33
34
  import { Qenv } from '@push.rocks/qenv';
34
35
 
35
- // Create a new Qenv instance
36
+ // Create a new Qenv instance. Every name listed under `required:` in qenv.yml is resolved right
37
+ // here, and a missing one throws a QenvMissingRequiredEnvVarsError.
36
38
  const qenv = new Qenv('./', './', true);
37
39
 
38
40
  // Access environment variables
@@ -61,6 +63,10 @@ required:
61
63
  - LOG_LEVEL
62
64
  ```
63
65
 
66
+ Every listed name is resolved while the `Qenv` instance is constructed. What is found lands in
67
+ `availableEnvVars` and, as a string, in `keyValueObject`; what is missing lands in `missingEnvVars`
68
+ and, unless you pass `failOnMissing: false`, makes the constructor throw.
69
+
64
70
  #### 2. Provide Values (`env.yml` or `env.json`)
65
71
 
66
72
  For local development, create an `env.yml` or `env.json` file:
@@ -97,6 +103,10 @@ Qenv loads variables in this order (first found wins):
97
103
  3. **Docker secrets** - From `/run/secrets/`
98
104
  4. **Docker secret JSON** - From `/run/secrets/secret.json`
99
105
 
106
+ All four sources are synchronous, so `getEnvVarOnDemand` and `getEnvVarOnDemandSync` resolve a name
107
+ identically. A `required:` entry is always a name; an async resolver function is a per-call source
108
+ and never takes part in the requirement check.
109
+
100
110
  ### Handling Complex Objects
101
111
 
102
112
  Store and retrieve complex configuration objects:
@@ -134,6 +144,9 @@ const fetchFromVault = async () => {
134
144
  const secret = await qenv.getEnvVarOnDemand(fetchFromVault);
135
145
  ```
136
146
 
147
+ The function form of `TEnvVarRef` is honoured by `getEnvVarOnDemand` and `getEnvVarOnDemandStrict`
148
+ only. `getEnvVarOnDemandSync` cannot await, so it accepts names and takes no resolver function.
149
+
137
150
  ### Working with Docker
138
151
 
139
152
  Qenv seamlessly integrates with Docker secrets:
@@ -168,9 +181,18 @@ const dbPassword = await qenv.getEnvVarOnDemand('db_password');
168
181
  Control how your application handles missing environment variables:
169
182
 
170
183
  ```typescript
171
- // Fail fast (default behavior)
172
- const qenvStrict = new Qenv('./', './', true);
173
- // Application exits if required variables are missing
184
+ import { Qenv, QenvMissingRequiredEnvVarsError } from '@push.rocks/qenv';
185
+
186
+ // Fail fast (default behaviour): the constructor throws
187
+ try {
188
+ const qenvStrict = new Qenv('./', './', true);
189
+ } catch (error) {
190
+ if (error instanceof QenvMissingRequiredEnvVarsError) {
191
+ console.error('Missing variables:', error.missingEnvVars);
192
+ console.error('Declared in:', error.qenvFilePathAbsolute);
193
+ }
194
+ throw error;
195
+ }
174
196
 
175
197
  // Graceful handling
176
198
  const qenvRelaxed = new Qenv('./', './', false);
@@ -183,6 +205,16 @@ if (qenvRelaxed.missingEnvVars.length > 0) {
183
205
  }
184
206
  ```
185
207
 
208
+ qenv never ends the process: the caller decides what an incomplete environment means. Where two
209
+ copies of qenv can end up in one dependency tree, `instanceof` is unreliable - match on the code
210
+ instead:
211
+
212
+ ```typescript
213
+ if (error instanceof Error && 'code' in error && error.code === 'QENV_MISSING_REQUIRED_ENV_VARS') {
214
+ // handle the incomplete environment
215
+ }
216
+ ```
217
+
186
218
  ### Strict Mode for Critical Variables
187
219
 
188
220
  Use the new strict getter when you absolutely need a variable:
@@ -191,15 +223,19 @@ Use the new strict getter when you absolutely need a variable:
191
223
  try {
192
224
  // This will throw if TOKEN is not set
193
225
  const token = await qenv.getEnvVarOnDemandStrict('TOKEN');
194
-
226
+
195
227
  // You can also check multiple fallback names
196
228
  const db = await qenv.getEnvVarOnDemandStrict(['DATABASE_URL', 'DB_CONNECTION']);
197
229
  } catch (error) {
230
+ // the message names every reference that could not be resolved
198
231
  console.error('Critical configuration missing:', error.message);
199
- process.exit(1);
232
+ throw error;
200
233
  }
201
234
  ```
202
235
 
236
+ A name that `qenv.yml` already lists under `required:` is resolved at construction time, so the
237
+ strict getter is for variables you look up on demand.
238
+
203
239
  ## 🏗️ CI/CD Integration
204
240
 
205
241
  ### GitHub Actions
@@ -276,12 +312,12 @@ qenv.logger.log('info', 'Custom log message');
276
312
  Here's how you might use qenv in a production Node.js application:
277
313
 
278
314
  ```typescript
279
- import { Qenv } from '@push.rocks/qenv';
315
+ import { Qenv, QenvMissingRequiredEnvVarsError } from '@push.rocks/qenv';
280
316
  import { createServer } from './server';
281
317
  import { connectDatabase } from './database';
282
318
 
283
319
  async function bootstrap() {
284
- // Initialize environment
320
+ // Initialize environment: throws when qenv.yml requires something no source provides
285
321
  const qenv = new Qenv();
286
322
 
287
323
  // Load critical configuration
@@ -304,7 +340,12 @@ async function bootstrap() {
304
340
  }
305
341
 
306
342
  bootstrap().catch(error => {
307
- console.error('Failed to start application:', error);
343
+ if (error instanceof QenvMissingRequiredEnvVarsError) {
344
+ console.error('Incomplete environment, missing:', error.missingEnvVars.join(', '));
345
+ } else {
346
+ console.error('Failed to start application:', error);
347
+ }
348
+ // exiting is the application's decision - qenv itself never calls process.exit
308
349
  process.exit(1);
309
350
  });
310
351
  ```
@@ -318,18 +359,21 @@ bootstrap().catch(error => {
318
359
  new Qenv(
319
360
  qenvFileBasePathArg?: string, // Path to qenv.yml (default: process.cwd())
320
361
  envFileBasePathArg?: string, // Path to env.yml/json (default: same as qenv)
321
- failOnMissing?: boolean // Exit on missing vars (default: true)
362
+ failOnMissing?: boolean // Throw on missing required vars (default: true)
322
363
  )
323
364
  ```
324
365
 
366
+ Throws `QenvMissingRequiredEnvVarsError` when `failOnMissing` is true and a name listed under
367
+ `required:` is not provided by any source.
368
+
325
369
  #### Methods
326
370
 
327
371
  | Method | Description | Returns |
328
372
  |--------|-------------|---------|
329
- | `getEnvVarOnDemand(name)` | Get environment variable value | `Promise<string \| undefined>` |
373
+ | `getEnvVarOnDemand(name)` | Get environment variable value, resolver functions included | `Promise<string \| undefined>` |
330
374
  | `getEnvVarOnDemandStrict(name)` | Get variable or throw error | `Promise<string>` |
331
- | `getEnvVarOnDemandSync(name)` | Synchronously get variable | `string \| undefined` |
332
- | `getEnvVarOnDemandAsObject(name)` | Get variable as decoded object | `Promise<any>` |
375
+ | `getEnvVarOnDemandSync(name)` | Synchronously get variable, names only | `string \| undefined` |
376
+ | `getEnvVarOnDemandAsObject(name)` | Get variable as decoded object | `Promise<unknown>` |
333
377
 
334
378
  #### Properties
335
379
 
@@ -338,7 +382,24 @@ new Qenv(
338
382
  | `requiredEnvVars` | `string[]` | List of required variable names |
339
383
  | `availableEnvVars` | `string[]` | List of found variable names |
340
384
  | `missingEnvVars` | `string[]` | List of missing variable names |
341
- | `keyValueObject` | `object` | All loaded variables as key-value pairs |
385
+ | `keyValueObject` | `Record<string, string>` | Every available required variable as a resolved string |
386
+ | `qenvFilePathAbsolute` | `string` | Absolute path of the qenv.yml in use |
387
+ | `envFilePathAbsolute` | `string \| undefined` | Absolute path of the env file in use |
388
+
389
+ ### Class: `QenvMissingRequiredEnvVarsError`
390
+
391
+ Thrown by the constructor when `failOnMissing` is true and a required variable has no source.
392
+
393
+ | Member | Type | Description |
394
+ |--------|------|-------------|
395
+ | `code` | `'QENV_MISSING_REQUIRED_ENV_VARS'` | Stable identifier, safe across duplicate installs |
396
+ | `missingEnvVars` | `string[]` | The required names no source provided |
397
+ | `requiredEnvVars` | `string[]` | Every name listed under `required:` |
398
+ | `qenvFilePathAbsolute` | `string` | The qenv.yml that declared them |
399
+
400
+ ## Issue Reporting and Security
401
+
402
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
342
403
 
343
404
  ## License and Legal Information
344
405
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/qenv',
6
- version: '7.0.0',
6
+ version: '8.0.0',
7
7
  description: 'A module for easily handling environment variables in Node.js projects with support for .yml and .json configuration.'
8
8
  }
package/ts/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './qenv.classes.qenv.js';
2
+ export * from './qenv.classes.missingrequiredenvvarserror.js';
@@ -0,0 +1,30 @@
1
+ export interface IQenvMissingRequiredEnvVarsErrorOptions {
2
+ missingEnvVars: string[];
3
+ requiredEnvVars: string[];
4
+ qenvFilePathAbsolute: string;
5
+ }
6
+
7
+ /**
8
+ * Thrown when a variable listed under `required:` in qenv.yml is not provided by any source.
9
+ * The names are carried as data so a caller can report or branch on them without parsing the
10
+ * message, and `code` identifies the refusal even when two copies of qenv end up in one tree,
11
+ * which makes `instanceof` unreliable.
12
+ */
13
+ export class QenvMissingRequiredEnvVarsError extends Error {
14
+ public readonly code = 'QENV_MISSING_REQUIRED_ENV_VARS';
15
+ public readonly missingEnvVars: string[];
16
+ public readonly requiredEnvVars: string[];
17
+ public readonly qenvFilePathAbsolute: string;
18
+
19
+ constructor(optionsArg: IQenvMissingRequiredEnvVarsErrorOptions) {
20
+ super(
21
+ `qenv is missing required environment variables: ${optionsArg.missingEnvVars.join(', ')}. ` +
22
+ `They are listed under "required:" in ${optionsArg.qenvFilePathAbsolute} and were found ` +
23
+ `neither in the process environment, nor in the env file, nor in the Docker secrets.`
24
+ );
25
+ this.name = 'QenvMissingRequiredEnvVarsError';
26
+ this.missingEnvVars = [...optionsArg.missingEnvVars];
27
+ this.requiredEnvVars = [...optionsArg.requiredEnvVars];
28
+ this.qenvFilePathAbsolute = optionsArg.qenvFilePathAbsolute;
29
+ }
30
+ }
@@ -1,18 +1,45 @@
1
1
  import * as plugins from './qenv.plugins.js';
2
+ import { QenvMissingRequiredEnvVarsError } from './qenv.classes.missingrequiredenvvarserror.js';
2
3
 
4
+ /**
5
+ * a reference to an environment variable: its name, or an async function that produces the value.
6
+ * The function form is only honoured by the async getters; the synchronous getter accepts names.
7
+ */
3
8
  export type TEnvVarRef = string | (() => Promise<string>);
4
- type TKeyValueObject = Record<string, any>;
9
+
10
+ /** the resolved value of every available required env var, always a string */
11
+ export type TEnvVarValueMap = Record<string, string>;
12
+
13
+ /** a parsed qenv.yml, env file or secret.json, whose values are whatever the file declared */
14
+ type TParsedFileObject = Record<string, unknown>;
5
15
 
6
16
  export class Qenv {
17
+ /** the names listed under `required:` in qenv.yml */
7
18
  public requiredEnvVars: string[] = [];
19
+
20
+ /** the required names that a source provided */
8
21
  public availableEnvVars: string[] = [];
22
+
23
+ /** the required names that no source provided */
9
24
  public missingEnvVars: string[] = [];
10
- public keyValueObject: TKeyValueObject = {};
25
+
26
+ /** the resolved value of every available required name */
27
+ public keyValueObject: TEnvVarValueMap = {};
28
+
11
29
  public logger = new plugins.smartlog.ConsoleLog();
12
30
 
13
31
  public qenvFilePathAbsolute = '';
14
32
  public envFilePathAbsolute?: string;
15
33
 
34
+ /**
35
+ * Resolves every name listed under `required:` in qenv.yml while constructing.
36
+ * @param qenvFileBasePathArg directory that holds qenv.yml
37
+ * @param envFileBasePathArg directory that holds env.json, env.yml or env.yaml
38
+ * @param failOnMissing throws a QenvMissingRequiredEnvVarsError when a variable listed under
39
+ * `required:` in qenv.yml is not provided by any source. Pass false to only record the names in
40
+ * `missingEnvVars` and continue.
41
+ * @throws QenvMissingRequiredEnvVarsError
42
+ */
16
43
  constructor(
17
44
  qenvFileBasePathArg: string = process.cwd(),
18
45
  envFileBasePathArg?: string,
@@ -29,18 +56,18 @@ export class Qenv {
29
56
  plugins.path.resolve(qenvFileBasePathArg),
30
57
  'qenv.yml'
31
58
  );
32
-
59
+
33
60
  if (envFileBasePathArg) {
34
61
  const envFileBasePath = plugins.path.resolve(envFileBasePathArg);
35
-
62
+
36
63
  const envFileJsonPath = plugins.path.join(envFileBasePath, 'env.json');
37
64
  const envFileYmlPath = plugins.path.join(envFileBasePath, 'env.yml');
38
65
  const envFileYamlPath = plugins.path.join(envFileBasePath, 'env.yaml');
39
-
66
+
40
67
  const envFileJsonExists = this.fileExists(envFileJsonPath);
41
68
  const envFileYmlExists = this.fileExists(envFileYmlPath);
42
69
  const envFileYamlExists = this.fileExists(envFileYamlPath);
43
-
70
+
44
71
  if (envFileJsonExists && (envFileYmlExists || envFileYamlExists)) {
45
72
  this.logger.log('warn', 'Both env.json and env.yml files exist! Using env.json');
46
73
  this.envFilePathAbsolute = envFileJsonPath;
@@ -55,22 +82,28 @@ export class Qenv {
55
82
  }
56
83
 
57
84
  private loadRequiredEnvVars() {
58
- if (this.fileExists(this.qenvFilePathAbsolute)) {
59
- const qenvFile = this.readObjectFromFile(this.qenvFilePathAbsolute);
60
- const requiredEnvVars = qenvFile.required;
61
- if (Array.isArray(requiredEnvVars)) {
62
- this.requiredEnvVars.push(
63
- ...requiredEnvVars.filter((envVar): envVar is string => typeof envVar === 'string')
64
- );
65
- } else {
66
- this.logger.log('warn', 'qenv.yml does not contain a "required" Array!');
67
- }
85
+ if (!this.fileExists(this.qenvFilePathAbsolute)) {
86
+ return;
87
+ }
88
+ const qenvFile = this.readObjectFromFile(this.qenvFilePathAbsolute);
89
+ const declaredRequirements = qenvFile['required'];
90
+ if (!Array.isArray(declaredRequirements)) {
91
+ this.logger.log('warn', 'qenv.yml does not contain a "required" Array!');
92
+ return;
68
93
  }
94
+ const declaredEntries: unknown[] = declaredRequirements;
95
+ this.requiredEnvVars.push(
96
+ ...declaredEntries.filter((entry): entry is string => typeof entry === 'string')
97
+ );
69
98
  }
70
99
 
71
100
  private loadAvailableEnvVars() {
101
+ // resolved synchronously: a required env var is always a name, and every source a name can come
102
+ // from - process environment, env file, Docker secrets - is a synchronous read. Resolving it
103
+ // here as a promise would store a pending promise instead of a value and make the check below
104
+ // pass for every name.
72
105
  for (const envVar of this.requiredEnvVars) {
73
- const value = this.getEnvVarOnDemand(envVar);
106
+ const value = this.tryGetEnvVarSync(envVar);
74
107
  if (value !== undefined) {
75
108
  this.availableEnvVars.push(envVar);
76
109
  this.keyValueObject[envVar] = value;
@@ -83,18 +116,30 @@ export class Qenv {
83
116
  (envVar) => !this.availableEnvVars.includes(envVar)
84
117
  );
85
118
 
86
- if (this.missingEnvVars.length > 0) {
87
- console.info('Required Env Vars are:', this.requiredEnvVars);
88
- console.error('Missing Env Vars:', this.missingEnvVars);
89
- if (failOnMissing) {
90
- this.logger.log('error', 'Exiting due to missing env vars!');
91
- process.exit(1);
92
- } else {
93
- this.logger.log('warn', 'qenv is not set to fail on missing environment variables');
94
- }
119
+ if (this.missingEnvVars.length === 0) {
120
+ return;
95
121
  }
122
+
123
+ if (failOnMissing) {
124
+ // a library must not end the process: the caller decides what an incomplete environment means
125
+ throw new QenvMissingRequiredEnvVarsError({
126
+ missingEnvVars: this.missingEnvVars,
127
+ requiredEnvVars: this.requiredEnvVars,
128
+ qenvFilePathAbsolute: this.qenvFilePathAbsolute,
129
+ });
130
+ }
131
+
132
+ this.logger.log(
133
+ 'warn',
134
+ `qenv is not set to fail on missing environment variables. Missing: ${this.missingEnvVars.join(', ')}`
135
+ );
96
136
  }
97
137
 
138
+ /**
139
+ * Resolves an env var from the process environment, the env file and the Docker secrets, in that
140
+ * order. An array is tried left to right and the first defined value wins.
141
+ * @param envVarNameOrNames a name, an async resolver function, or a list of either
142
+ */
98
143
  public async getEnvVarOnDemand(
99
144
  envVarNameOrNames: TEnvVarRef | TEnvVarRef[]
100
145
  ): Promise<string | undefined> {
@@ -112,23 +157,25 @@ export class Qenv {
112
157
  }
113
158
 
114
159
  /**
115
- * Like getEnvVarOnDemand, but throws an error if the env var is not set.
116
- * @param envVarNameOrNames
117
- * @returns
160
+ * Like getEnvVarOnDemand, but throws when no source provides a value.
161
+ * @param envVarNameOrNames a name, an async resolver function, or a list of either
118
162
  */
119
163
  public async getEnvVarOnDemandStrict(
120
164
  envVarNameOrNames: TEnvVarRef | TEnvVarRef[]
121
165
  ): Promise<string> {
122
166
  const value = await this.getEnvVarOnDemand(envVarNameOrNames);
123
167
  if (value === undefined) {
124
- throw new Error(`Env var ${envVarNameOrNames} is not set!`);
168
+ throw new Error(`Env var ${this.describeEnvVarRefs(envVarNameOrNames)} is not set!`);
125
169
  }
126
170
  return value;
127
171
  }
128
172
 
173
+ /**
174
+ * The synchronous counterpart of getEnvVarOnDemand. It resolves names only: an async resolver
175
+ * function cannot be awaited here, so the function form of TEnvVarRef is not accepted.
176
+ * @param envVarNameOrNames a name or a list of names
177
+ */
129
178
  public getEnvVarOnDemandSync(envVarNameOrNames: string | string[]): string | undefined {
130
- console.warn('requesting env var sync leaves out potentially important async env sources.');
131
-
132
179
  if (Array.isArray(envVarNameOrNames)) {
133
180
  for (const envVarName of envVarNameOrNames) {
134
181
  const value = this.tryGetEnvVarSync(envVarName);
@@ -142,7 +189,14 @@ export class Qenv {
142
189
  }
143
190
  }
144
191
 
145
- public async getEnvVarOnDemandAsObject(envVarNameOrNames: string | string[]): Promise<any> {
192
+ /**
193
+ * Resolves an env var whose value was stored as a base64 encoded object and decodes it. A plain
194
+ * value is returned as the string it is, so the caller narrows what it gets.
195
+ * @param envVarNameOrNames a name or a list of names
196
+ */
197
+ public async getEnvVarOnDemandAsObject(
198
+ envVarNameOrNames: string | string[]
199
+ ): Promise<unknown> {
146
200
  const rawValue = await this.getEnvVarOnDemand(envVarNameOrNames);
147
201
  if (rawValue && rawValue.startsWith('base64Object:')) {
148
202
  const base64Part = rawValue.split('base64Object:')[1];
@@ -156,39 +210,43 @@ export class Qenv {
156
210
  return await envVarRefArg();
157
211
  }
158
212
 
159
- const sources = [
160
- this.getFromEnvironmentVariable(envVarRefArg),
161
- this.getFromEnvYamlOrJsonFile(envVarRefArg),
162
- this.getFromDockerSecret(envVarRefArg),
163
- this.getFromDockerSecretJson(envVarRefArg)
164
- ];
165
-
166
- for (const value of sources) {
167
- if (value !== undefined) {
168
- return value;
169
- }
170
- }
171
-
172
- return undefined;
213
+ // a name resolves from synchronous sources only, so both getters share one resolution order
214
+ return this.tryGetEnvVarSync(envVarRefArg);
173
215
  }
174
216
 
175
217
  private tryGetEnvVarSync(envVarName: string): string | undefined {
176
- const sources = [
177
- this.getFromEnvironmentVariable(envVarName),
178
- this.getFromEnvYamlOrJsonFile(envVarName),
179
- this.getFromDockerSecret(envVarName),
180
- this.getFromDockerSecretJson(envVarName)
218
+ // read lazily: a source is only touched once every earlier one came back undefined, so a name
219
+ // the process environment answers never opens a secret file, and a malformed secret.json only
220
+ // ever affects the names that actually reach it
221
+ const sources: Array<() => string | undefined> = [
222
+ () => this.getFromEnvironmentVariable(envVarName),
223
+ () => this.getFromEnvYamlOrJsonFile(envVarName),
224
+ () => this.getFromDockerSecret(envVarName),
225
+ () => this.getFromDockerSecretJson(envVarName),
181
226
  ];
182
-
183
- for (const value of sources) {
227
+
228
+ for (const readSource of sources) {
229
+ const value = readSource();
184
230
  if (value !== undefined) {
185
231
  return value;
186
232
  }
187
233
  }
188
-
234
+
189
235
  return undefined;
190
236
  }
191
237
 
238
+ /** renders env var references for an error message, naming a resolver function where it has one */
239
+ private describeEnvVarRefs(envVarNameOrNames: TEnvVarRef | TEnvVarRef[]): string {
240
+ const envVarRefs = Array.isArray(envVarNameOrNames) ? envVarNameOrNames : [envVarNameOrNames];
241
+ return envVarRefs
242
+ .map((envVarRef) =>
243
+ typeof envVarRef === 'function'
244
+ ? `${envVarRef.name || 'anonymous'}()`
245
+ : envVarRef
246
+ )
247
+ .join(', ');
248
+ }
249
+
192
250
  private getFromEnvironmentVariable(envVarName: string): string | undefined {
193
251
  return process.env[envVarName];
194
252
  }
@@ -212,8 +270,16 @@ export class Qenv {
212
270
  }
213
271
  }
214
272
 
273
+ /**
274
+ * the directory Docker mounts secrets into. It is a method so a test can point both secret
275
+ * readers at a directory it is allowed to create; a process can never write /run/secrets itself.
276
+ */
277
+ protected getDockerSecretsDirectoryPath(): string {
278
+ return '/run/secrets';
279
+ }
280
+
215
281
  private getFromDockerSecret(envVarName: string): string | undefined {
216
- const secretPath = `/run/secrets/${envVarName}`;
282
+ const secretPath = plugins.path.join(this.getDockerSecretsDirectoryPath(), envVarName);
217
283
  if (this.fileExists(secretPath)) {
218
284
  return plugins.fs.readFileSync(secretPath, 'utf8');
219
285
  }
@@ -221,11 +287,14 @@ export class Qenv {
221
287
  }
222
288
 
223
289
  private getFromDockerSecretJson(envVarName: string): string | undefined {
224
- if (this.directoryExists('/run/secrets')) {
225
- const availableSecrets = plugins.fs.readdirSync('/run/secrets');
290
+ const secretsDirectoryPath = this.getDockerSecretsDirectoryPath();
291
+ if (this.directoryExists(secretsDirectoryPath)) {
292
+ const availableSecrets = plugins.fs.readdirSync(secretsDirectoryPath);
226
293
  for (const secret of availableSecrets) {
227
294
  if (secret.includes('secret.json')) {
228
- const secretObject = this.readObjectFromFile(`/run/secrets/${secret}`);
295
+ const secretObject = this.readObjectFromFile(
296
+ plugins.path.join(secretsDirectoryPath, secret)
297
+ );
229
298
  const value = secretObject[envVarName];
230
299
  if (value === undefined) {
231
300
  continue;
@@ -240,12 +309,12 @@ export class Qenv {
240
309
  return undefined;
241
310
  }
242
311
 
243
- private encodeBase64(data: any): string {
312
+ private encodeBase64(data: unknown): string {
244
313
  const jsonString = JSON.stringify(data);
245
314
  return Buffer.from(jsonString).toString('base64');
246
315
  }
247
316
 
248
- private decodeBase64(encodedString: string): any {
317
+ private decodeBase64(encodedString: string): unknown {
249
318
  const decodedString = Buffer.from(encodedString, 'base64').toString('utf-8');
250
319
  return JSON.parse(decodedString);
251
320
  }
@@ -265,11 +334,13 @@ export class Qenv {
265
334
  }
266
335
  }
267
336
 
268
- private readObjectFromFile(filePath: string): TKeyValueObject {
337
+ private readObjectFromFile(filePath: string): TParsedFileObject {
269
338
  const fileString = plugins.fs.readFileSync(filePath, 'utf8');
270
- const parsedObject = filePath.endsWith('.json')
339
+ const parsedObject: unknown = filePath.endsWith('.json')
271
340
  ? JSON.parse(fileString)
272
341
  : plugins.yaml.parse(fileString);
273
- return typeof parsedObject === 'object' && parsedObject !== null ? parsedObject : {};
342
+ return typeof parsedObject === 'object' && parsedObject !== null
343
+ ? (parsedObject as TParsedFileObject)
344
+ : {};
274
345
  }
275
346
  }