@achs/env 2.0.0 → 3.1.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.
Files changed (136) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.json +329 -0
  3. package/.vscode/extensions.json +18 -0
  4. package/.vscode/launch.json +30 -0
  5. package/.vscode/settings.json +29 -0
  6. package/CHANGELOG.md +13 -0
  7. package/README.md +14 -7
  8. package/jest.config.json +28 -0
  9. package/package.json +16 -16
  10. package/{arguments.js → src/arguments.ts} +38 -14
  11. package/src/commands/env.command.ts +139 -0
  12. package/src/commands/export.command.ts +88 -0
  13. package/{commands/index.d.ts → src/commands/index.ts} +0 -1
  14. package/src/commands/pull.command.ts +52 -0
  15. package/src/commands/push.command.ts +48 -0
  16. package/src/commands/schema.command.ts +31 -0
  17. package/src/exec.ts +221 -0
  18. package/{index.d.ts → src/index.ts} +0 -1
  19. package/{interfaces/index.d.ts → src/interfaces/index.ts} +0 -1
  20. package/src/interfaces/loader.interface.ts +66 -0
  21. package/src/main.ts +6 -0
  22. package/src/providers/app-settings.provider.ts +67 -0
  23. package/src/providers/azure-key-vault.provider.ts +277 -0
  24. package/src/providers/index.ts +29 -0
  25. package/src/providers/local.provider.ts +44 -0
  26. package/src/providers/package-json.provider.ts +39 -0
  27. package/src/utils/command.util.ts +223 -0
  28. package/{utils/index.d.ts → src/utils/index.ts} +0 -1
  29. package/src/utils/interpolate.util.ts +65 -0
  30. package/src/utils/json.util.ts +116 -0
  31. package/{utils/logger.js → src/utils/logger.ts} +6 -6
  32. package/src/utils/normalize.util.ts +142 -0
  33. package/src/utils/schema.util.ts +191 -0
  34. package/tests/env/appsettings.json +32 -0
  35. package/tests/env/dev.env.json +12 -0
  36. package/tests/env/dev.local.env.json +9 -0
  37. package/tests/env/env.schema.json +225 -0
  38. package/tests/env/keys.json +7 -0
  39. package/tests/env/settings/schema.json +239 -0
  40. package/tests/env/settings/settings.json +22 -0
  41. package/tests/env.int.test.ts +42 -0
  42. package/tests/exec.ts +19 -0
  43. package/tests/export.int.test.ts +9 -0
  44. package/tests/pull-push.int.test.ts +15 -0
  45. package/tests/run.js +32 -0
  46. package/tests/schema.int.test.ts +9 -0
  47. package/tests/setup.ts +13 -0
  48. package/tsconfig.build.json +10 -0
  49. package/tsconfig.json +37 -0
  50. package/arguments.d.ts +0 -25
  51. package/arguments.d.ts.map +0 -1
  52. package/arguments.js.map +0 -1
  53. package/commands/env.command.d.ts +0 -8
  54. package/commands/env.command.d.ts.map +0 -1
  55. package/commands/env.command.js +0 -85
  56. package/commands/env.command.js.map +0 -1
  57. package/commands/export.command.d.ts +0 -8
  58. package/commands/export.command.d.ts.map +0 -1
  59. package/commands/export.command.js +0 -54
  60. package/commands/export.command.js.map +0 -1
  61. package/commands/index.d.ts.map +0 -1
  62. package/commands/index.js +0 -14
  63. package/commands/index.js.map +0 -1
  64. package/commands/pull.command.d.ts +0 -7
  65. package/commands/pull.command.d.ts.map +0 -1
  66. package/commands/pull.command.js +0 -39
  67. package/commands/pull.command.js.map +0 -1
  68. package/commands/push.command.d.ts +0 -7
  69. package/commands/push.command.d.ts.map +0 -1
  70. package/commands/push.command.js +0 -38
  71. package/commands/push.command.js.map +0 -1
  72. package/commands/schema.command.d.ts +0 -4
  73. package/commands/schema.command.d.ts.map +0 -1
  74. package/commands/schema.command.js +0 -18
  75. package/commands/schema.command.js.map +0 -1
  76. package/exec.d.ts +0 -3
  77. package/exec.d.ts.map +0 -1
  78. package/exec.js +0 -142
  79. package/exec.js.map +0 -1
  80. package/index.d.ts.map +0 -1
  81. package/index.js +0 -20
  82. package/index.js.map +0 -1
  83. package/interfaces/index.d.ts.map +0 -1
  84. package/interfaces/index.js +0 -18
  85. package/interfaces/index.js.map +0 -1
  86. package/interfaces/loader.interface.d.ts +0 -21
  87. package/interfaces/loader.interface.d.ts.map +0 -1
  88. package/interfaces/loader.interface.js +0 -3
  89. package/interfaces/loader.interface.js.map +0 -1
  90. package/main.d.ts +0 -3
  91. package/main.d.ts.map +0 -1
  92. package/main.js +0 -6
  93. package/main.js.map +0 -1
  94. package/providers/app-settings.provider.d.ts +0 -8
  95. package/providers/app-settings.provider.d.ts.map +0 -1
  96. package/providers/app-settings.provider.js +0 -50
  97. package/providers/app-settings.provider.js.map +0 -1
  98. package/providers/azure-key-vault.provider.d.ts +0 -22
  99. package/providers/azure-key-vault.provider.d.ts.map +0 -1
  100. package/providers/azure-key-vault.provider.js +0 -164
  101. package/providers/azure-key-vault.provider.js.map +0 -1
  102. package/providers/index.d.ts +0 -8
  103. package/providers/index.d.ts.map +0 -1
  104. package/providers/index.js +0 -28
  105. package/providers/index.js.map +0 -1
  106. package/providers/package-json.provider.d.ts +0 -8
  107. package/providers/package-json.provider.d.ts.map +0 -1
  108. package/providers/package-json.provider.js +0 -29
  109. package/providers/package-json.provider.js.map +0 -1
  110. package/tsconfig.build.tsbuildinfo +0 -1
  111. package/utils/command.util.d.ts +0 -13
  112. package/utils/command.util.d.ts.map +0 -1
  113. package/utils/command.util.js +0 -134
  114. package/utils/command.util.js.map +0 -1
  115. package/utils/index.d.ts.map +0 -1
  116. package/utils/index.js +0 -23
  117. package/utils/index.js.map +0 -1
  118. package/utils/interpolate.util.d.ts +0 -4
  119. package/utils/interpolate.util.d.ts.map +0 -1
  120. package/utils/interpolate.util.js +0 -33
  121. package/utils/interpolate.util.js.map +0 -1
  122. package/utils/json.util.d.ts +0 -5
  123. package/utils/json.util.d.ts.map +0 -1
  124. package/utils/json.util.js +0 -48
  125. package/utils/json.util.js.map +0 -1
  126. package/utils/logger.d.ts +0 -3
  127. package/utils/logger.d.ts.map +0 -1
  128. package/utils/logger.js.map +0 -1
  129. package/utils/normalize.util.d.ts +0 -3
  130. package/utils/normalize.util.d.ts.map +0 -1
  131. package/utils/normalize.util.js +0 -61
  132. package/utils/normalize.util.js.map +0 -1
  133. package/utils/schema.util.d.ts +0 -11
  134. package/utils/schema.util.d.ts.map +0 -1
  135. package/utils/schema.util.js +0 -100
  136. package/utils/schema.util.js.map +0 -1
