ultraenv 1.0.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/LICENSE +21 -0
- package/README.md +2058 -0
- package/bin/ultraenv.mjs +3 -0
- package/dist/chunk-2USZPWLZ.js +288 -0
- package/dist/chunk-3UV2QNJL.js +270 -0
- package/dist/chunk-3VYXPTYV.js +179 -0
- package/dist/chunk-4XUYMRK5.js +366 -0
- package/dist/chunk-5G2DU52U.js +189 -0
- package/dist/chunk-6KS56D6E.js +172 -0
- package/dist/chunk-AWN6ADV7.js +328 -0
- package/dist/chunk-CHVO6NWI.js +203 -0
- package/dist/chunk-CIFMBJ4H.js +3975 -0
- package/dist/chunk-GC7RXHLA.js +253 -0
- package/dist/chunk-HFXQGJY3.js +445 -0
- package/dist/chunk-IGFVP24Q.js +91 -0
- package/dist/chunk-IKPTKALB.js +78 -0
- package/dist/chunk-JB7RKV3C.js +66 -0
- package/dist/chunk-MNVFG7H4.js +611 -0
- package/dist/chunk-MSXMESFP.js +1910 -0
- package/dist/chunk-N5PAV4NM.js +127 -0
- package/dist/chunk-NBOABPHM.js +158 -0
- package/dist/chunk-OMAOROL4.js +49 -0
- package/dist/chunk-R7PZRSZ7.js +105 -0
- package/dist/chunk-TE7HPLA6.js +73 -0
- package/dist/chunk-TMT5KCO3.js +101 -0
- package/dist/chunk-UEWYFN6A.js +189 -0
- package/dist/chunk-WMHN5RW2.js +128 -0
- package/dist/chunk-XC65ORJ5.js +70 -0
- package/dist/chunk-YMMP4VQL.js +118 -0
- package/dist/chunk-YN2KGTCB.js +33 -0
- package/dist/chunk-YTICOB5M.js +65 -0
- package/dist/chunk-YVWLXFUT.js +107 -0
- package/dist/ci-check-sync-VBMSVWIV.js +48 -0
- package/dist/ci-scan-24MT5XGS.js +41 -0
- package/dist/ci-setup-C2NKEFRD.js +135 -0
- package/dist/ci-validate-7AW24LSQ.js +57 -0
- package/dist/cli/index.cjs +9217 -0
- package/dist/cli/index.d.cts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +339 -0
- package/dist/comparator-RDKX3OI7.js +13 -0
- package/dist/completion-MW35C2XO.js +168 -0
- package/dist/config-O5YRQP5Z.js +13 -0
- package/dist/debug-PTPXAF3K.js +131 -0
- package/dist/declaration-LEME4AFZ.js +10 -0
- package/dist/doctor-FZAUPKHS.js +129 -0
- package/dist/envs-compare-5K3HESX5.js +49 -0
- package/dist/envs-create-2XXHXMGA.js +58 -0
- package/dist/envs-list-NQM5252B.js +59 -0
- package/dist/envs-switch-6L2AQYID.js +50 -0
- package/dist/envs-validate-FL73Q76T.js +89 -0
- package/dist/fs-VH7ATUS3.js +31 -0
- package/dist/generator-LFZBMZZS.js +14 -0
- package/dist/git-BZS4DPAI.js +30 -0
- package/dist/help-3XJBXEHE.js +121 -0
- package/dist/index.cjs +12907 -0
- package/dist/index.d.cts +2562 -0
- package/dist/index.d.ts +2562 -0
- package/dist/index.js +3212 -0
- package/dist/init-Y7JQ2KYJ.js +146 -0
- package/dist/install-hook-SKXIV6NV.js +111 -0
- package/dist/json-schema-I26YNQBH.js +10 -0
- package/dist/key-manager-O3G55WPU.js +25 -0
- package/dist/middleware/express.cjs +103 -0
- package/dist/middleware/express.d.cts +115 -0
- package/dist/middleware/express.d.ts +115 -0
- package/dist/middleware/express.js +8 -0
- package/dist/middleware/fastify.cjs +91 -0
- package/dist/middleware/fastify.d.cts +111 -0
- package/dist/middleware/fastify.d.ts +111 -0
- package/dist/middleware/fastify.js +8 -0
- package/dist/module-IDIZPP4M.js +10 -0
- package/dist/protect-NCWPM6VC.js +161 -0
- package/dist/scan-TRLY36TT.js +58 -0
- package/dist/schema/index.cjs +4074 -0
- package/dist/schema/index.d.cts +1244 -0
- package/dist/schema/index.d.ts +1244 -0
- package/dist/schema/index.js +152 -0
- package/dist/sync-TMHMTLH2.js +186 -0
- package/dist/typegen-SQOSXBWM.js +80 -0
- package/dist/validate-IOAM5HWS.js +100 -0
- package/dist/vault-decrypt-U6HJZNBV.js +111 -0
- package/dist/vault-diff-B3ZOQTWI.js +132 -0
- package/dist/vault-encrypt-GUSLCSKS.js +112 -0
- package/dist/vault-init-GUBOTOUL.js +106 -0
- package/dist/vault-rekey-DAHT7JCN.js +132 -0
- package/dist/vault-status-GDLRU2OK.js +90 -0
- package/dist/vault-verify-CD76FJSF.js +102 -0
- package/package.json +106 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirm,
|
|
3
|
+
prompt
|
|
4
|
+
} from "./chunk-YTICOB5M.js";
|
|
5
|
+
import {
|
|
6
|
+
bold,
|
|
7
|
+
cyan,
|
|
8
|
+
green,
|
|
9
|
+
red
|
|
10
|
+
} from "./chunk-OMAOROL4.js";
|
|
11
|
+
import {
|
|
12
|
+
writeError,
|
|
13
|
+
writeLine
|
|
14
|
+
} from "./chunk-YN2KGTCB.js";
|
|
15
|
+
import {
|
|
16
|
+
exists,
|
|
17
|
+
writeFile
|
|
18
|
+
} from "./chunk-3VYXPTYV.js";
|
|
19
|
+
import {
|
|
20
|
+
DEFAULT_GITIGNORE_ENTRIES
|
|
21
|
+
} from "./chunk-XC65ORJ5.js";
|
|
22
|
+
import {
|
|
23
|
+
FileSystemError,
|
|
24
|
+
getErrorMessage
|
|
25
|
+
} from "./chunk-5G2DU52U.js";
|
|
26
|
+
|
|
27
|
+
// src/cli/commands/init.ts
|
|
28
|
+
import { resolve, join } from "path";
|
|
29
|
+
var DEFAULT_ENV_CONTENT = `# Environment Variables
|
|
30
|
+
# Created by ultraenv \u2014 https://github.com/Avinashvelu03/ultraenv
|
|
31
|
+
|
|
32
|
+
# App
|
|
33
|
+
NODE_ENV=development
|
|
34
|
+
APP_NAME=
|
|
35
|
+
APP_URL=http://localhost:3000
|
|
36
|
+
|
|
37
|
+
# Database
|
|
38
|
+
DATABASE_URL=
|
|
39
|
+
DATABASE_POOL_SIZE=10
|
|
40
|
+
|
|
41
|
+
# Server
|
|
42
|
+
PORT=3000
|
|
43
|
+
HOST=localhost
|
|
44
|
+
|
|
45
|
+
# Logging
|
|
46
|
+
LOG_LEVEL=info
|
|
47
|
+
`;
|
|
48
|
+
async function run(args, ctx) {
|
|
49
|
+
try {
|
|
50
|
+
const cwd = args.flags["--cwd"] ?? ctx.cwd;
|
|
51
|
+
const baseDir = resolve(cwd);
|
|
52
|
+
writeLine(bold("\u{1F680} ultraenv project initialization"));
|
|
53
|
+
writeLine("");
|
|
54
|
+
const configPath = join(baseDir, ".ultraenvrc.json");
|
|
55
|
+
const envPath = join(baseDir, ".env");
|
|
56
|
+
if (await exists(configPath) && !args.flags["--force"]) {
|
|
57
|
+
writeLine(cyan(" An ultraenv configuration already exists."));
|
|
58
|
+
const overwrite = await confirm(" Overwrite existing configuration?", false);
|
|
59
|
+
if (!overwrite) {
|
|
60
|
+
writeLine(green(" Initialization cancelled."));
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await prompt(" Project name", { default: baseDir.split("/").pop() ?? "my-project" });
|
|
65
|
+
const envDir = await prompt(" Environment directory", { default: "." });
|
|
66
|
+
writeLine("");
|
|
67
|
+
writeLine(cyan(" Creating configuration..."));
|
|
68
|
+
const configContent = JSON.stringify({
|
|
69
|
+
envDir,
|
|
70
|
+
expandVariables: true,
|
|
71
|
+
overrideProcessEnv: false,
|
|
72
|
+
mergeStrategy: "last-wins",
|
|
73
|
+
outputFormat: "terminal",
|
|
74
|
+
debug: false
|
|
75
|
+
}, null, 2) + "\n";
|
|
76
|
+
await writeFile(configPath, configContent);
|
|
77
|
+
writeLine(green(` \u2713 Created ${configPath}`));
|
|
78
|
+
if (!await exists(envPath)) {
|
|
79
|
+
writeLine(cyan(" Creating .env file..."));
|
|
80
|
+
await writeFile(envPath, DEFAULT_ENV_CONTENT);
|
|
81
|
+
writeLine(green(` \u2713 Created ${envPath}`));
|
|
82
|
+
} else {
|
|
83
|
+
writeLine(cyan(` .env already exists, skipping.`));
|
|
84
|
+
}
|
|
85
|
+
const examplePath = join(baseDir, ".env.example");
|
|
86
|
+
if (!await exists(examplePath)) {
|
|
87
|
+
const exampleContent = `# Environment Variables Template
|
|
88
|
+
# Auto-generated by ultraenv
|
|
89
|
+
# Copy this file to .env and fill in the values
|
|
90
|
+
|
|
91
|
+
# App
|
|
92
|
+
NODE_ENV=development
|
|
93
|
+
APP_NAME=<your-app-name>
|
|
94
|
+
APP_URL=http://localhost:3000
|
|
95
|
+
|
|
96
|
+
# Database
|
|
97
|
+
DATABASE_URL=<your-database-url>
|
|
98
|
+
DATABASE_POOL_SIZE=10
|
|
99
|
+
|
|
100
|
+
# Server
|
|
101
|
+
PORT=3000
|
|
102
|
+
HOST=localhost
|
|
103
|
+
|
|
104
|
+
# Logging
|
|
105
|
+
LOG_LEVEL=info
|
|
106
|
+
`;
|
|
107
|
+
await writeFile(examplePath, exampleContent);
|
|
108
|
+
writeLine(green(` \u2713 Created ${examplePath}`));
|
|
109
|
+
}
|
|
110
|
+
const gitignorePath = join(baseDir, ".gitignore");
|
|
111
|
+
if (await exists(gitignorePath)) {
|
|
112
|
+
const { readFile } = await import("./fs-VH7ATUS3.js");
|
|
113
|
+
const existingContent = await readFile(gitignorePath);
|
|
114
|
+
if (!existingContent.includes("# ultraenv")) {
|
|
115
|
+
const { appendFileSync } = await import("fs");
|
|
116
|
+
appendFileSync(gitignorePath, "\n" + DEFAULT_GITIGNORE_ENTRIES.join("\n") + "\n");
|
|
117
|
+
writeLine(green(" \u2713 Updated .gitignore"));
|
|
118
|
+
} else {
|
|
119
|
+
writeLine(cyan(" .gitignore already contains ultraenv entries"));
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
const gitignoreContent = DEFAULT_GITIGNORE_ENTRIES.join("\n") + "\n";
|
|
123
|
+
await writeFile(gitignorePath, gitignoreContent);
|
|
124
|
+
writeLine(green(" \u2713 Created .gitignore"));
|
|
125
|
+
}
|
|
126
|
+
writeLine("");
|
|
127
|
+
writeLine(green(bold(" \u2705 ultraenv initialized successfully!")));
|
|
128
|
+
writeLine("");
|
|
129
|
+
writeLine(cyan(" Next steps:"));
|
|
130
|
+
writeLine(` 1. Edit ${envPath} with your environment variables`);
|
|
131
|
+
writeLine(` 2. Edit ${configPath} to customize settings`);
|
|
132
|
+
writeLine(` 3. Run ${cyan("ultraenv validate")} to check your configuration`);
|
|
133
|
+
writeLine("");
|
|
134
|
+
return 0;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof FileSystemError) {
|
|
137
|
+
writeError(red(` Error: ${getErrorMessage(error)}`));
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
writeError(red(` Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
run
|
|
146
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isGitRepository
|
|
3
|
+
} from "./chunk-R7PZRSZ7.js";
|
|
4
|
+
import {
|
|
5
|
+
bold,
|
|
6
|
+
cyan,
|
|
7
|
+
green,
|
|
8
|
+
red,
|
|
9
|
+
yellow
|
|
10
|
+
} from "./chunk-OMAOROL4.js";
|
|
11
|
+
import {
|
|
12
|
+
writeError,
|
|
13
|
+
writeLine
|
|
14
|
+
} from "./chunk-YN2KGTCB.js";
|
|
15
|
+
import {
|
|
16
|
+
ensureDir,
|
|
17
|
+
exists,
|
|
18
|
+
writeFile
|
|
19
|
+
} from "./chunk-3VYXPTYV.js";
|
|
20
|
+
import "./chunk-5G2DU52U.js";
|
|
21
|
+
|
|
22
|
+
// src/cli/commands/install-hook.ts
|
|
23
|
+
import { resolve, join } from "path";
|
|
24
|
+
var HOOK_SCRIPT = `#!/bin/sh
|
|
25
|
+
# ultraenv pre-commit hook
|
|
26
|
+
# Validates environment, checks sync, and scans for secrets
|
|
27
|
+
|
|
28
|
+
echo "Running ultraenv pre-commit checks..."
|
|
29
|
+
|
|
30
|
+
# Check if ultraenv is available
|
|
31
|
+
if ! command -v ultraenv >/dev/null 2>&1; then
|
|
32
|
+
echo "Warning: ultraenv not found. Skipping pre-commit checks."
|
|
33
|
+
echo "Install with: npm install -g ultraenv"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Validate environment
|
|
38
|
+
echo " \u2192 Validating environment..."
|
|
39
|
+
ultraenv ci validate --format json --strict
|
|
40
|
+
if [ $? -ne 0 ]; then
|
|
41
|
+
echo "\u2717 Environment validation failed"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Check sync
|
|
46
|
+
echo " \u2192 Checking .env sync..."
|
|
47
|
+
ultraenv ci check-sync
|
|
48
|
+
if [ $? -ne 0 ]; then
|
|
49
|
+
echo "\u26A0 .env.example is out of sync (non-blocking)"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Scan for secrets
|
|
53
|
+
echo " \u2192 Scanning for secrets..."
|
|
54
|
+
ultraenv ci scan --format sarif --output .ultraenv-scan.sarif
|
|
55
|
+
if [ $? -ne 0 ]; then
|
|
56
|
+
echo "\u2717 Secret scan detected potential secrets!"
|
|
57
|
+
echo " Review the findings and remove any committed secrets."
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
echo "\u2713 All pre-commit checks passed"
|
|
62
|
+
exit 0
|
|
63
|
+
`;
|
|
64
|
+
async function run(args, ctx) {
|
|
65
|
+
try {
|
|
66
|
+
const cwd = args.flags["--cwd"] ?? ctx.cwd;
|
|
67
|
+
const baseDir = resolve(cwd);
|
|
68
|
+
const isGit = await isGitRepository(baseDir);
|
|
69
|
+
if (!isGit) {
|
|
70
|
+
writeError(red(" Not a git repository. Cannot install hooks."));
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
const { getGitRoot } = await import("./git-BZS4DPAI.js");
|
|
74
|
+
const gitRoot = await getGitRoot(baseDir);
|
|
75
|
+
if (!gitRoot) {
|
|
76
|
+
writeError(red(" Could not determine git root."));
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
const hooksDir = join(gitRoot, ".git", "hooks");
|
|
80
|
+
const hookPath = join(hooksDir, "pre-commit");
|
|
81
|
+
if (await exists(hookPath) && !args.flags["--force"]) {
|
|
82
|
+
writeLine(yellow(" A pre-commit hook already exists."));
|
|
83
|
+
writeLine(yellow(` Path: ${hookPath}`));
|
|
84
|
+
writeLine(yellow(" Use --force to overwrite."));
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
await ensureDir(hooksDir);
|
|
88
|
+
await writeFile(hookPath, HOOK_SCRIPT);
|
|
89
|
+
const { chmodSync } = await import("fs");
|
|
90
|
+
chmodSync(hookPath, 493);
|
|
91
|
+
writeLine(green(bold(" \u2705 Pre-commit hook installed!")));
|
|
92
|
+
writeLine("");
|
|
93
|
+
writeLine(cyan(` Hook path: ${hookPath}`));
|
|
94
|
+
writeLine("");
|
|
95
|
+
writeLine(cyan(" The hook will run on every commit and:"));
|
|
96
|
+
writeLine(" \u2713 Validate environment variables");
|
|
97
|
+
writeLine(" \u2713 Check .env \u2194 .env.example sync");
|
|
98
|
+
writeLine(" \u2713 Scan for leaked secrets");
|
|
99
|
+
writeLine("");
|
|
100
|
+
writeLine(yellow(" Bypass with: git commit --no-verify"));
|
|
101
|
+
writeLine("");
|
|
102
|
+
return 0;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
105
|
+
writeError(red(` Error: ${msg}`));
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
run
|
|
111
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveEnvironmentKey,
|
|
3
|
+
formatKey,
|
|
4
|
+
generateKeysFile,
|
|
5
|
+
generateMasterKey,
|
|
6
|
+
isValidKeyFormat,
|
|
7
|
+
maskKey,
|
|
8
|
+
parseKey,
|
|
9
|
+
parseKeysFile,
|
|
10
|
+
rotateKey
|
|
11
|
+
} from "./chunk-UEWYFN6A.js";
|
|
12
|
+
import "./chunk-2USZPWLZ.js";
|
|
13
|
+
import "./chunk-XC65ORJ5.js";
|
|
14
|
+
import "./chunk-5G2DU52U.js";
|
|
15
|
+
export {
|
|
16
|
+
deriveEnvironmentKey,
|
|
17
|
+
formatKey,
|
|
18
|
+
generateKeysFile,
|
|
19
|
+
generateMasterKey,
|
|
20
|
+
isValidKeyFormat,
|
|
21
|
+
maskKey,
|
|
22
|
+
parseKey,
|
|
23
|
+
parseKeysFile,
|
|
24
|
+
rotateKey
|
|
25
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middleware/express.ts
|
|
21
|
+
var express_exports = {};
|
|
22
|
+
__export(express_exports, {
|
|
23
|
+
healthCheckRoute: () => healthCheckRoute,
|
|
24
|
+
ultraenvMiddleware: () => ultraenvMiddleware
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(express_exports);
|
|
27
|
+
function ultraenvMiddleware(options = {}) {
|
|
28
|
+
const {
|
|
29
|
+
exposePublic = true,
|
|
30
|
+
prefix = "PUBLIC_",
|
|
31
|
+
additionalPrefixes = [],
|
|
32
|
+
allowList = [],
|
|
33
|
+
denyList = [],
|
|
34
|
+
exposeNodeEnv = true,
|
|
35
|
+
source = process.env
|
|
36
|
+
} = options;
|
|
37
|
+
const allPrefixes = [prefix];
|
|
38
|
+
for (const p of additionalPrefixes) {
|
|
39
|
+
allPrefixes.push(p);
|
|
40
|
+
}
|
|
41
|
+
const allowSet = new Set(allowList.map((k) => k.toUpperCase()));
|
|
42
|
+
const denySet = new Set(denyList.map((k) => k.toUpperCase()));
|
|
43
|
+
const filteredEnv = buildFilteredEnv(
|
|
44
|
+
source,
|
|
45
|
+
allPrefixes,
|
|
46
|
+
allowSet,
|
|
47
|
+
denySet,
|
|
48
|
+
exposePublic,
|
|
49
|
+
exposeNodeEnv
|
|
50
|
+
);
|
|
51
|
+
return (_req, _res, next) => {
|
|
52
|
+
_req.env = filteredEnv;
|
|
53
|
+
next();
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function buildFilteredEnv(source, prefixes, allowSet, denySet, exposePublic, exposeNodeEnv) {
|
|
57
|
+
if (!exposePublic) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
const env = {};
|
|
61
|
+
for (const [key, value] of Object.entries(source)) {
|
|
62
|
+
if (value === void 0 || value === "") continue;
|
|
63
|
+
const upperKey = key.toUpperCase();
|
|
64
|
+
if (denySet.has(upperKey)) continue;
|
|
65
|
+
if (allowSet.has(upperKey)) {
|
|
66
|
+
env[key] = value;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (exposeNodeEnv && upperKey === "NODE_ENV") {
|
|
70
|
+
env[key] = value;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const p of prefixes) {
|
|
74
|
+
if (upperKey.startsWith(p.toUpperCase())) {
|
|
75
|
+
env[key] = value;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return env;
|
|
81
|
+
}
|
|
82
|
+
function healthCheckRoute(options = {}) {
|
|
83
|
+
const { source = process.env, metadata = {} } = options;
|
|
84
|
+
return (_req, res) => {
|
|
85
|
+
const envKeys = Object.keys(source).filter(
|
|
86
|
+
(k) => source[k] !== void 0 && source[k] !== ""
|
|
87
|
+
);
|
|
88
|
+
const nodeEnv = source["NODE_ENV"] ?? "unknown";
|
|
89
|
+
const response = {
|
|
90
|
+
status: "ok",
|
|
91
|
+
loaded: envKeys.length,
|
|
92
|
+
environment: nodeEnv,
|
|
93
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
94
|
+
...metadata
|
|
95
|
+
};
|
|
96
|
+
res.status(200).json(response);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
100
|
+
0 && (module.exports = {
|
|
101
|
+
healthCheckRoute,
|
|
102
|
+
ultraenvMiddleware
|
|
103
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** Minimal Express Request type */
|
|
2
|
+
interface UltraenvRequest {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
env?: UltraenvFilteredEnv;
|
|
5
|
+
}
|
|
6
|
+
/** Minimal Express Response type */
|
|
7
|
+
interface UltraenvResponse {
|
|
8
|
+
status(code: number): UltraenvResponse;
|
|
9
|
+
json(data: Record<string, unknown>): void;
|
|
10
|
+
}
|
|
11
|
+
/** Express NextFunction type */
|
|
12
|
+
type UltraenvNextFunction = (err?: Error) => void;
|
|
13
|
+
/** Options for the ultraenv Express middleware */
|
|
14
|
+
interface UltraenvMiddlewareOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Whether to expose public env vars.
|
|
17
|
+
* When true, filters process.env by the specified prefix and attaches
|
|
18
|
+
* a filtered subset to req.env.
|
|
19
|
+
* Default: true
|
|
20
|
+
*/
|
|
21
|
+
exposePublic?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Prefix to filter public variables by.
|
|
24
|
+
* Only env vars starting with this prefix will be included in req.env.
|
|
25
|
+
* Default: 'PUBLIC_'
|
|
26
|
+
*/
|
|
27
|
+
prefix?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Additional prefixes to include in the filtered env.
|
|
30
|
+
* Useful for framework-specific prefixes (e.g., ['NEXT_PUBLIC_', 'VITE_']).
|
|
31
|
+
* Default: []
|
|
32
|
+
*/
|
|
33
|
+
additionalPrefixes?: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Explicit list of env var names to always include.
|
|
36
|
+
* These are included regardless of prefix matching.
|
|
37
|
+
* Default: []
|
|
38
|
+
*/
|
|
39
|
+
allowList?: readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* Explicit list of env var names to always exclude.
|
|
42
|
+
* Even if they match the prefix, they will be excluded.
|
|
43
|
+
* Default: []
|
|
44
|
+
*/
|
|
45
|
+
denyList?: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Whether to also set NODE_ENV on req.env.
|
|
48
|
+
* Default: true
|
|
49
|
+
*/
|
|
50
|
+
exposeNodeEnv?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Custom source for environment variables.
|
|
53
|
+
* Defaults to process.env if not provided.
|
|
54
|
+
*/
|
|
55
|
+
source?: Record<string, string | undefined>;
|
|
56
|
+
}
|
|
57
|
+
/** The filtered env object attached to req.env */
|
|
58
|
+
interface UltraenvFilteredEnv {
|
|
59
|
+
/** The filtered environment variables */
|
|
60
|
+
[key: string]: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create an Express/Connect middleware that filters environment variables
|
|
64
|
+
* by prefix and attaches them to `req.env`.
|
|
65
|
+
*
|
|
66
|
+
* Only env vars matching the specified prefix(es) are included,
|
|
67
|
+
* preventing accidental exposure of secrets to client-side code.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Middleware configuration options
|
|
70
|
+
* @returns Express middleware function
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import express from 'express';
|
|
75
|
+
* import { ultraenvMiddleware } from 'ultraenv/middleware';
|
|
76
|
+
*
|
|
77
|
+
* const app = express();
|
|
78
|
+
* app.use(ultraenvMiddleware({
|
|
79
|
+
* exposePublic: true,
|
|
80
|
+
* prefix: 'PUBLIC_',
|
|
81
|
+
* additionalPrefixes: ['NEXT_PUBLIC_', 'VITE_'],
|
|
82
|
+
* }));
|
|
83
|
+
*
|
|
84
|
+
* // Now in routes:
|
|
85
|
+
* app.get('/api/config', (req, res) => {
|
|
86
|
+
* res.json({
|
|
87
|
+
* apiUrl: req.env?.PUBLIC_API_URL,
|
|
88
|
+
* siteUrl: req.env?.PUBLIC_SITE_URL,
|
|
89
|
+
* });
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function ultraenvMiddleware(options?: UltraenvMiddlewareOptions): (req: UltraenvRequest, res: UltraenvResponse, next: UltraenvNextFunction) => void;
|
|
94
|
+
/**
|
|
95
|
+
* Create a health check route handler that returns env health status.
|
|
96
|
+
* Does NOT expose secret values — only counts and metadata.
|
|
97
|
+
*
|
|
98
|
+
* @param options - Health check options
|
|
99
|
+
* @returns Express route handler
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* import { healthCheckRoute } from 'ultraenv/middleware';
|
|
104
|
+
* app.get('/health/env', healthCheckRoute());
|
|
105
|
+
* // Response: { status: 'ok', loaded: 38, environment: 'production', timestamp: '...' }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function healthCheckRoute(options?: {
|
|
109
|
+
/** Custom env source. Defaults to process.env */
|
|
110
|
+
source?: Record<string, string | undefined>;
|
|
111
|
+
/** Additional metadata to include */
|
|
112
|
+
metadata?: Record<string, string | number | boolean>;
|
|
113
|
+
}): (req: UltraenvRequest, res: UltraenvResponse) => void;
|
|
114
|
+
|
|
115
|
+
export { type UltraenvFilteredEnv, type UltraenvMiddlewareOptions, type UltraenvNextFunction, type UltraenvRequest, type UltraenvResponse, healthCheckRoute, ultraenvMiddleware };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** Minimal Express Request type */
|
|
2
|
+
interface UltraenvRequest {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
env?: UltraenvFilteredEnv;
|
|
5
|
+
}
|
|
6
|
+
/** Minimal Express Response type */
|
|
7
|
+
interface UltraenvResponse {
|
|
8
|
+
status(code: number): UltraenvResponse;
|
|
9
|
+
json(data: Record<string, unknown>): void;
|
|
10
|
+
}
|
|
11
|
+
/** Express NextFunction type */
|
|
12
|
+
type UltraenvNextFunction = (err?: Error) => void;
|
|
13
|
+
/** Options for the ultraenv Express middleware */
|
|
14
|
+
interface UltraenvMiddlewareOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Whether to expose public env vars.
|
|
17
|
+
* When true, filters process.env by the specified prefix and attaches
|
|
18
|
+
* a filtered subset to req.env.
|
|
19
|
+
* Default: true
|
|
20
|
+
*/
|
|
21
|
+
exposePublic?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Prefix to filter public variables by.
|
|
24
|
+
* Only env vars starting with this prefix will be included in req.env.
|
|
25
|
+
* Default: 'PUBLIC_'
|
|
26
|
+
*/
|
|
27
|
+
prefix?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Additional prefixes to include in the filtered env.
|
|
30
|
+
* Useful for framework-specific prefixes (e.g., ['NEXT_PUBLIC_', 'VITE_']).
|
|
31
|
+
* Default: []
|
|
32
|
+
*/
|
|
33
|
+
additionalPrefixes?: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Explicit list of env var names to always include.
|
|
36
|
+
* These are included regardless of prefix matching.
|
|
37
|
+
* Default: []
|
|
38
|
+
*/
|
|
39
|
+
allowList?: readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* Explicit list of env var names to always exclude.
|
|
42
|
+
* Even if they match the prefix, they will be excluded.
|
|
43
|
+
* Default: []
|
|
44
|
+
*/
|
|
45
|
+
denyList?: readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Whether to also set NODE_ENV on req.env.
|
|
48
|
+
* Default: true
|
|
49
|
+
*/
|
|
50
|
+
exposeNodeEnv?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Custom source for environment variables.
|
|
53
|
+
* Defaults to process.env if not provided.
|
|
54
|
+
*/
|
|
55
|
+
source?: Record<string, string | undefined>;
|
|
56
|
+
}
|
|
57
|
+
/** The filtered env object attached to req.env */
|
|
58
|
+
interface UltraenvFilteredEnv {
|
|
59
|
+
/** The filtered environment variables */
|
|
60
|
+
[key: string]: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create an Express/Connect middleware that filters environment variables
|
|
64
|
+
* by prefix and attaches them to `req.env`.
|
|
65
|
+
*
|
|
66
|
+
* Only env vars matching the specified prefix(es) are included,
|
|
67
|
+
* preventing accidental exposure of secrets to client-side code.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Middleware configuration options
|
|
70
|
+
* @returns Express middleware function
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import express from 'express';
|
|
75
|
+
* import { ultraenvMiddleware } from 'ultraenv/middleware';
|
|
76
|
+
*
|
|
77
|
+
* const app = express();
|
|
78
|
+
* app.use(ultraenvMiddleware({
|
|
79
|
+
* exposePublic: true,
|
|
80
|
+
* prefix: 'PUBLIC_',
|
|
81
|
+
* additionalPrefixes: ['NEXT_PUBLIC_', 'VITE_'],
|
|
82
|
+
* }));
|
|
83
|
+
*
|
|
84
|
+
* // Now in routes:
|
|
85
|
+
* app.get('/api/config', (req, res) => {
|
|
86
|
+
* res.json({
|
|
87
|
+
* apiUrl: req.env?.PUBLIC_API_URL,
|
|
88
|
+
* siteUrl: req.env?.PUBLIC_SITE_URL,
|
|
89
|
+
* });
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function ultraenvMiddleware(options?: UltraenvMiddlewareOptions): (req: UltraenvRequest, res: UltraenvResponse, next: UltraenvNextFunction) => void;
|
|
94
|
+
/**
|
|
95
|
+
* Create a health check route handler that returns env health status.
|
|
96
|
+
* Does NOT expose secret values — only counts and metadata.
|
|
97
|
+
*
|
|
98
|
+
* @param options - Health check options
|
|
99
|
+
* @returns Express route handler
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* import { healthCheckRoute } from 'ultraenv/middleware';
|
|
104
|
+
* app.get('/health/env', healthCheckRoute());
|
|
105
|
+
* // Response: { status: 'ok', loaded: 38, environment: 'production', timestamp: '...' }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function healthCheckRoute(options?: {
|
|
109
|
+
/** Custom env source. Defaults to process.env */
|
|
110
|
+
source?: Record<string, string | undefined>;
|
|
111
|
+
/** Additional metadata to include */
|
|
112
|
+
metadata?: Record<string, string | number | boolean>;
|
|
113
|
+
}): (req: UltraenvRequest, res: UltraenvResponse) => void;
|
|
114
|
+
|
|
115
|
+
export { type UltraenvFilteredEnv, type UltraenvMiddlewareOptions, type UltraenvNextFunction, type UltraenvRequest, type UltraenvResponse, healthCheckRoute, ultraenvMiddleware };
|