updating-secrets 0.0.1 → 0.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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Automatically update secrets on an interval with support for seamless secret rotation.
4
4
 
5
+ Reference docs: https://electrovir.github.io/updating-secrets
6
+
5
7
  ## Install
6
8
 
7
9
  ```sh
@@ -1,4 +1,34 @@
1
+ import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
2
+ import type { SecretDefinitions, SecretValues } from '../secrets-definition/define-secrets.js';
1
3
  import { BaseSecretsAdapter } from './base.adapter.js';
4
+ /**
5
+ * Options for {@link SecretsJsonFileAdapter}.
6
+ *
7
+ * @category Internal
8
+ */
9
+ export type SecretsJsonFileAdapterOptions<Secrets extends SecretDefinitions = any> = {
10
+ /**
11
+ * Optional override for Node.js's `fs` for mocking, testing, or other purposes.
12
+ *
13
+ * @default import * as fs from 'node:fs';
14
+ */
15
+ fsOverride: {
16
+ /** `'node:fs/promises'` */
17
+ promises: {
18
+ /** `readFile` from `'node:fs/promises'` */
19
+ readFile: (filePath: string) => Promise<string | Buffer>;
20
+ /** `writeFile` from `'node:fs/promises'` */
21
+ writeFile: (filePath: string, contents: string | Buffer) => Promise<void>;
22
+ };
23
+ /** `existsSync` from `'node:fs'` */
24
+ existsSync: (filePath: string) => boolean;
25
+ };
26
+ /**
27
+ * Optional function that will automatically generate and save new secrets if the JSON file is
28
+ * missing. This is particularly useful for dev or testing environments.
29
+ */
30
+ generateValues: (() => MaybePromise<SecretValues<Secrets>>) | undefined;
31
+ };
2
32
  /**
3
33
  * Loads all secrets from a single JSON file. This should rarely be used in production environments.
4
34
  * Make sure that your secrets JSON file is not committed to your source code.
@@ -9,16 +39,13 @@ import { BaseSecretsAdapter } from './base.adapter.js';
9
39
  *
10
40
  * @category Adapters
11
41
  */
12
- export declare class SecretsJsonFileAdapter extends BaseSecretsAdapter {
42
+ export declare class SecretsJsonFileAdapter<const Secrets extends SecretDefinitions = any> extends BaseSecretsAdapter {
13
43
  /** Path to the JSON */
14
44
  protected readonly jsonFilePath: string;
15
- /** Optional override for `fs.promises.readFile` for mocking, testing, or other purposes. */
16
- protected readonly readFileOverride: (filePath: string) => Promise<string | Buffer>;
45
+ protected readonly options: SecretsJsonFileAdapterOptions;
17
46
  constructor(
18
47
  /** Path to the JSON */
19
- jsonFilePath: string,
20
- /** Optional override for `fs.promises.readFile` for mocking, testing, or other purposes. */
21
- readFileOverride?: (filePath: string) => Promise<string | Buffer>);
48
+ jsonFilePath: string, options?: PartialWithUndefined<SecretsJsonFileAdapterOptions<Secrets>>);
22
49
  /** Loads secrets from the given JSON file path. */
23
50
  loadSecrets(): Promise<any>;
24
51
  }
@@ -1,6 +1,17 @@
1
- import { parseWithJson5 } from '@augment-vir/common';
2
- import { readFile as readFileImport } from 'node:fs/promises';
1
+ import { mergeDefinedProperties, parseWithJson5, } from '@augment-vir/common';
2
+ import { existsSync as existsSyncImport } from 'node:fs';
3
+ import { readFile as readFileImport, writeFile as writeFileImport } from 'node:fs/promises';
3
4
  import { BaseSecretsAdapter } from './base.adapter.js';
5
+ const defaultSecretsJsonFileAdapterOptions = {
6
+ fsOverride: {
7
+ existsSync: existsSyncImport,
8
+ promises: {
9
+ readFile: readFileImport,
10
+ writeFile: writeFileImport,
11
+ },
12
+ },
13
+ generateValues: undefined,
14
+ };
4
15
  /**
5
16
  * Loads all secrets from a single JSON file. This should rarely be used in production environments.
6
17
  * Make sure that your secrets JSON file is not committed to your source code.
@@ -13,19 +24,26 @@ import { BaseSecretsAdapter } from './base.adapter.js';
13
24
  */
14
25
  export class SecretsJsonFileAdapter extends BaseSecretsAdapter {
15
26
  jsonFilePath;
16
- readFileOverride;
27
+ options;
17
28
  constructor(
18
29
  /** Path to the JSON */
19
- jsonFilePath,
20
- /** Optional override for `fs.promises.readFile` for mocking, testing, or other purposes. */
21
- readFileOverride = readFileImport) {
30
+ jsonFilePath, options = {}) {
22
31
  super('SecretsJsonFileAdapter');
23
32
  this.jsonFilePath = jsonFilePath;
24
- this.readFileOverride = readFileOverride;
33
+ this.options = mergeDefinedProperties(defaultSecretsJsonFileAdapterOptions, options);
25
34
  }
26
35
  /** Loads secrets from the given JSON file path. */
27
36
  async loadSecrets() {
28
- const fileContents = String(await this.readFileOverride(this.jsonFilePath));
37
+ if (!this.options.fsOverride.existsSync(this.jsonFilePath)) {
38
+ if (this.options.generateValues) {
39
+ const newSecrets = await this.options.generateValues();
40
+ await this.options.fsOverride.promises.writeFile(this.jsonFilePath, JSON.stringify(newSecrets));
41
+ }
42
+ else {
43
+ throw new Error(`Missing secrets JSON file at '${this.jsonFilePath}'`);
44
+ }
45
+ }
46
+ const fileContents = String(await this.options.fsOverride.promises.readFile(this.jsonFilePath));
29
47
  return parseWithJson5(fileContents);
30
48
  }
31
49
  }
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export * from './adapters/base.adapter.js';
4
4
  export * from './adapters/secrets-json-file.adapter.js';
5
5
  export * from './adapters/static-secrets.adapter.js';
6
6
  export * from './public-mocks/mock-aws-secrets-manager.js';
7
- export * from './public-mocks/mock-read-file.js';
7
+ export * from './public-mocks/mock-fs.js';
8
8
  export * from './secret-load.error.js';
9
9
  export * from './secrets-definition/define-secrets.js';
10
10
  export * from './updating-secrets.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export * from './adapters/base.adapter.js';
4
4
  export * from './adapters/secrets-json-file.adapter.js';
5
5
  export * from './adapters/static-secrets.adapter.js';
6
6
  export * from './public-mocks/mock-aws-secrets-manager.js';
7
- export * from './public-mocks/mock-read-file.js';
7
+ export * from './public-mocks/mock-fs.js';
8
8
  export * from './secret-load.error.js';
9
9
  export * from './secrets-definition/define-secrets.js';
10
10
  export * from './updating-secrets.js';
@@ -0,0 +1,28 @@
1
+ import type { MaybePromise } from '@augment-vir/common';
2
+ import type { RequireExactlyOne } from 'type-fest';
3
+ import { SecretsJsonFileAdapterOptions } from '../adapters/secrets-json-file.adapter.js';
4
+ /**
5
+ * Mock data for {@link createMockFs}. There are two possible options contained herein, but only one
6
+ * may be used at a time.
7
+ *
8
+ * @category Internal
9
+ */
10
+ export type FileMocks = RequireExactlyOne<{
11
+ /**
12
+ * This option specifies multiple files that can be read from the mocked `readFile` output of
13
+ * {@link createMockFs}. The keys of this object must _exactly_ match the file paths passed to
14
+ * the mock `readFile` output of {@link createMockFs}.
15
+ */
16
+ paths: {
17
+ [FilePath in string]: string | Buffer;
18
+ };
19
+ /** This option specifies the exact same output for any file path. */
20
+ contents: string | Buffer;
21
+ }>;
22
+ /**
23
+ * Mocks the built-in Node.js `fs.promises.readFile` method. See {@link FileMocks} for details on
24
+ * mocking strategies.
25
+ *
26
+ * @category Mocks
27
+ */
28
+ export declare function createMockFs(mockFiles: FileMocks, writeFileMockCallback?: ((filePath: string, contents: string | Buffer) => MaybePromise<void>) | undefined): SecretsJsonFileAdapterOptions['fsOverride'];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Mocks the built-in Node.js `fs.promises.readFile` method. See {@link FileMocks} for details on
3
+ * mocking strategies.
4
+ *
5
+ * @category Mocks
6
+ */
7
+ export function createMockFs(mockFiles, writeFileMockCallback) {
8
+ const writtenFiles = {};
9
+ return {
10
+ promises: {
11
+ readFile(filePath) {
12
+ if ('contents' in mockFiles) {
13
+ return Promise.resolve(mockFiles.contents);
14
+ }
15
+ const contents = {
16
+ ...mockFiles.paths,
17
+ ...writtenFiles,
18
+ }[filePath];
19
+ if (contents == undefined) {
20
+ const error = new Error(`ENOENT: no such file or directory, open '${filePath}'`);
21
+ error.code = 'ENOENT';
22
+ error.errno = -2;
23
+ error.syscall = 'open';
24
+ error.path = filePath;
25
+ throw error;
26
+ }
27
+ else {
28
+ return Promise.resolve(contents);
29
+ }
30
+ },
31
+ async writeFile(filePath, contents) {
32
+ if (writeFileMockCallback) {
33
+ await writeFileMockCallback(filePath, contents);
34
+ }
35
+ writtenFiles[filePath] = contents;
36
+ },
37
+ },
38
+ existsSync(filePath) {
39
+ if ('contents' in mockFiles) {
40
+ return true;
41
+ }
42
+ else {
43
+ return filePath in mockFiles.paths;
44
+ }
45
+ },
46
+ };
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "updating-secrets",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Automatically update secrets on an interval with support for seamless secret rotation.",
5
5
  "keywords": [
6
6
  "secrets",
@@ -1,26 +0,0 @@
1
- import type { RequireExactlyOne } from 'type-fest';
2
- /**
3
- * Mock data for {@link createMockReadFile}. There are two possible options contained herein, but
4
- * only one may be used at a time.
5
- *
6
- * @category Internal
7
- */
8
- export type ReadFileMocks = RequireExactlyOne<{
9
- /**
10
- * This option specifies multiple files that can be read from the mocked `readFile` output of
11
- * {@link createMockReadFile}. The keys of this object must _exactly_ match the file paths passed
12
- * to the mock `readFile` output of {@link createMockReadFile}.
13
- */
14
- paths: {
15
- [FilePath in string]: string | Buffer;
16
- };
17
- /** This option specifies the exact same output for any file path. */
18
- contents: string | Buffer;
19
- }>;
20
- /**
21
- * Mocks the built-in Node.js `fs.promises.readFile` method. See {@link ReadFileMocks} for details on
22
- * mocking strategies.
23
- *
24
- * @category Mocks
25
- */
26
- export declare function createMockReadFile(mock: ReadFileMocks): (path: string) => Promise<string | Buffer>;
@@ -1,25 +0,0 @@
1
- /**
2
- * Mocks the built-in Node.js `fs.promises.readFile` method. See {@link ReadFileMocks} for details on
3
- * mocking strategies.
4
- *
5
- * @category Mocks
6
- */
7
- export function createMockReadFile(mock) {
8
- return (filePath) => {
9
- if ('contents' in mock) {
10
- return Promise.resolve(mock.contents);
11
- }
12
- const contents = mock.paths[filePath];
13
- if (contents == undefined) {
14
- const err = new Error(`ENOENT: no such file or directory, open '${filePath}'`);
15
- err.code = 'ENOENT';
16
- err.errno = -2;
17
- err.syscall = 'open';
18
- err.path = filePath;
19
- throw err;
20
- }
21
- else {
22
- return Promise.resolve(contents);
23
- }
24
- };
25
- }