@@ -0,0 +1,66 @@
1
+ import { Arguments, Argv } from 'yargs';
2
+
3
+ /**
4
+ * Result of provider environment load.
5
+ *
6
+ * @export
7
+ * @type EnvResult
8
+ */
9
+ export type EnvResult =
10
+ | Record<string, unknown>
11
+ | Record<string, unknown>[]
12
+ | Promise<Record<string, unknown>>
13
+ | Promise<Record<string, unknown>[]>;
14
+
15
+ /**
16
+ * Wrapped provider result for inject to env.
17
+ *
18
+ * @export
19
+ * @interface EnvProviderResult
20
+ */
21
+ export interface EnvProviderResult {
22
+ key: string;
23
+ config?: Record<string, unknown>;
24
+ value: Record<string, any> | Record<string, any>[];
25
+ }
26
+
27
+ /**
28
+ * Provider handler.
29
+ *
30
+ * @export
31
+ * @interface EnvProvider
32
+ * @template A define arguments used by provider
33
+ * @template C define config used by provider
34
+ */
35
+ export interface EnvProvider<
36
+ A,
37
+ C extends Record<string, any> | undefined = undefined
38
+ > {
39
+ // unique key
40
+ key: string;
41
+
42
+ // modifies command building (adds or modifies commands, options .etc)
43
+ builder?: (builder: Argv<unknown>) => void;
44
+
45
+ // loads environment variables.
46
+ load: (argv: Arguments<A>, config?: C) => EnvResult | never;
47
+
48
+ // pulls vars
49
+ pull?: (argv: Arguments<A>, config?: C) => void;
50
+
51
+ // push vars
52
+ push?: (argv: Arguments<A>, config?: C) => void;
53
+ }
54
+
55
+ /**
56
+ * Provider definition for config file.
57
+ *
58
+ * @export
59
+ * @interface EnvProviderConfig
60
+ */
61
+ export interface EnvProviderConfig {
62
+ path: string;
63
+ type: 'integrated' | 'module' | 'script';
64
+ handler: EnvProvider<any, any>;
65
+ config?: Record<string, unknown>;
66
+ }
package/src/main.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { exec } from './exec';
4
+
5
+ // runs the program
6
+ exec(process.argv.slice(2));
@@ -0,0 +1,67 @@
1
+ import chalk from 'chalk';
2
+ import { CommandArguments } from '../arguments';
3
+ import { EnvProvider } from '../interfaces';
4
+ import { logger as globalLogger, readJson, writeJson } from '../utils';
5
+
6
+ const KEY = 'app-settings';
7
+
8
+ const logger = globalLogger.getChildLogger({
9
+ prefix: [chalk.bold.blue(`[${KEY}]`)]
10
+ });
11
+
12
+ const APP_SETTINGS_DEFAULT = {
13
+ '|DEFAULT|': {},
14
+ '|MODE|': {},
15
+ '|ENV|': {},
16
+ '|LOCAL|': {}
17
+ };
18
+
19
+ interface AppSettingsCommandArguments extends CommandArguments {
20
+ envFile: string;
21
+ }
22
+
23
+ /**
24
+ * Loads config from appsettings.json.
25
+ */
26
+ export const AppSettingsProvider: EnvProvider<AppSettingsCommandArguments> = {
27
+ key: KEY,
28
+
29
+ builder: (builder) => {
30
+ builder.options({
31
+ envFile: {
32
+ group: KEY,
33
+ alias: 'ef',
34
+ type: 'string',
35
+ default: '[[root]]/appsettings.json',
36
+ describe: 'Environment variables file path (non secrets)'
37
+ }
38
+ });
39
+ },
40
+
41
+ load: async ({ env, modes = [], envFile, local }) => {
42
+ const [appsettings = APP_SETTINGS_DEFAULT, wasFound] = await readJson(
43
+ envFile
44
+ );
45
+
46
+ if (!wasFound) {
47
+ logger.warn(`${chalk.blue(envFile)} not found`);
48
+
49
+ logger.debug(`creating default ${chalk.blue(envFile)} file`);
50
+
51
+ await writeJson(envFile, APP_SETTINGS_DEFAULT);
52
+ }
53
+
54
+ // only load local in env load cmd
55
+ if (!local) appsettings['|LOCAL|'] = null;
56
+
57
+ return [
58
+ appsettings['|DEFAULT|'],
59
+
60
+ appsettings['|ENV|']?.[env],
61
+
62
+ ...modes.map((mode) => appsettings['|MODE|']?.[mode]),
63
+
64
+ appsettings['|LOCAL|']?.[env]
65
+ ];
66
+ }
67
+ };
@@ -0,0 +1,277 @@
1
+ import chalk from 'chalk';
2
+ import {
3
+ AzureKeyVault,
4
+ AzureKeyVaultSecrets,
5
+ createAzureKeyVaultMock
6
+ } from '@achs/azure-key-vault';
7
+ import { CommandArguments } from '../arguments';
8
+ import { EnvProvider } from '../interfaces';
9
+ import {
10
+ generateSchemaFrom,
11
+ logger as globalLogger,
12
+ readJson,
13
+ schemaToJson,
14
+ writeJson
15
+ } from '../utils';
16
+ import { Arguments } from 'yargs';
17
+ import { existsSync } from 'fs';
18
+ import { PullCommandArguments } from 'commands/pull.command';
19
+
20
+ const KEY = 'azure-key-vault';
21
+
22
+ const logger = globalLogger.getChildLogger({
23
+ prefix: [chalk.bold.blue(`[${KEY}]`)]
24
+ });
25
+
26
+ interface AzureKeyVaultCommandArguments extends CommandArguments {
27
+ secretsFile: string;
28
+ keysFile?: string[];
29
+ vaultUrl: string;
30
+ spn: string;
31
+ password: string;
32
+ tenant: string;
33
+ mock: boolean;
34
+ }
35
+
36
+ interface AzureKeyVaultCommandConfig {
37
+ [key: string]: {
38
+ vaultUrl: string;
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Loads secrets from env files in env/secrets
44
+ * folder, loaded from Azure Key Vault.
45
+ */
46
+ export const AzureKeyVaultProvider: EnvProvider<
47
+ AzureKeyVaultCommandArguments & PullCommandArguments,
48
+ AzureKeyVaultCommandConfig
49
+ > = {
50
+ key: KEY,
51
+
52
+ builder: (builder) => {
53
+ builder.options({
54
+ secretsFile: {
55
+ group: KEY,
56
+ type: 'string',
57
+ default: '[[root]]/[[env]].env.json',
58
+ describe: 'Secret variables file path'
59
+ },
60
+ keysFile: {
61
+ group: KEY,
62
+ alias: ['k', 'keys'],
63
+ type: 'array',
64
+ default: ['[[root]]/keys.json', '../keys.json'],
65
+ describe: 'Azure Key Vault keys file path'
66
+ },
67
+ vaultUrl: {
68
+ group: KEY,
69
+ alias: 'url',
70
+ type: 'string',
71
+ describe: 'Azure Key Vault URL'
72
+ },
73
+ spn: {
74
+ group: KEY,
75
+ alias: ['clientId', 'id'],
76
+ type: 'string',
77
+ describe: 'SPN Client ID'
78
+ },
79
+ password: {
80
+ group: KEY,
81
+ alias: ['p', 'pass', 'clientSecret'],
82
+ type: 'string',
83
+ describe: 'SPN Client Secret Password'
84
+ },
85
+ tenant: {
86
+ group: KEY,
87
+ alias: 't',
88
+ type: 'string',
89
+ describe: 'Azure Tenant ID'
90
+ },
91
+ mock: {
92
+ group: KEY,
93
+ type: 'boolean',
94
+ default: false,
95
+ describe: 'Mocks Azure Key Vault client'
96
+ }
97
+ });
98
+ },
99
+
100
+ push: async (argv, config) => {
101
+ const [secrets, secretsWasFound] = await readJson(argv.secretsFile);
102
+
103
+ if (!secretsWasFound) {
104
+ logger.error(`${chalk.blue(argv.secretFile)} not found`);
105
+
106
+ process.exit(1);
107
+ }
108
+
109
+ logger.silly('local secrets loaded:', secrets);
110
+ const akv = await loadAzureKeyVaultClient(
111
+ argv,
112
+ config?.[argv.env]?.vaultUrl
113
+ );
114
+ logger.info('pushing variables to store');
115
+ const results = await akv.setAll(secrets);
116
+ logger.silly('secrets pushed:', results);
117
+
118
+ const schema = await generateSchemaFrom(
119
+ [{ key: KEY, value: secrets }],
120
+ argv
121
+ );
122
+
123
+ logger.silly('schema for akv updated:', schema);
124
+ },
125
+
126
+ pull: async (argv, config) => {
127
+ const schema = argv.schema?.[KEY] as
128
+ | Record<string, unknown>
129
+ | undefined;
130
+
131
+ if (!schema) {
132
+ logger.error('no schema found');
133
+
134
+ process.exit(1);
135
+ }
136
+
137
+ const jsonTemplate = schemaToJson(schema) as Record<string, unknown>;
138
+
139
+ const akv = await loadAzureKeyVaultClient(
140
+ argv,
141
+ config?.[argv.env]?.vaultUrl
142
+ );
143
+
144
+ logger.info('pulling variables from store');
145
+
146
+ const secrets = await akv.getFor(
147
+ jsonTemplate as AzureKeyVaultSecrets,
148
+ true
149
+ );
150
+
151
+ logger.silly('remote secrets loaded:', secrets);
152
+
153
+ await writeJson(argv.secretsFile, secrets, argv.overwrite, true);
154
+ },
155
+
156
+ load: async (argv, config) => {
157
+ const { secretsFile } = argv;
158
+
159
+ if (!existsSync(secretsFile)) {
160
+ logger.warn('secrets file not found, pulling from store');
161
+
162
+ await AzureKeyVaultProvider.pull!(argv, config);
163
+ }
164
+
165
+ const [secrets] = await readJson(secretsFile);
166
+
167
+ return [secrets];
168
+ }
169
+ };
170
+
171
+ /**
172
+ * Validate SPN credentials/keys.
173
+ *
174
+ * @param {(Record<string, any> | null)} keys
175
+ *
176
+ * @returns {boolean}
177
+ */
178
+ function keysAreValid(keys: Record<string, any> | null): boolean {
179
+ return keys && keys.clientId && keys.clientSecret && keys.tenantId;
180
+ }
181
+
182
+ /**
183
+ * Loads SPN Azure Key Vault credentials from keys file.
184
+ *
185
+ * @param {string} env
186
+ * @param {string[]} paths
187
+ *
188
+ * @returns {Record<string, string>} credentials
189
+ */
190
+ async function loadKeysFile(
191
+ env: string,
192
+ paths: string[]
193
+ ): Promise<Record<string, string>> {
194
+ logger.debug(`searching keys at ${chalk.yellow(paths.join(','))}`);
195
+
196
+ const readers = await Promise.all(paths.map((path) => readJson(path)));
197
+
198
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
199
+ const [keys, wasFound] = readers.find(
200
+ ([keys, wasFound]) => wasFound && keysAreValid(keys[env])
201
+ ) ?? [null, false];
202
+
203
+ // if (!wasFound || !keys) {
204
+ // logger.error(`no credentials found for ${chalk.green.underline(env)}`);
205
+ // logger.warn(
206
+ // 'if you use Azure CLI, set "keysFile": null in your env.config file'
207
+ // );
208
+
209
+ // process.exit(1);
210
+ // }
211
+
212
+ return keys?.[env] ?? {};
213
+ }
214
+
215
+ /**
216
+ * Loads credentials and initializes Azure Key Vault client.
217
+ *
218
+ * @param {Arguments<AzureKeyVaultCommandArguments>} argv command arguments
219
+ *
220
+ * @returns {*} {Promise<AzureKeyVault>}
221
+ */
222
+ async function loadAzureKeyVaultClient(
223
+ {
224
+ env,
225
+ vaultUrl,
226
+ spn,
227
+ password,
228
+ tenant,
229
+ app,
230
+ keysFile,
231
+ mock
232
+ }: Arguments<AzureKeyVaultCommandArguments>,
233
+ configVaultUrl?: string
234
+ ): Promise<AzureKeyVault> {
235
+ let url = process.env.AZURE_VAULT_URL ?? vaultUrl ?? configVaultUrl;
236
+
237
+ const config = {
238
+ project: process.env.AZURE_PROJECT ?? (app?.project as string),
239
+ group: process.env.AZURE_GROUP ?? (app?.name as string),
240
+ env
241
+ };
242
+
243
+ if (!config.project) {
244
+ logger.error(
245
+ `no project info from ${chalk.blue('package.json')} found`
246
+ );
247
+
248
+ process.exit(1);
249
+ }
250
+
251
+ const credentials = {
252
+ clientId: process.env.AZURE_CLIENT_ID ?? spn,
253
+ clientSecret: process.env.AZURE_CLIENT_SECRET ?? password,
254
+ tenantId: process.env.AZURE_TENANT_ID ?? tenant
255
+ };
256
+
257
+ if (keysFile && keysFile.length > 0 && !keysAreValid(credentials)) {
258
+ const keys = await loadKeysFile(env, keysFile);
259
+
260
+ url ??= keys.vaultUrl;
261
+ credentials.clientId ??= keys.clientId;
262
+ credentials.clientSecret ??= keys.clientSecret;
263
+ credentials.tenantId ??= keys.tenantId;
264
+ }
265
+
266
+ logger.debug(
267
+ `credentials loaded for project ${chalk.bold.underline.yellowBright(
268
+ config.project
269
+ )} and group ${chalk.bold.underline.yellowBright(config.group)}`
270
+ );
271
+
272
+ logger.debug(`connected to ${chalk.bold.underline.greenBright(url)}`);
273
+
274
+ return mock
275
+ ? createAzureKeyVaultMock(config)
276
+ : new AzureKeyVault(url, config, credentials);
277
+ }
@@ -0,0 +1,29 @@
1
+ import { EnvProvider } from '../interfaces';
2
+ import { AppSettingsProvider } from './app-settings.provider';
3
+ import { PackageJsonProvider } from './package-json.provider';
4
+ import { AzureKeyVaultProvider } from './azure-key-vault.provider';
5
+ import { LocalProvider } from './local.provider';
6
+
7
+ const IntegratedProviders: Record<string, EnvProvider<any, any>> = {
8
+ [PackageJsonProvider.key]: PackageJsonProvider,
9
+ [AppSettingsProvider.key]: AppSettingsProvider,
10
+ [AzureKeyVaultProvider.key]: AzureKeyVaultProvider,
11
+ [LocalProvider.key]: LocalProvider
12
+ };
13
+
14
+ const IntegratedProviderConfig = [
15
+ {
16
+ path: PackageJsonProvider.key
17
+ },
18
+ {
19
+ path: AppSettingsProvider.key
20
+ },
21
+ {
22
+ path: AzureKeyVaultProvider.key
23
+ },
24
+ {
25
+ path: LocalProvider.key
26
+ }
27
+ ];
28
+
29
+ export { IntegratedProviders, IntegratedProviderConfig };
@@ -0,0 +1,44 @@
1
+ import { existsSync } from 'fs';
2
+ import { CommandArguments } from '../arguments';
3
+ import { EnvProvider } from '../interfaces';
4
+ import { readJson, writeJson } from '../utils';
5
+
6
+ const KEY = 'local';
7
+
8
+ interface LocalCommandArguments extends CommandArguments {
9
+ localFile: string;
10
+ }
11
+
12
+ /**
13
+ * Loads local variables from env files in env folder.
14
+ */
15
+ export const LocalProvider: EnvProvider<LocalCommandArguments> = {
16
+ key: KEY,
17
+
18
+ builder: (builder) => {
19
+ builder.options({
20
+ localFile: {
21
+ group: KEY,
22
+ alias: 'lf',
23
+ type: 'string',
24
+ default: '[[root]]/[[env]].local.env.json',
25
+ describe: 'Local secret variables file path'
26
+ }
27
+ });
28
+ },
29
+
30
+ load: async ({ localFile, ci }) => {
31
+ // ci mode doesn't load local vars
32
+ if (ci) return [];
33
+
34
+ if (!existsSync(localFile)) {
35
+ await writeJson(localFile, {});
36
+
37
+ return [];
38
+ }
39
+
40
+ const [vars] = await readJson(localFile);
41
+
42
+ return [vars];
43
+ }
44
+ };
@@ -0,0 +1,39 @@
1
+ import { CommandArguments } from '../arguments';
2
+ import { EnvProvider } from '../interfaces';
3
+
4
+ const KEY = 'package-json';
5
+
6
+ interface PackageJsonCommandArguments extends CommandArguments {
7
+ varPrefix: string;
8
+ }
9
+
10
+ /**
11
+ * Loads project info from package.json.
12
+ */
13
+ export const PackageJsonProvider: EnvProvider<PackageJsonCommandArguments> = {
14
+ key: KEY,
15
+
16
+ builder: (builder) => {
17
+ builder.options({
18
+ varPrefix: {
19
+ group: KEY,
20
+ alias: 'vp',
21
+ type: 'string',
22
+ default: '',
23
+ describe: 'Prefix for loaded variables'
24
+ }
25
+ });
26
+ },
27
+
28
+ load: ({ env, app, varPrefix }) => {
29
+ return {
30
+ [`${varPrefix}ENV`]: env,
31
+
32
+ [`${varPrefix}VERSION`]: app?.version,
33
+ [`${varPrefix}PROJECT`]: app?.project,
34
+ [`${varPrefix}NAME`]: app?.name,
35
+ [`${varPrefix}TITLE`]: app?.title,
36
+ [`${varPrefix}DESCRIPTION`]: app?.description
37
+ };
38
+ }
39
+ };