updating-secrets 1.1.0 → 1.2.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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
-
import { type SecretDefinitions, type SecretValues } from '../secrets-definition/define-secrets.js';
|
|
2
|
+
import { type ProcessedSecretDefinitions, type SecretDefinitions, type SecretValues } from '../secrets-definition/define-secrets.js';
|
|
3
3
|
import { BaseSecretsAdapter } from './base.adapter.js';
|
|
4
4
|
/**
|
|
5
5
|
* Options for {@link SecretsJsonFileAdapter}.
|
|
@@ -28,8 +28,10 @@ export type SecretsJsonFileAdapterOptions<Secrets extends SecretDefinitions = an
|
|
|
28
28
|
existsSync: (filePath: string) => boolean;
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
|
-
* Optional function that will automatically generate and save
|
|
32
|
-
* missing.
|
|
31
|
+
* Optional function that will automatically generate and save secrets if the JSON file is
|
|
32
|
+
* missing or if any secrets are missing. Only missing secrets will be added from the generated
|
|
33
|
+
* values; existing secrets are preserved. This is particularly useful for dev or testing
|
|
34
|
+
* environments where new secrets may be added over time.
|
|
33
35
|
*/
|
|
34
36
|
generateValues: (() => MaybePromise<SecretValues<Secrets>>) | undefined;
|
|
35
37
|
};
|
|
@@ -51,7 +53,7 @@ export declare class SecretsJsonFileAdapter<const Secrets extends SecretDefiniti
|
|
|
51
53
|
/** Path to the JSON */
|
|
52
54
|
jsonFilePath: string, options?: PartialWithUndefined<SecretsJsonFileAdapterOptions<Secrets>>);
|
|
53
55
|
/** Loads secrets from the given JSON file path. */
|
|
54
|
-
loadSecrets(): Promise<any>;
|
|
56
|
+
loadSecrets(secrets: Readonly<ProcessedSecretDefinitions>): Promise<any>;
|
|
55
57
|
/** Load an individual secret from the JSON file. */
|
|
56
58
|
loadSingleSecret(secretKey: string): Promise<any>;
|
|
57
59
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { check } from '@augment-vir/assert';
|
|
1
2
|
import { mergeDefinedProperties, parseWithJson5, } from '@augment-vir/common';
|
|
2
3
|
import { existsSync as existsSyncImport } from 'node:fs';
|
|
3
4
|
import { mkdir as mkDirImport, readFile as readFileImport, writeFile as writeFileImport, } from 'node:fs/promises';
|
|
4
5
|
import { dirname } from 'node:path';
|
|
6
|
+
import { checkValidShape } from 'object-shape-tester';
|
|
5
7
|
import { BaseSecretsAdapter } from './base.adapter.js';
|
|
6
8
|
const defaultSecretsJsonFileAdapterOptions = {
|
|
7
9
|
fsOverride: {
|
|
@@ -35,21 +37,44 @@ export class SecretsJsonFileAdapter extends BaseSecretsAdapter {
|
|
|
35
37
|
this.options = mergeDefinedProperties(defaultSecretsJsonFileAdapterOptions, options);
|
|
36
38
|
}
|
|
37
39
|
/** Loads secrets from the given JSON file path. */
|
|
38
|
-
async loadSecrets() {
|
|
39
|
-
|
|
40
|
+
async loadSecrets(secrets) {
|
|
41
|
+
const fileExists = this.options.fsOverride.existsSync(this.jsonFilePath);
|
|
42
|
+
if (!fileExists) {
|
|
40
43
|
if (this.options.generateValues) {
|
|
41
44
|
const newSecrets = await this.options.generateValues();
|
|
42
45
|
await this.options.fsOverride.promises.mkdir(dirname(this.jsonFilePath), {
|
|
43
46
|
recursive: true,
|
|
44
47
|
});
|
|
45
48
|
await this.options.fsOverride.promises.writeFile(this.jsonFilePath, JSON.stringify(newSecrets));
|
|
49
|
+
return newSecrets;
|
|
46
50
|
}
|
|
47
51
|
else {
|
|
48
52
|
throw new Error(`Missing secrets JSON file at '${this.jsonFilePath}'`);
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
const fileContents = String(await this.options.fsOverride.promises.readFile(this.jsonFilePath));
|
|
52
|
-
|
|
56
|
+
const existingSecrets = parseWithJson5(fileContents);
|
|
57
|
+
if (this.options.generateValues) {
|
|
58
|
+
const invalidSecretKeys = Object.keys(secrets).filter((key) => {
|
|
59
|
+
if (!(key in existingSecrets)) {
|
|
60
|
+
/** Secret is missing. */
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const secretValue = existingSecrets[key];
|
|
64
|
+
const shapeDefinition = secrets[key]?.shapeDefinition;
|
|
65
|
+
return shapeDefinition
|
|
66
|
+
? !checkValidShape(secretValue, shapeDefinition)
|
|
67
|
+
: !check.isString(secretValue);
|
|
68
|
+
});
|
|
69
|
+
if (invalidSecretKeys.length > 0) {
|
|
70
|
+
const generatedSecrets = await this.options.generateValues();
|
|
71
|
+
invalidSecretKeys.forEach((invalidSecretKey) => {
|
|
72
|
+
existingSecrets[invalidSecretKey] = generatedSecrets[invalidSecretKey];
|
|
73
|
+
});
|
|
74
|
+
await this.options.fsOverride.promises.writeFile(this.jsonFilePath, JSON.stringify(existingSecrets));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return existingSecrets;
|
|
53
78
|
}
|
|
54
79
|
/** Load an individual secret from the JSON file. */
|
|
55
80
|
async loadSingleSecret(secretKey) {
|
|
@@ -13,7 +13,9 @@ export class SecretLoadError extends Error {
|
|
|
13
13
|
allErrors = [];
|
|
14
14
|
constructor(originalError, { adapterName, secretName, }) {
|
|
15
15
|
const message = `Failed to load secret '${secretName}' from adapter '${adapterName}': ${extractErrorMessage(originalError)}`;
|
|
16
|
-
super(message, {
|
|
16
|
+
super(message, {
|
|
17
|
+
cause: originalError,
|
|
18
|
+
});
|
|
17
19
|
this.allErrors.push(originalError);
|
|
18
20
|
}
|
|
19
21
|
}
|
package/dist/updating-secrets.js
CHANGED
|
@@ -141,14 +141,14 @@ export class UpdatingSecrets {
|
|
|
141
141
|
try {
|
|
142
142
|
const rawSecrets = await adapter.loadSecrets(this.processedSecrets);
|
|
143
143
|
return mapObjectValues(rawSecrets, async (secretName, loadedSecretValue) => {
|
|
144
|
+
const shapeDefinition = this.processedSecrets[secretName]?.shapeDefinition;
|
|
144
145
|
try {
|
|
145
146
|
const value = await loadedSecretValue;
|
|
146
147
|
if (value instanceof Error) {
|
|
147
148
|
throw value;
|
|
148
149
|
}
|
|
149
|
-
else if (
|
|
150
|
-
assertValidShape(value,
|
|
151
|
-
.shapeDefinition,
|
|
150
|
+
else if (shapeDefinition) {
|
|
151
|
+
assertValidShape(value, shapeDefinition,
|
|
152
152
|
/** Allow extra keys for forwards compatibility. */
|
|
153
153
|
{
|
|
154
154
|
allowExtraKeys: true,
|
|
@@ -157,6 +157,10 @@ export class UpdatingSecrets {
|
|
|
157
157
|
return value;
|
|
158
158
|
}
|
|
159
159
|
catch (caught) {
|
|
160
|
+
if (shapeDefinition &&
|
|
161
|
+
checkValidShape(undefined, shapeDefinition)) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
160
164
|
const error = new SecretLoadError(ensureError(caught), {
|
|
161
165
|
adapterName: adapter.adapterName,
|
|
162
166
|
secretName,
|
|
@@ -330,6 +334,9 @@ export class UpdatingSecrets {
|
|
|
330
334
|
errors.push(ensureErrorAndPrependMessage(error, `Failed to load secret '${secretKey}' from adapter '${adapter.adapterName}'`));
|
|
331
335
|
}
|
|
332
336
|
}
|
|
337
|
+
if (checkValidShape(undefined, shapeRequirement)) {
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
333
340
|
throw combineErrors(errors);
|
|
334
341
|
}
|
|
335
342
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "updating-secrets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Automatically update secrets on an interval with support for seamless secret rotation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"secrets",
|
|
@@ -44,19 +44,19 @@
|
|
|
44
44
|
"test:update": "npm run test update"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@augment-vir/assert": "^31.
|
|
48
|
-
"@augment-vir/common": "^31.
|
|
49
|
-
"date-vir": "^8.
|
|
50
|
-
"object-shape-tester": "^6.
|
|
51
|
-
"type-fest": "^5.
|
|
47
|
+
"@augment-vir/assert": "^31.68.4",
|
|
48
|
+
"@augment-vir/common": "^31.68.4",
|
|
49
|
+
"date-vir": "^8.3.2",
|
|
50
|
+
"object-shape-tester": "^6.12.1",
|
|
51
|
+
"type-fest": "^5.6.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@augment-vir/test": "^31.
|
|
55
|
-
"@types/node": "^
|
|
56
|
-
"c8": "^
|
|
54
|
+
"@augment-vir/test": "^31.68.4",
|
|
55
|
+
"@types/node": "^25.6.0",
|
|
56
|
+
"c8": "^11.0.0",
|
|
57
57
|
"istanbul-smart-text-reporter": "^1.1.5",
|
|
58
|
-
"markdown-code-example-inserter": "^3.0.
|
|
59
|
-
"typedoc": "^0.28.
|
|
58
|
+
"markdown-code-example-inserter": "^3.0.5",
|
|
59
|
+
"typedoc": "^0.28.19",
|
|
60
60
|
"typescript": "^5.9.3"
|
|
61
61
|
},
|
|
62
62
|
"engines": {
|