@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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/qenv.classes.missingrequiredenvvarserror.d.ts +18 -0
- package/dist_ts/qenv.classes.missingrequiredenvvarserror.js +19 -0
- package/dist_ts/qenv.classes.qenv.d.ts +45 -7
- package/dist_ts/qenv.classes.qenv.js +94 -48
- package/package.json +5 -5
- package/readme.md +76 -15
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -0
- package/ts/qenv.classes.missingrequiredenvvarserror.ts +30 -0
- package/ts/qenv.classes.qenv.ts +135 -64
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/qenv',
|
|
6
|
-
version: '
|
|
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=
|
package/dist_ts/index.d.ts
CHANGED
package/dist_ts/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export * from './qenv.classes.qenv.js';
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
64
|
-
|
|
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
|
|
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
|
-
|
|
129
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
186
|
-
|
|
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(
|
|
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
|
|
274
|
+
return typeof parsedObject === 'object' && parsedObject !== null
|
|
275
|
+
? parsedObject
|
|
276
|
+
: {};
|
|
231
277
|
}
|
|
232
278
|
}
|
|
233
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicWVudi5jbGFzc2VzLnFlbnYuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9xZW52LmNsYXNzZXMucWVudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLG1CQUFtQixDQUFDO0FBSzdDLE1BQU0sT0FBTyxJQUFJO0lBVWYsWUFDRSxzQkFBOEIsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUMzQyxrQkFBMkIsRUFDM0IsZ0JBQXlCLElBQUk7UUFaeEIsb0JBQWUsR0FBYSxFQUFFLENBQUM7UUFDL0IscUJBQWdCLEdBQWEsRUFBRSxDQUFDO1FBQ2hDLG1CQUFjLEdBQWEsRUFBRSxDQUFDO1FBQzlCLG1CQUFjLEdBQW9CLEVBQUUsQ0FBQztRQUNyQyxXQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBRTNDLHlCQUFvQixHQUFHLEVBQUUsQ0FBQztRQVEvQixJQUFJLENBQUMsbUJBQW1CLENBQUMsbUJBQW1CLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztRQUM1QixJQUFJLENBQUMsc0JBQXNCLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDN0MsQ0FBQztJQUVPLG1CQUFtQixDQUFDLG1CQUEyQixFQUFFLGtCQUEyQjtRQUNsRixJQUFJLENBQUMsb0JBQW9CLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQzNDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLEVBQ3pDLFVBQVUsQ0FDWCxDQUFDO1FBRUYsSUFBSSxrQkFBa0IsRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sZUFBZSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDLENBQUM7WUFFakUsTUFBTSxlQUFlLEdBQUcsT0FBTyxD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
|
|
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": "
|
|
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://
|
|
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.
|
|
29
|
+
"@git.zone/tsbuild": "^4.5.0",
|
|
30
30
|
"@git.zone/tsrun": "^2.0.6",
|
|
31
|
-
"@git.zone/tstest": "^6.1.
|
|
32
|
-
"@types/node": "^26.5.
|
|
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
|
-
✅ **
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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 //
|
|
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<
|
|
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` | `
|
|
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
|
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/ts/qenv.classes.qenv.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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.
|
|
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
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
225
|
-
|
|
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(
|
|
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:
|
|
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):
|
|
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):
|
|
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
|
|
342
|
+
return typeof parsedObject === 'object' && parsedObject !== null
|
|
343
|
+
? (parsedObject as TParsedFileObject)
|
|
344
|
+
: {};
|
|
274
345
|
}
|
|
275
346
|
}
|