@red-hat-developer-hub/e2e-test-utils 2.1.13 → 2.1.14
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/playwright/global-setup.d.ts +5 -0
- package/dist/playwright/global-setup.d.ts.map +1 -1
- package/dist/playwright/global-setup.js +2 -4
- package/dist/playwright/helpers/github-session.test.js +1 -1
- package/dist/playwright/tests/global-setup.test.d.ts +2 -0
- package/dist/playwright/tests/global-setup.test.d.ts.map +1 -0
- package/dist/playwright/tests/global-setup.test.js +68 -0
- package/dist/secrets/bitwarden.d.ts +34 -0
- package/dist/secrets/bitwarden.d.ts.map +1 -0
- package/dist/secrets/bitwarden.js +193 -0
- package/dist/secrets/cli.d.ts +11 -0
- package/dist/secrets/cli.d.ts.map +1 -0
- package/dist/secrets/cli.js +98 -0
- package/dist/secrets/command.d.ts +15 -0
- package/dist/secrets/command.d.ts.map +1 -0
- package/dist/secrets/command.js +30 -0
- package/dist/secrets/config.d.ts +33 -0
- package/dist/secrets/config.d.ts.map +1 -0
- package/dist/secrets/config.js +156 -0
- package/dist/secrets/environment.d.ts +10 -0
- package/dist/secrets/environment.d.ts.map +1 -0
- package/dist/secrets/environment.js +53 -0
- package/dist/secrets/exec.d.ts +18 -0
- package/dist/secrets/exec.d.ts.map +1 -0
- package/dist/secrets/exec.js +50 -0
- package/dist/secrets/index.d.ts +5 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +4 -0
- package/dist/secrets/tests/bitwarden.integration.test.d.ts +2 -0
- package/dist/secrets/tests/bitwarden.integration.test.d.ts.map +1 -0
- package/dist/secrets/tests/bitwarden.integration.test.js +50 -0
- package/dist/secrets/tests/bitwarden.test.d.ts +2 -0
- package/dist/secrets/tests/bitwarden.test.d.ts.map +1 -0
- package/dist/secrets/tests/bitwarden.test.js +418 -0
- package/dist/secrets/tests/cli.integration.test.d.ts +2 -0
- package/dist/secrets/tests/cli.integration.test.d.ts.map +1 -0
- package/dist/secrets/tests/cli.integration.test.js +70 -0
- package/dist/secrets/tests/cli.test.d.ts +2 -0
- package/dist/secrets/tests/cli.test.d.ts.map +1 -0
- package/dist/secrets/tests/cli.test.js +42 -0
- package/dist/secrets/tests/config.test.d.ts +2 -0
- package/dist/secrets/tests/config.test.d.ts.map +1 -0
- package/dist/secrets/tests/config.test.js +105 -0
- package/dist/secrets/tests/environment.test.d.ts +2 -0
- package/dist/secrets/tests/environment.test.d.ts.map +1 -0
- package/dist/secrets/tests/environment.test.js +119 -0
- package/dist/secrets/tests/exec.test.d.ts +2 -0
- package/dist/secrets/tests/exec.test.d.ts.map +1 -0
- package/dist/secrets/tests/exec.test.js +87 -0
- package/package.json +9 -2
- package/dist/utils/vault.d.ts +0 -16
- package/dist/utils/vault.d.ts.map +0 -1
- package/dist/utils/vault.js +0 -94
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention, playwright/expect-expect -- node:test fixtures model process environment keys */
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { materializeEnvironment, } from "../environment.js";
|
|
5
|
+
const selector = {
|
|
6
|
+
prefix: "global/",
|
|
7
|
+
optional: false,
|
|
8
|
+
destination: {
|
|
9
|
+
kind: "environment",
|
|
10
|
+
stripPrefix: "global/",
|
|
11
|
+
requirePrefix: "VAULT_",
|
|
12
|
+
keyTransform: "legacy-env",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
test("maps selected notes into a child environment without mutating the parent", () => {
|
|
16
|
+
const parent = {
|
|
17
|
+
PATH: "/usr/bin",
|
|
18
|
+
VAULT_GITHUB_TOKEN: "old-value",
|
|
19
|
+
BW_SESSION: "session-value",
|
|
20
|
+
BW_CLIENTID: "client-id",
|
|
21
|
+
BW_CLIENTSECRET: "client-secret",
|
|
22
|
+
BW_PASSWORD: "password",
|
|
23
|
+
VAULT_TOKEN: "legacy-provider-token",
|
|
24
|
+
VAULT_ADDR: "legacy-provider-address",
|
|
25
|
+
VAULT_BASE_PATH: "legacy-provider-path",
|
|
26
|
+
VAULT: "legacy-provider-setting",
|
|
27
|
+
};
|
|
28
|
+
const secrets = [
|
|
29
|
+
{
|
|
30
|
+
id: "token-id",
|
|
31
|
+
name: "global/VAULT_GITHUB_TOKEN",
|
|
32
|
+
value: "line one\nline two\n",
|
|
33
|
+
selector,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
const child = materializeEnvironment(secrets, [selector], parent);
|
|
37
|
+
assert.equal(child.VAULT_GITHUB_TOKEN, "line one\nline two\n");
|
|
38
|
+
assert.equal(child.PATH, "/usr/bin");
|
|
39
|
+
assert.equal(child.BW_SESSION, undefined);
|
|
40
|
+
assert.equal(child.BW_CLIENTID, undefined);
|
|
41
|
+
assert.equal(child.BW_CLIENTSECRET, undefined);
|
|
42
|
+
assert.equal(child.BW_PASSWORD, undefined);
|
|
43
|
+
assert.equal(child.VAULT_TOKEN, undefined);
|
|
44
|
+
assert.equal(child.VAULT_ADDR, undefined);
|
|
45
|
+
assert.equal(child.VAULT_BASE_PATH, undefined);
|
|
46
|
+
assert.equal(child.VAULT, undefined);
|
|
47
|
+
assert.deepEqual(parent, {
|
|
48
|
+
PATH: "/usr/bin",
|
|
49
|
+
VAULT_GITHUB_TOKEN: "old-value",
|
|
50
|
+
BW_SESSION: "session-value",
|
|
51
|
+
BW_CLIENTID: "client-id",
|
|
52
|
+
BW_CLIENTSECRET: "client-secret",
|
|
53
|
+
BW_PASSWORD: "password",
|
|
54
|
+
VAULT_TOKEN: "legacy-provider-token",
|
|
55
|
+
VAULT_ADDR: "legacy-provider-address",
|
|
56
|
+
VAULT_BASE_PATH: "legacy-provider-path",
|
|
57
|
+
VAULT: "legacy-provider-setting",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
test("filters item names that do not satisfy requirePrefix", () => {
|
|
61
|
+
const secrets = [
|
|
62
|
+
{
|
|
63
|
+
id: "ignored-id",
|
|
64
|
+
name: "global/OTHER_SETTING",
|
|
65
|
+
value: "ignored",
|
|
66
|
+
selector,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
const child = materializeEnvironment(secrets, [selector], {});
|
|
70
|
+
assert.deepEqual(child, {});
|
|
71
|
+
});
|
|
72
|
+
test("rejects transformed environment-name collisions", () => {
|
|
73
|
+
const secrets = [
|
|
74
|
+
{
|
|
75
|
+
id: "first-id",
|
|
76
|
+
name: "global/VAULT_A-B",
|
|
77
|
+
value: "first",
|
|
78
|
+
selector,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "second-id",
|
|
82
|
+
name: "global/VAULT_A.B",
|
|
83
|
+
value: "second",
|
|
84
|
+
selector,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
assert.throws(() => materializeEnvironment(secrets, [selector], {}), /environment variable collision.*VAULT_A_B/i);
|
|
88
|
+
});
|
|
89
|
+
test("rejects invalid identity environment names", () => {
|
|
90
|
+
const identitySelector = {
|
|
91
|
+
...selector,
|
|
92
|
+
destination: {
|
|
93
|
+
...selector.destination,
|
|
94
|
+
requirePrefix: undefined,
|
|
95
|
+
keyTransform: "identity",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
assert.throws(() => materializeEnvironment([
|
|
99
|
+
{
|
|
100
|
+
id: "invalid-id",
|
|
101
|
+
name: "global/NOT-VALID",
|
|
102
|
+
value: "synthetic",
|
|
103
|
+
selector: identitySelector,
|
|
104
|
+
},
|
|
105
|
+
], [identitySelector], {}), /invalid environment variable name/i);
|
|
106
|
+
});
|
|
107
|
+
test("rejects secrets that are not covered by their selector", () => {
|
|
108
|
+
const wrongSelector = {
|
|
109
|
+
...selector,
|
|
110
|
+
prefix: "workspaces/backstage/",
|
|
111
|
+
};
|
|
112
|
+
const secret = {
|
|
113
|
+
id: "wrong-id",
|
|
114
|
+
name: "global/VAULT_TOKEN",
|
|
115
|
+
value: "synthetic",
|
|
116
|
+
selector: wrongSelector,
|
|
117
|
+
};
|
|
118
|
+
assert.throws(() => materializeEnvironment([secret], [selector], {}), /does not match selector/i);
|
|
119
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.test.d.ts","sourceRoot":"","sources":["../../../src/secrets/tests/exec.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention, playwright/expect-expect -- node:test fixtures model process environment keys */
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { executeCommand, runChild } from "../exec.js";
|
|
5
|
+
const profile = {
|
|
6
|
+
schemaVersion: 1,
|
|
7
|
+
collection: "rhdh-plugin-export-overlays",
|
|
8
|
+
selectors: [
|
|
9
|
+
{
|
|
10
|
+
prefix: "global/",
|
|
11
|
+
destination: {
|
|
12
|
+
kind: "environment",
|
|
13
|
+
stripPrefix: "global/",
|
|
14
|
+
requirePrefix: "VAULT_",
|
|
15
|
+
keyTransform: "legacy-env",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
const secret = {
|
|
21
|
+
id: "secret-id",
|
|
22
|
+
name: "global/VAULT_TOKEN",
|
|
23
|
+
value: "synthetic-value",
|
|
24
|
+
selector: {
|
|
25
|
+
...profile.selectors[0],
|
|
26
|
+
optional: false,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
test("executes a command with only selected secrets in its child environment", async () => {
|
|
30
|
+
let received;
|
|
31
|
+
const childRunner = async (command, args, env) => {
|
|
32
|
+
received = { command, args, env };
|
|
33
|
+
return 0;
|
|
34
|
+
};
|
|
35
|
+
const client = { read: async () => [secret] };
|
|
36
|
+
const exitCode = await executeCommand({
|
|
37
|
+
profile,
|
|
38
|
+
workspaces: [],
|
|
39
|
+
command: "playwright",
|
|
40
|
+
args: ["test", "--project", "github"],
|
|
41
|
+
env: {
|
|
42
|
+
PATH: "/usr/bin",
|
|
43
|
+
BW_SESSION: "synthetic-session",
|
|
44
|
+
EXISTING_VALUE: "preserved",
|
|
45
|
+
},
|
|
46
|
+
client,
|
|
47
|
+
childRunner,
|
|
48
|
+
});
|
|
49
|
+
assert.equal(exitCode, 0);
|
|
50
|
+
assert.deepEqual(received?.args, ["test", "--project", "github"]);
|
|
51
|
+
assert.equal(received?.command, "playwright");
|
|
52
|
+
assert.equal(received?.env.VAULT_TOKEN, "synthetic-value");
|
|
53
|
+
assert.equal(received?.env.EXISTING_VALUE, "preserved");
|
|
54
|
+
assert.equal(received?.env.BW_SESSION, undefined);
|
|
55
|
+
});
|
|
56
|
+
test("validates all selected mappings before starting the child", async () => {
|
|
57
|
+
let childStarted = false;
|
|
58
|
+
const childRunner = async () => {
|
|
59
|
+
childStarted = true;
|
|
60
|
+
return 0;
|
|
61
|
+
};
|
|
62
|
+
const client = {
|
|
63
|
+
read: async () => {
|
|
64
|
+
throw new Error("required prefix has no matching item");
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
await assert.rejects(() => executeCommand({
|
|
68
|
+
profile,
|
|
69
|
+
workspaces: [],
|
|
70
|
+
command: "playwright",
|
|
71
|
+
args: [],
|
|
72
|
+
env: { BW_SESSION: "synthetic-session" },
|
|
73
|
+
client,
|
|
74
|
+
childRunner,
|
|
75
|
+
}), /required prefix/i);
|
|
76
|
+
assert.equal(childStarted, false);
|
|
77
|
+
});
|
|
78
|
+
test("propagates the real child exit code", async () => {
|
|
79
|
+
const exitCode = await runChild(process.execPath, ["-e", "process.exit(17)"], {
|
|
80
|
+
...process.env,
|
|
81
|
+
});
|
|
82
|
+
assert.equal(exitCode, 17);
|
|
83
|
+
});
|
|
84
|
+
test("returns the shell-compatible status for a signal-terminated child", async () => {
|
|
85
|
+
const exitCode = await runChild(process.execPath, ["-e", "process.kill(process.pid, 'SIGTERM')"], { ...process.env });
|
|
86
|
+
assert.equal(exitCode, 143);
|
|
87
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@red-hat-developer-hub/e2e-test-utils",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.14",
|
|
4
4
|
"description": "Test utilities for RHDH E2E tests",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -56,8 +56,15 @@
|
|
|
56
56
|
"./openldap": {
|
|
57
57
|
"types": "./dist/deployment/openldap/index.d.ts",
|
|
58
58
|
"default": "./dist/deployment/openldap/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./secrets": {
|
|
61
|
+
"types": "./dist/secrets/index.d.ts",
|
|
62
|
+
"default": "./dist/secrets/index.js"
|
|
59
63
|
}
|
|
60
64
|
},
|
|
65
|
+
"bin": {
|
|
66
|
+
"rhdh-e2e-secrets": "./dist/secrets/cli.js"
|
|
67
|
+
},
|
|
61
68
|
"publishConfig": {
|
|
62
69
|
"access": "public"
|
|
63
70
|
},
|
|
@@ -66,7 +73,7 @@
|
|
|
66
73
|
"tsconfig.base.json"
|
|
67
74
|
],
|
|
68
75
|
"scripts": {
|
|
69
|
-
"build": "yarn clean && tsc -p tsconfig.build.json && cp -r src/deployment/rhdh/config dist/deployment/rhdh/ && cp -r src/deployment/keycloak/config dist/deployment/keycloak/ && cp -r src/deployment/openldap/config dist/deployment/openldap/ && cp src/deployment/orchestrator/install-orchestrator.sh dist/deployment/orchestrator/",
|
|
76
|
+
"build": "yarn clean && tsc -p tsconfig.build.json && cp -r src/deployment/rhdh/config dist/deployment/rhdh/ && cp -r src/deployment/keycloak/config dist/deployment/keycloak/ && cp -r src/deployment/openldap/config dist/deployment/openldap/ && cp src/deployment/orchestrator/install-orchestrator.sh dist/deployment/orchestrator/ && chmod +x dist/secrets/cli.js",
|
|
70
77
|
"prepare": "husky",
|
|
71
78
|
"check": "yarn typecheck && yarn lint:check && yarn prettier:check",
|
|
72
79
|
"clean": "rm -rf dist",
|
package/dist/utils/vault.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loads secrets from HashiCorp Vault into process.env.
|
|
3
|
-
* Only runs when `VAULT=1` or `VAULT=true` is set. Handles OIDC login automatically.
|
|
4
|
-
*
|
|
5
|
-
* Fetches secrets from:
|
|
6
|
-
* - Global path: `<basePath>/global`
|
|
7
|
-
* - Per-workspace paths: `<basePath>/workspaces/<name>`
|
|
8
|
-
*
|
|
9
|
-
* Configure via env vars:
|
|
10
|
-
* - `VAULT_ADDR` — Vault server URL (default: https://vault.ci.openshift.org)
|
|
11
|
-
* - `VAULT_BASE_PATH` — Base path in Vault (default: selfservice/rhdh-plugin-export-overlays)
|
|
12
|
-
*
|
|
13
|
-
* Security: Only key names are logged, never secret values.
|
|
14
|
-
*/
|
|
15
|
-
export declare function loadLocalVaultSecrets(): Promise<void>;
|
|
16
|
-
//# sourceMappingURL=vault.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../src/utils/vault.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiE3D"}
|
package/dist/utils/vault.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { $ } from "./bash.js";
|
|
2
|
-
const VAULT_ADDR_DEFAULT = "https://vault.ci.openshift.org";
|
|
3
|
-
const VAULT_BASE_PATH_DEFAULT = "selfservice/rhdh-plugin-export-overlays";
|
|
4
|
-
/**
|
|
5
|
-
* Loads secrets from HashiCorp Vault into process.env.
|
|
6
|
-
* Only runs when `VAULT=1` or `VAULT=true` is set. Handles OIDC login automatically.
|
|
7
|
-
*
|
|
8
|
-
* Fetches secrets from:
|
|
9
|
-
* - Global path: `<basePath>/global`
|
|
10
|
-
* - Per-workspace paths: `<basePath>/workspaces/<name>`
|
|
11
|
-
*
|
|
12
|
-
* Configure via env vars:
|
|
13
|
-
* - `VAULT_ADDR` — Vault server URL (default: https://vault.ci.openshift.org)
|
|
14
|
-
* - `VAULT_BASE_PATH` — Base path in Vault (default: selfservice/rhdh-plugin-export-overlays)
|
|
15
|
-
*
|
|
16
|
-
* Security: Only key names are logged, never secret values.
|
|
17
|
-
*/
|
|
18
|
-
export async function loadLocalVaultSecrets() {
|
|
19
|
-
if (process.env.VAULT !== "1" && process.env.VAULT !== "true")
|
|
20
|
-
return;
|
|
21
|
-
const vaultAddr = process.env.VAULT_ADDR || VAULT_ADDR_DEFAULT;
|
|
22
|
-
const basePath = process.env.VAULT_BASE_PATH || VAULT_BASE_PATH_DEFAULT;
|
|
23
|
-
process.env.VAULT_ADDR = vaultAddr;
|
|
24
|
-
// Check vault CLI is installed
|
|
25
|
-
const whichResult = await vaultCmd `command -v vault`;
|
|
26
|
-
if (whichResult.exitCode !== 0) {
|
|
27
|
-
throw new Error("vault CLI not found. Install from https://developer.hashicorp.com/vault/downloads");
|
|
28
|
-
}
|
|
29
|
-
// Check if already logged in
|
|
30
|
-
const tokenCheck = await vaultCmd `vault token lookup`;
|
|
31
|
-
if (tokenCheck.exitCode !== 0) {
|
|
32
|
-
console.log("Vault: not logged in, starting OIDC login...");
|
|
33
|
-
// vault login needs inherited stdio for browser-based OIDC flow
|
|
34
|
-
await $ `vault login -no-print -method=oidc`;
|
|
35
|
-
const retryCheck = await vaultCmd `vault token lookup`;
|
|
36
|
-
if (retryCheck.exitCode !== 0) {
|
|
37
|
-
throw new Error("Vault login failed. Run manually:\n export VAULT_ADDR='" +
|
|
38
|
-
vaultAddr +
|
|
39
|
-
"'\n vault login -method=oidc");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
// Check access by fetching global secrets first
|
|
43
|
-
const globalResult = await vaultCmd `vault kv get -format=json -mount=kv ${basePath}/global`;
|
|
44
|
-
if (globalResult.stderr.includes("permission denied")) {
|
|
45
|
-
console.log("Vault: permission denied. Request access in Slack: #rhdh-e2e-tests");
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
console.log("Loading secrets from vault...");
|
|
49
|
-
// Load global secrets
|
|
50
|
-
loadSecretsFromResult(globalResult, "global");
|
|
51
|
-
// List and fetch per-workspace secrets
|
|
52
|
-
const listResult = await vaultCmd `vault kv list -format=json -mount=kv ${basePath}/workspaces`;
|
|
53
|
-
if (listResult.exitCode === 0) {
|
|
54
|
-
const workspaces = JSON.parse(listResult.stdout);
|
|
55
|
-
await Promise.all(workspaces.map((ws) => {
|
|
56
|
-
const name = ws.replace(/\/$/, "");
|
|
57
|
-
return exportSecretsFromPath(`${basePath}/workspaces/${name}`, name);
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
console.log(" No workspace-specific secrets found");
|
|
62
|
-
}
|
|
63
|
-
console.log("Vault secrets loaded successfully.");
|
|
64
|
-
}
|
|
65
|
-
/** Runs a shell command with piped stdio and nothrow, for capturing vault CLI output. */
|
|
66
|
-
const vaultCmd = $({
|
|
67
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
68
|
-
nothrow: true,
|
|
69
|
-
});
|
|
70
|
-
async function exportSecretsFromPath(vaultPath, label) {
|
|
71
|
-
const result = await vaultCmd `vault kv get -format=json -mount=kv ${vaultPath}`;
|
|
72
|
-
loadSecretsFromResult(result, label);
|
|
73
|
-
}
|
|
74
|
-
function loadSecretsFromResult(result, label) {
|
|
75
|
-
if (result.exitCode !== 0) {
|
|
76
|
-
console.log(` No secrets at: ${label}`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const json = JSON.parse(result.stdout);
|
|
80
|
-
const secrets = json?.data?.data;
|
|
81
|
-
if (!secrets) {
|
|
82
|
-
console.log(` No secrets at: ${label}`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
console.log(` From: ${label}`);
|
|
86
|
-
for (const [key, value] of Object.entries(secrets)) {
|
|
87
|
-
if (key.startsWith("secretsync/"))
|
|
88
|
-
continue;
|
|
89
|
-
if (!key.startsWith("VAULT_"))
|
|
90
|
-
continue;
|
|
91
|
-
const safeKey = key.replace(/[.\-/]/g, "_");
|
|
92
|
-
process.env[safeKey] = value;
|
|
93
|
-
}
|
|
94
|
-
}
|