@itgorillaz/configify 1.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.
- package/.prettierrc +4 -0
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/src/configify.module.d.ts +12 -0
- package/dist/src/configify.module.js +120 -0
- package/dist/src/configify.module.js.map +1 -0
- package/dist/src/configuration/configuration-options.interface.d.ts +11 -0
- package/dist/src/configuration/configuration-options.interface.js +9 -0
- package/dist/src/configuration/configuration-options.interface.js.map +1 -0
- package/dist/src/configuration/configuration-parser.interface.d.ts +3 -0
- package/dist/src/configuration/configuration-parser.interface.js +3 -0
- package/dist/src/configuration/configuration-parser.interface.js.map +1 -0
- package/dist/src/configuration/configuration-providers.interface.d.ts +5 -0
- package/dist/src/configuration/configuration-providers.interface.js +3 -0
- package/dist/src/configuration/configuration-providers.interface.js.map +1 -0
- package/dist/src/configuration/configuration.registry.d.ts +9 -0
- package/dist/src/configuration/configuration.registry.js +25 -0
- package/dist/src/configuration/configuration.registry.js.map +1 -0
- package/dist/src/configuration/index.d.ts +6 -0
- package/dist/src/configuration/index.js +23 -0
- package/dist/src/configuration/index.js.map +1 -0
- package/dist/src/configuration/parsers/configuration-parser.factory.d.ts +7 -0
- package/dist/src/configuration/parsers/configuration-parser.factory.js +27 -0
- package/dist/src/configuration/parsers/configuration-parser.factory.js.map +1 -0
- package/dist/src/configuration/parsers/dotenv-configuration.parser.d.ts +4 -0
- package/dist/src/configuration/parsers/dotenv-configuration.parser.js +12 -0
- package/dist/src/configuration/parsers/dotenv-configuration.parser.js.map +1 -0
- package/dist/src/configuration/parsers/index.d.ts +4 -0
- package/dist/src/configuration/parsers/index.js +21 -0
- package/dist/src/configuration/parsers/index.js.map +1 -0
- package/dist/src/configuration/parsers/json-configuration.parser.d.ts +4 -0
- package/dist/src/configuration/parsers/json-configuration.parser.js +11 -0
- package/dist/src/configuration/parsers/json-configuration.parser.js.map +1 -0
- package/dist/src/configuration/parsers/yaml-configuration.parser.d.ts +4 -0
- package/dist/src/configuration/parsers/yaml-configuration.parser.js +12 -0
- package/dist/src/configuration/parsers/yaml-configuration.parser.js.map +1 -0
- package/dist/src/configuration/resolvers/aws/index.d.ts +2 -0
- package/dist/src/configuration/resolvers/aws/index.js +19 -0
- package/dist/src/configuration/resolvers/aws/index.js.map +1 -0
- package/dist/src/configuration/resolvers/aws/parameter-store-configuration.resolver.d.ts +12 -0
- package/dist/src/configuration/resolvers/aws/parameter-store-configuration.resolver.js +61 -0
- package/dist/src/configuration/resolvers/aws/parameter-store-configuration.resolver.js.map +1 -0
- package/dist/src/configuration/resolvers/aws/secrets-manager-configuration.resolver.d.ts +12 -0
- package/dist/src/configuration/resolvers/aws/secrets-manager-configuration.resolver.js +59 -0
- package/dist/src/configuration/resolvers/aws/secrets-manager-configuration.resolver.js.map +1 -0
- package/dist/src/configuration/resolvers/configuration-resolver.interface.d.ts +3 -0
- package/dist/src/configuration/resolvers/configuration-resolver.interface.js +3 -0
- package/dist/src/configuration/resolvers/configuration-resolver.interface.js.map +1 -0
- package/dist/src/configuration/resolvers/index.d.ts +3 -0
- package/dist/src/configuration/resolvers/index.js +20 -0
- package/dist/src/configuration/resolvers/index.js.map +1 -0
- package/dist/src/configuration/resolvers/resolved-value.interface.d.ts +7 -0
- package/dist/src/configuration/resolvers/resolved-value.interface.js +3 -0
- package/dist/src/configuration/resolvers/resolved-value.interface.js.map +1 -0
- package/dist/src/decorators/configuration.decorator.d.ts +2 -0
- package/dist/src/decorators/configuration.decorator.js +13 -0
- package/dist/src/decorators/configuration.decorator.js.map +1 -0
- package/dist/src/decorators/index.d.ts +2 -0
- package/dist/src/decorators/index.js +19 -0
- package/dist/src/decorators/index.js.map +1 -0
- package/dist/src/decorators/value.decorator.d.ts +10 -0
- package/dist/src/decorators/value.decorator.js +14 -0
- package/dist/src/decorators/value.decorator.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interpolation/variables.d.ts +9 -0
- package/dist/src/interpolation/variables.js +37 -0
- package/dist/src/interpolation/variables.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/index.d.ts +1 -0
- package/index.js +6 -0
- package/index.ts +1 -0
- package/nest-cli.json +8 -0
- package/package.json +82 -0
- package/src/configify.module.ts +241 -0
- package/src/configuration/configuration-options.interface.ts +51 -0
- package/src/configuration/configuration-parser.interface.ts +15 -0
- package/src/configuration/configuration-providers.interface.ts +9 -0
- package/src/configuration/configuration.registry.ts +70 -0
- package/src/configuration/index.ts +6 -0
- package/src/configuration/parsers/configuration-parser.factory.ts +53 -0
- package/src/configuration/parsers/dotenv-configuration.parser.ts +18 -0
- package/src/configuration/parsers/index.ts +4 -0
- package/src/configuration/parsers/json-configuration.parser.ts +17 -0
- package/src/configuration/parsers/yaml-configuration.parser.ts +18 -0
- package/src/configuration/resolvers/aws/index.ts +2 -0
- package/src/configuration/resolvers/aws/parameter-store-configuration.resolver.ts +115 -0
- package/src/configuration/resolvers/aws/secrets-manager-configuration.resolver.ts +117 -0
- package/src/configuration/resolvers/configuration-resolver.interface.ts +11 -0
- package/src/configuration/resolvers/index.ts +3 -0
- package/src/configuration/resolvers/resolved-value.interface.ts +10 -0
- package/src/decorators/configuration.decorator.ts +26 -0
- package/src/decorators/index.ts +2 -0
- package/src/decorators/value.decorator.ts +47 -0
- package/src/index.ts +2 -0
- package/src/interpolation/variables.ts +101 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';
|
|
2
|
+
import { ConfigurationResolver } from '../configuration-resolver.interface';
|
|
3
|
+
import { ResolvedValue } from '../resolved-value.interface';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AWS Parameter Store configuration resolver.
|
|
7
|
+
*/
|
|
8
|
+
export class AwsParameterStoreConfigurationResolver
|
|
9
|
+
implements ConfigurationResolver
|
|
10
|
+
{
|
|
11
|
+
private readonly AWS_PARAMETER_STORE_YAML_KEY = 'aws-parameter-store';
|
|
12
|
+
private readonly AWS_PARAMETER_STORE_ENV_PREFIX = 'AWS_PARAMETER_STORE';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new instance of parameter store configuration resolver
|
|
16
|
+
*
|
|
17
|
+
* @param {SSMClient} ssm systems manager client
|
|
18
|
+
*/
|
|
19
|
+
constructor(private readonly ssm: SSMClient) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetches parameters and assign it to an object representation
|
|
23
|
+
* of the configuration.
|
|
24
|
+
*
|
|
25
|
+
* @param {Record<string, any>} config the configuration object
|
|
26
|
+
* @returns {Record<string, any>} the cofiguration with secret values assigned
|
|
27
|
+
* @throws {Error} if unable to fetch the secret
|
|
28
|
+
*/
|
|
29
|
+
async resolve(config: Record<string, any>): Promise<Record<string, any>> {
|
|
30
|
+
const parameters = this.filterConfiguration(config);
|
|
31
|
+
const promises = this.buildBulkRequest(parameters);
|
|
32
|
+
|
|
33
|
+
const results = await Promise.all(promises);
|
|
34
|
+
|
|
35
|
+
const errors = results.filter((r) => !r.success);
|
|
36
|
+
if (errors && errors.length) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Unable to resolve parameter:\n${errors
|
|
39
|
+
.map((e) => `${e.key}: ${e.id} - ${e.error.message}`)
|
|
40
|
+
.join('\n')}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const result of results) {
|
|
45
|
+
config[result.key] = result.value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return config;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Filters the configuration object by aws parameters store keys.
|
|
53
|
+
*
|
|
54
|
+
* @param {Record<string, any>} config the configuration object
|
|
55
|
+
* @returns {Record<string, any>} the filtered object
|
|
56
|
+
*/
|
|
57
|
+
private filterConfiguration(
|
|
58
|
+
config: Record<string, any>,
|
|
59
|
+
): Record<string, any> {
|
|
60
|
+
return Object.fromEntries(
|
|
61
|
+
Object.entries(config).filter(
|
|
62
|
+
([key]) =>
|
|
63
|
+
key.startsWith(this.AWS_PARAMETER_STORE_YAML_KEY) ||
|
|
64
|
+
key.startsWith(this.AWS_PARAMETER_STORE_ENV_PREFIX),
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolves parameter values.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} key the object key
|
|
73
|
+
* @param {string} id the secret id
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
private async resolveSecretValue(
|
|
77
|
+
key: string,
|
|
78
|
+
id: string,
|
|
79
|
+
): Promise<ResolvedValue> {
|
|
80
|
+
try {
|
|
81
|
+
const payload = { Name: id, WithDecryption: true };
|
|
82
|
+
const command = new GetParameterCommand(payload);
|
|
83
|
+
const response = await this.ssm.send(command);
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
key,
|
|
87
|
+
value: response.Parameter?.Value,
|
|
88
|
+
success: true,
|
|
89
|
+
};
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return {
|
|
92
|
+
id,
|
|
93
|
+
key,
|
|
94
|
+
error: e,
|
|
95
|
+
success: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates a list of promises to get parameter values.
|
|
102
|
+
*
|
|
103
|
+
* @param {Record<string, any>} config the configuration object
|
|
104
|
+
* @returns {Promise<ResolvedValue>[]} the get parameter value promises
|
|
105
|
+
*/
|
|
106
|
+
private buildBulkRequest(
|
|
107
|
+
config: Record<string, any>,
|
|
108
|
+
): Promise<ResolvedValue>[] {
|
|
109
|
+
const promises = [];
|
|
110
|
+
for (const [key, value] of Object.entries(config)) {
|
|
111
|
+
promises.push(this.resolveSecretValue(key, value));
|
|
112
|
+
}
|
|
113
|
+
return promises;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GetSecretValueCommand,
|
|
3
|
+
SecretsManagerClient,
|
|
4
|
+
} from '@aws-sdk/client-secrets-manager';
|
|
5
|
+
import { ConfigurationResolver } from '../configuration-resolver.interface';
|
|
6
|
+
import { ResolvedValue } from '../resolved-value.interface';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AWS Secrets Manager configuration resolver.
|
|
10
|
+
*/
|
|
11
|
+
export class AwsSecretsManagerConfigurationResolver
|
|
12
|
+
implements ConfigurationResolver
|
|
13
|
+
{
|
|
14
|
+
private readonly AWS_SECRETS_AMANGER_YAML_KEY = 'aws-secrets-manager';
|
|
15
|
+
private readonly AWS_SECRETS_MANAGER_ENV_PREFIX = 'AWS_SECRETS_MANAGER';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new instance of secrets manager configuration resolver
|
|
19
|
+
*
|
|
20
|
+
* @param {SecretsManagerClient} secretsManager secrets manager client
|
|
21
|
+
*/
|
|
22
|
+
constructor(private readonly secretsManager: SecretsManagerClient) {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetches secrets and assign it to an object representation
|
|
26
|
+
* of the configuration.
|
|
27
|
+
*
|
|
28
|
+
* @param {Record<string, any>} config the configuration object
|
|
29
|
+
* @returns {Record<string, any>} the cofiguration with secret values assigned
|
|
30
|
+
* @throws {Error} if unable to fetch the secret
|
|
31
|
+
*/
|
|
32
|
+
async resolve(config: Record<string, any>): Promise<Record<string, any>> {
|
|
33
|
+
const secrets = this.filterConfiguration(config);
|
|
34
|
+
const promises = this.buildBulkRequest(secrets);
|
|
35
|
+
|
|
36
|
+
const results = await Promise.all(promises);
|
|
37
|
+
|
|
38
|
+
const errors = results.filter((r) => !r.success);
|
|
39
|
+
if (errors && errors.length) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Unable to resolve secrets:\n${errors
|
|
42
|
+
.map((e) => `${e.key}: ${e.id} - ${e.error.message}`)
|
|
43
|
+
.join('\n')}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
config[result.key] = result.value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Filters the configuration object by aws secrets manager keys.
|
|
56
|
+
*
|
|
57
|
+
* @param {Record<string, any>} config the configuration object
|
|
58
|
+
* @returns {Record<string, any>} the filtered object
|
|
59
|
+
*/
|
|
60
|
+
private filterConfiguration(
|
|
61
|
+
config: Record<string, any>,
|
|
62
|
+
): Record<string, any> {
|
|
63
|
+
return Object.fromEntries(
|
|
64
|
+
Object.entries(config).filter(
|
|
65
|
+
([key]) =>
|
|
66
|
+
key.startsWith(this.AWS_SECRETS_AMANGER_YAML_KEY) ||
|
|
67
|
+
key.startsWith(this.AWS_SECRETS_MANAGER_ENV_PREFIX),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolves secret values.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} key the object key
|
|
76
|
+
* @param {string} id the secret id
|
|
77
|
+
* @returns
|
|
78
|
+
*/
|
|
79
|
+
private async resolveSecretValue(
|
|
80
|
+
key: string,
|
|
81
|
+
id: string,
|
|
82
|
+
): Promise<ResolvedValue> {
|
|
83
|
+
try {
|
|
84
|
+
const command = new GetSecretValueCommand({ SecretId: id });
|
|
85
|
+
const response = await this.secretsManager.send(command);
|
|
86
|
+
return {
|
|
87
|
+
id,
|
|
88
|
+
key,
|
|
89
|
+
value: response.SecretString,
|
|
90
|
+
success: true,
|
|
91
|
+
};
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return {
|
|
94
|
+
id,
|
|
95
|
+
key,
|
|
96
|
+
error: e,
|
|
97
|
+
success: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates a list of promises to get secret values.
|
|
104
|
+
*
|
|
105
|
+
* @param {Record<string, any>} config the configuration object
|
|
106
|
+
* @returns {Promise<ResolvedValue>[]} the get secret value promises
|
|
107
|
+
*/
|
|
108
|
+
private buildBulkRequest(
|
|
109
|
+
config: Record<string, any>,
|
|
110
|
+
): Promise<ResolvedValue>[] {
|
|
111
|
+
const promises = [];
|
|
112
|
+
for (const [key, value] of Object.entries(config)) {
|
|
113
|
+
promises.push(this.resolveSecretValue(key, value));
|
|
114
|
+
}
|
|
115
|
+
return promises;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The configuration resolver interface
|
|
3
|
+
*/
|
|
4
|
+
export interface ConfigurationResolver {
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a remote configuration
|
|
7
|
+
*
|
|
8
|
+
* @param {Record<string, any>} configuration the configuration obejct
|
|
9
|
+
*/
|
|
10
|
+
resolve(configuration: Record<string, any>): Promise<Record<string, any>>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ConfigurationRegistry } from '../configuration/configuration.registry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The configuration metadata
|
|
5
|
+
*/
|
|
6
|
+
export const CONFIGURATION_METADATA = Symbol.for('__configuration__');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The configuration decorator.
|
|
10
|
+
*
|
|
11
|
+
* Decorating a class with this decorator will make it
|
|
12
|
+
* visible for the module to manage its instance and assign
|
|
13
|
+
* values to the class attributes based on configuration files.
|
|
14
|
+
*
|
|
15
|
+
* @returns {ClassDecorator} the class decorator
|
|
16
|
+
*/
|
|
17
|
+
export const Configuration = (): ClassDecorator => {
|
|
18
|
+
return (target: object) => {
|
|
19
|
+
ConfigurationRegistry.registerTarget(target);
|
|
20
|
+
Reflect.defineMetadata(
|
|
21
|
+
CONFIGURATION_METADATA,
|
|
22
|
+
target.constructor.name,
|
|
23
|
+
target,
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ConfigurationRegistry } from '../configuration/configuration.registry';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The value metadata.
|
|
5
|
+
*/
|
|
6
|
+
export const VALUE_METADATA = Symbol.for('__value__');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The value properties metadata.
|
|
10
|
+
*/
|
|
11
|
+
export const VALUE_PROPERTIES_METADATA = Symbol.for('__properties__');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The value options.
|
|
15
|
+
* Allows custom parsing to configuration values
|
|
16
|
+
*/
|
|
17
|
+
export interface ValueOptions {
|
|
18
|
+
parse: (value: any) => any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The value decorated key interface.
|
|
23
|
+
*/
|
|
24
|
+
export interface ValueDecoratedKey {
|
|
25
|
+
key: string;
|
|
26
|
+
options?: ValueOptions;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The value decorator.
|
|
31
|
+
*
|
|
32
|
+
* This decorator defines which configuration key should
|
|
33
|
+
* be assined to the class attribute.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} key the configuration key
|
|
36
|
+
* @param {ValueOptions} options custom options
|
|
37
|
+
* @returns {PropertyDecorator} the property decorator
|
|
38
|
+
*/
|
|
39
|
+
export const Value = (
|
|
40
|
+
key: string,
|
|
41
|
+
options?: ValueOptions,
|
|
42
|
+
): PropertyDecorator => {
|
|
43
|
+
return (target: object, property: string) => {
|
|
44
|
+
ConfigurationRegistry.registerAttribute(target, property);
|
|
45
|
+
Reflect.defineMetadata(VALUE_METADATA, { key, options }, target, property);
|
|
46
|
+
};
|
|
47
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The variables expansion class.
|
|
3
|
+
*
|
|
4
|
+
* This is a slightly modified version of the dotenv-expand lib
|
|
5
|
+
* due to the lack of support to variables containing '.' and '-'.
|
|
6
|
+
*
|
|
7
|
+
* Once the dotev-expand lib adds support to '.' and '-' this class can be removed.
|
|
8
|
+
*/
|
|
9
|
+
export class Variables {
|
|
10
|
+
/**
|
|
11
|
+
* Escaped \$ matcher
|
|
12
|
+
*/
|
|
13
|
+
private static readonly ESCAPE_PATTERN: RegExp = /\\\$/g;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Unescaped $ matcher
|
|
17
|
+
*/
|
|
18
|
+
private static readonly UNESCAPED_PATTERN: RegExp = /(?!(?<=\\))\$/g;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* (
|
|
22
|
+
* (?!(?<=\\))\$ // only match dollar signs that are not escaped
|
|
23
|
+
* {? // optional opening curly brace
|
|
24
|
+
* ([\w]+) // match the variable name
|
|
25
|
+
* ([\w-.]+) // match the variable name
|
|
26
|
+
* (?::-([^}\\]*))? // match an optional default value
|
|
27
|
+
* }? // optional closing curly brace
|
|
28
|
+
* )
|
|
29
|
+
*/
|
|
30
|
+
private static readonly EXPANSION_GROUP_PATTERN: RegExp =
|
|
31
|
+
/((?!(?<=\\))\${?([\w-.]+)(?::-([^}\\]*))?}?)/;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Expands the variables of the given object.
|
|
35
|
+
*
|
|
36
|
+
* @param {Record<string, any>} record The object
|
|
37
|
+
* @returns {Record<string, any>} The expanded object
|
|
38
|
+
*/
|
|
39
|
+
static expand(record: Record<string, any>): Record<string, any> {
|
|
40
|
+
const expanded = {};
|
|
41
|
+
for (const key in record) {
|
|
42
|
+
const value = record[key];
|
|
43
|
+
expanded[key] = this.escapeSequences(this.interpolate(value, record));
|
|
44
|
+
}
|
|
45
|
+
return expanded;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Replaces ${variable} with actual values recursively.
|
|
50
|
+
*
|
|
51
|
+
* @param value
|
|
52
|
+
* @param record
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
private static interpolate(
|
|
56
|
+
value: string,
|
|
57
|
+
record: Record<string, any>,
|
|
58
|
+
): string {
|
|
59
|
+
const lastUnescapedDollarSignIndex = this.lastIndexOf(
|
|
60
|
+
value,
|
|
61
|
+
this.UNESCAPED_PATTERN,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (lastUnescapedDollarSignIndex === -1) return value;
|
|
65
|
+
|
|
66
|
+
const rightMostGroup = value.slice(lastUnescapedDollarSignIndex);
|
|
67
|
+
const match = rightMostGroup.match(this.EXPANSION_GROUP_PATTERN);
|
|
68
|
+
|
|
69
|
+
if (match != null) {
|
|
70
|
+
const [, group, variableName, defaultValue] = match;
|
|
71
|
+
return this.interpolate(
|
|
72
|
+
value.replace(group, defaultValue || record[variableName] || ''),
|
|
73
|
+
record,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Replaces escaped \$ signs with $
|
|
82
|
+
*
|
|
83
|
+
* @param value
|
|
84
|
+
* @returns
|
|
85
|
+
*/
|
|
86
|
+
private static escapeSequences(value: string): string {
|
|
87
|
+
return value.replace(this.ESCAPE_PATTERN, '$');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Finds the last index of the matching regex.
|
|
92
|
+
*
|
|
93
|
+
* @param value
|
|
94
|
+
* @param regex
|
|
95
|
+
* @returns
|
|
96
|
+
*/
|
|
97
|
+
private static lastIndexOf(value: string, regex: RegExp): number {
|
|
98
|
+
const matches = Array.from(value.matchAll(regex));
|
|
99
|
+
return matches.length > 0 ? matches.slice(-1)[0].index : -1;
|
|
100
|
+
}
|
|
101
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "es2017",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strictNullChecks": false,
|
|
16
|
+
"noImplicitAny": false,
|
|
17
|
+
"strictBindCallApply": false,
|
|
18
|
+
"forceConsistentCasingInFileNames": false,
|
|
19
|
+
"noFallthroughCasesInSwitch": false
|
|
20
|
+
}
|
|
21
|
+
}
|