@dotenvage/node 0.1.5
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 +337 -0
- package/bin/dotenvage-next.js +18 -0
- package/bin/dotenvage.js +265 -0
- package/dotenvage-napi.darwin-arm64.node +0 -0
- package/dotenvage-napi.darwin-x64.node +0 -0
- package/dotenvage-napi.linux-arm64-musl.node +0 -0
- package/examples/app-config.ts +129 -0
- package/examples/auto-encrypt.ts +132 -0
- package/examples/basic.js +127 -0
- package/examples/encrypt-decrypt.ts +56 -0
- package/examples/load-env.ts +51 -0
- package/examples/nextjs-config.ts +132 -0
- package/examples/tsconfig.json +22 -0
- package/index.d.ts +57 -0
- package/index.js +582 -0
- package/nextjs/README.md +271 -0
- package/nextjs/config.d.ts +32 -0
- package/nextjs/config.mjs +59 -0
- package/nextjs/loader.d.ts +22 -0
- package/nextjs/loader.mjs +117 -0
- package/nextjs/preinit.d.ts +13 -0
- package/nextjs/preinit.mjs +106 -0
- package/nextjs/wrapper.mjs +87 -0
- package/package.json +91 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript example: Type-safe application configuration
|
|
3
|
+
*
|
|
4
|
+
* This shows how to use dotenvage with TypeScript for type-safe
|
|
5
|
+
* configuration management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as dotenvage from "../index.js";
|
|
9
|
+
|
|
10
|
+
// Define your application configuration schema
|
|
11
|
+
interface AppConfig {
|
|
12
|
+
readonly apiKey: string;
|
|
13
|
+
readonly databaseUrl: string;
|
|
14
|
+
readonly port: number;
|
|
15
|
+
readonly nodeEnv: "development" | "production" | "test";
|
|
16
|
+
readonly logLevel: "debug" | "info" | "warn" | "error";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Type-safe config loader
|
|
20
|
+
class ConfigLoader {
|
|
21
|
+
private loader: dotenvage.JsEnvLoader;
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.loader = dotenvage.JsEnvLoaderNew();
|
|
25
|
+
this.loader.load(); // Load into process.env
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load and validate configuration
|
|
30
|
+
*/
|
|
31
|
+
loadConfig(): AppConfig {
|
|
32
|
+
const raw = this.loader.getAllVariables();
|
|
33
|
+
|
|
34
|
+
// Validate and transform
|
|
35
|
+
const apiKey = this.getRequired("API_KEY", raw);
|
|
36
|
+
const databaseUrl = this.getRequired("DATABASE_URL", raw);
|
|
37
|
+
const port = this.getNumber("PORT", raw, 8080);
|
|
38
|
+
const nodeEnv = this.getEnum(
|
|
39
|
+
"NODE_ENV",
|
|
40
|
+
raw,
|
|
41
|
+
["development", "production", "test"],
|
|
42
|
+
"development"
|
|
43
|
+
) as AppConfig["nodeEnv"];
|
|
44
|
+
const logLevel = this.getEnum(
|
|
45
|
+
"LOG_LEVEL",
|
|
46
|
+
raw,
|
|
47
|
+
["debug", "info", "warn", "error"],
|
|
48
|
+
"info"
|
|
49
|
+
) as AppConfig["logLevel"];
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
apiKey,
|
|
53
|
+
databaseUrl,
|
|
54
|
+
port,
|
|
55
|
+
nodeEnv,
|
|
56
|
+
logLevel,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private getRequired(key: string, env: Record<string, string>): string {
|
|
61
|
+
const value = env[key] || process.env[key];
|
|
62
|
+
if (!value) {
|
|
63
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private getNumber(
|
|
69
|
+
key: string,
|
|
70
|
+
env: Record<string, string>,
|
|
71
|
+
defaultValue: number
|
|
72
|
+
): number {
|
|
73
|
+
const value = env[key] || process.env[key];
|
|
74
|
+
if (!value) {
|
|
75
|
+
return defaultValue;
|
|
76
|
+
}
|
|
77
|
+
const parsed = parseInt(value, 10);
|
|
78
|
+
if (isNaN(parsed)) {
|
|
79
|
+
console.warn(`Invalid number for ${key}, using default: ${defaultValue}`);
|
|
80
|
+
return defaultValue;
|
|
81
|
+
}
|
|
82
|
+
return parsed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private getEnum<T extends string>(
|
|
86
|
+
key: string,
|
|
87
|
+
env: Record<string, string>,
|
|
88
|
+
validValues: readonly T[],
|
|
89
|
+
defaultValue: T
|
|
90
|
+
): T {
|
|
91
|
+
const value = (env[key] || process.env[key] || defaultValue).toLowerCase();
|
|
92
|
+
if (validValues.includes(value as T)) {
|
|
93
|
+
return value as T;
|
|
94
|
+
}
|
|
95
|
+
console.warn(
|
|
96
|
+
`Invalid value for ${key}: ${value}, using default: ${defaultValue}`
|
|
97
|
+
);
|
|
98
|
+
return defaultValue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Usage in your application
|
|
103
|
+
function main() {
|
|
104
|
+
try {
|
|
105
|
+
const loader = new ConfigLoader();
|
|
106
|
+
const config: AppConfig = loader.loadConfig();
|
|
107
|
+
|
|
108
|
+
console.log("Application Configuration:");
|
|
109
|
+
console.log(` API Key: ${config.apiKey.substring(0, 10)}...`);
|
|
110
|
+
console.log(` Database: ${config.databaseUrl}`);
|
|
111
|
+
console.log(` Port: ${config.port}`);
|
|
112
|
+
console.log(` Environment: ${config.nodeEnv}`);
|
|
113
|
+
console.log(` Log Level: ${config.logLevel}`);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof Error) {
|
|
116
|
+
console.error(`Configuration error: ${error.message}`);
|
|
117
|
+
} else {
|
|
118
|
+
console.error("Unknown configuration error");
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Run if this is the main module
|
|
125
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
126
|
+
main();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { ConfigLoader, type AppConfig };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript example: Auto-detection patterns
|
|
3
|
+
*
|
|
4
|
+
* Shows how to use pattern matching to determine which
|
|
5
|
+
* environment variables should be encrypted.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as dotenvage from "../index.js";
|
|
9
|
+
|
|
10
|
+
// Test various key names
|
|
11
|
+
const testKeys = [
|
|
12
|
+
"API_KEY",
|
|
13
|
+
"FLY_API_TOKEN",
|
|
14
|
+
"SECRET_TOKEN",
|
|
15
|
+
"DATABASE_PASSWORD",
|
|
16
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
17
|
+
"STRIPE_SECRET_KEY",
|
|
18
|
+
"NODE_ENV",
|
|
19
|
+
"PORT",
|
|
20
|
+
"DATABASE_URL",
|
|
21
|
+
"LOG_LEVEL",
|
|
22
|
+
"PUBLIC_API_URL",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
// Type-safe function to check if a key should be encrypted
|
|
26
|
+
function shouldEncryptKey(key: string): boolean {
|
|
27
|
+
return dotenvage.shouldEncrypt(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Group keys by whether they should be encrypted
|
|
31
|
+
const encryptedKeys: string[] = [];
|
|
32
|
+
const plainKeys: string[] = [];
|
|
33
|
+
|
|
34
|
+
testKeys.forEach((key) => {
|
|
35
|
+
if (shouldEncryptKey(key)) {
|
|
36
|
+
encryptedKeys.push(key);
|
|
37
|
+
} else {
|
|
38
|
+
plainKeys.push(key);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log("Auto-detection Results:");
|
|
43
|
+
console.log(`\n🔒 Should encrypt (${encryptedKeys.length}):`);
|
|
44
|
+
encryptedKeys.forEach((key) => {
|
|
45
|
+
console.log(` - ${key}`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log(`\n✅ Plain text (${plainKeys.length}):`);
|
|
49
|
+
plainKeys.forEach((key) => {
|
|
50
|
+
console.log(` - ${key}`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Example: Type-safe helper to encrypt secrets automatically
|
|
54
|
+
class SecretManager {
|
|
55
|
+
private manager: dotenvage.JsSecretManager;
|
|
56
|
+
|
|
57
|
+
constructor() {
|
|
58
|
+
this.manager = dotenvage.JsSecretManagerNew();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set an environment variable, auto-encrypting if the key matches patterns
|
|
63
|
+
*/
|
|
64
|
+
setEnvVar(key: string, value: string): string {
|
|
65
|
+
if (dotenvage.shouldEncrypt(key)) {
|
|
66
|
+
// Auto-encrypt sensitive keys
|
|
67
|
+
const encrypted = this.manager.encryptValue(value);
|
|
68
|
+
console.log(`🔒 Auto-encrypted ${key}`);
|
|
69
|
+
return encrypted;
|
|
70
|
+
} else {
|
|
71
|
+
// Keep plain text for non-sensitive keys
|
|
72
|
+
console.log(`✅ Kept ${key} as plain text`);
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Usage example
|
|
79
|
+
const secretMgr = new SecretManager();
|
|
80
|
+
const apiKey = secretMgr.setEnvVar("API_KEY", "sk_live_abc123");
|
|
81
|
+
const port = secretMgr.setEnvVar("PORT", "8080");
|
|
82
|
+
|
|
83
|
+
console.log(`\nAPI_KEY (encrypted): ${apiKey.substring(0, 30)}...`);
|
|
84
|
+
console.log(`PORT (plain): ${port}`);
|
|
85
|
+
|
|
86
|
+
// Type-safe configuration with auto-encryption
|
|
87
|
+
type ConfigEntry<T> = {
|
|
88
|
+
key: string;
|
|
89
|
+
value: T;
|
|
90
|
+
encrypt: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function createConfig<T>(
|
|
94
|
+
entries: Array<{ key: string; value: T; encrypt?: boolean }>
|
|
95
|
+
): ConfigEntry<T>[] {
|
|
96
|
+
const manager = dotenvage.JsSecretManagerGenerate();
|
|
97
|
+
|
|
98
|
+
return entries.map((entry) => {
|
|
99
|
+
const shouldEncrypt =
|
|
100
|
+
entry.encrypt !== undefined
|
|
101
|
+
? entry.encrypt
|
|
102
|
+
: dotenvage.shouldEncrypt(entry.key);
|
|
103
|
+
|
|
104
|
+
let processedValue: T = entry.value;
|
|
105
|
+
|
|
106
|
+
if (shouldEncrypt && typeof entry.value === "string") {
|
|
107
|
+
processedValue = manager.encryptValue(entry.value) as T;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
key: entry.key,
|
|
112
|
+
value: processedValue,
|
|
113
|
+
encrypt: shouldEncrypt,
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const configEntries = createConfig([
|
|
119
|
+
{ key: "API_KEY", value: "sk_live_123" },
|
|
120
|
+
{ key: "PORT", value: "8080" },
|
|
121
|
+
{ key: "DATABASE_URL", value: "postgres://localhost/db" },
|
|
122
|
+
{ key: "SECRET_TOKEN", value: "secret-value" },
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
console.log("\nConfig entries:");
|
|
126
|
+
configEntries.forEach((entry) => {
|
|
127
|
+
const displayValue =
|
|
128
|
+
entry.encrypt && typeof entry.value === "string"
|
|
129
|
+
? `${entry.value.substring(0, 30)}...`
|
|
130
|
+
: entry.value;
|
|
131
|
+
console.log(` ${entry.key}: ${displayValue} ${entry.encrypt ? "🔒" : "✅"}`);
|
|
132
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic example of using dotenvage in Node.js
|
|
3
|
+
*
|
|
4
|
+
* This is the most common pattern: load encrypted .env files into process.env
|
|
5
|
+
* (similar to how dotenv works with require('dotenv').config())
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const dotenvage = require("../index.js");
|
|
9
|
+
|
|
10
|
+
// Example 1: Load environment files (MOST COMMON - like dotenv.config())
|
|
11
|
+
// This loads and decrypts .env files, setting variables into process.env
|
|
12
|
+
// Yes, this is how dotenv packages work - they mutate process.env
|
|
13
|
+
console.log(
|
|
14
|
+
"=== Example 1: Load Environment Files (Most Common) ==="
|
|
15
|
+
);
|
|
16
|
+
try {
|
|
17
|
+
const loader = dotenvage.JsEnvLoaderNew();
|
|
18
|
+
loader.load(); // Loads into process.env (mutates environment, like dotenv)
|
|
19
|
+
|
|
20
|
+
// Now variables are available in process.env
|
|
21
|
+
console.log("Loaded variables into process.env");
|
|
22
|
+
const variableNames = loader.getAllVariableNames();
|
|
23
|
+
console.log("Total variables:", variableNames.length);
|
|
24
|
+
|
|
25
|
+
if (variableNames.length > 0) {
|
|
26
|
+
console.log(
|
|
27
|
+
"Sample variables:",
|
|
28
|
+
variableNames.slice(0, 5).join(", ")
|
|
29
|
+
);
|
|
30
|
+
// Example: access a variable (if it exists)
|
|
31
|
+
const sampleKey = variableNames[0];
|
|
32
|
+
if (process.env[sampleKey]) {
|
|
33
|
+
console.log(
|
|
34
|
+
`Example: process.env.${sampleKey} = ${process.env[
|
|
35
|
+
sampleKey
|
|
36
|
+
].substring(0, 20)}...`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log(
|
|
42
|
+
"Note: Environment loading requires a valid encryption key."
|
|
43
|
+
);
|
|
44
|
+
console.log(
|
|
45
|
+
"Set DOTENVAGE_AGE_KEY, AGE_KEY, or EKG_AGE_KEY environment variable."
|
|
46
|
+
);
|
|
47
|
+
console.log("Error:", error.message);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Example 1b: Load without mutating process.env (alternative pattern)
|
|
51
|
+
// Get variables as an object instead of setting process.env
|
|
52
|
+
console.log(
|
|
53
|
+
"\n=== Example 1b: Get Variables as Object (Non-mutating) ==="
|
|
54
|
+
);
|
|
55
|
+
try {
|
|
56
|
+
const loader = dotenvage.JsEnvLoaderNew();
|
|
57
|
+
const variables = loader.getAllVariables(); // Returns object without modifying process.env
|
|
58
|
+
const keys = Object.keys(variables);
|
|
59
|
+
console.log(
|
|
60
|
+
"Got",
|
|
61
|
+
keys.length,
|
|
62
|
+
"variables as object (not in process.env)"
|
|
63
|
+
);
|
|
64
|
+
if (keys.length > 0) {
|
|
65
|
+
const sampleKey = keys[0];
|
|
66
|
+
console.log(
|
|
67
|
+
`Example: variables.${sampleKey} = ${variables[
|
|
68
|
+
sampleKey
|
|
69
|
+
].substring(0, 20)}...`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.log(
|
|
74
|
+
"Note: Requires valid encryption key. Error:",
|
|
75
|
+
error.message
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Example 2: Generate a new encryption key
|
|
80
|
+
console.log("\n=== Example 2: Generate Key ===");
|
|
81
|
+
try {
|
|
82
|
+
const manager = dotenvage.JsSecretManagerGenerate();
|
|
83
|
+
const publicKey = manager.publicKeyString();
|
|
84
|
+
console.log("Generated public key:", publicKey);
|
|
85
|
+
console.log("Save this key - you'll need it to decrypt values!");
|
|
86
|
+
console.log(
|
|
87
|
+
'Set it as: export DOTENVAGE_AGE_KEY="<private-key-string>"'
|
|
88
|
+
);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("Error generating key:", error.message);
|
|
91
|
+
console.log(
|
|
92
|
+
'Note: Make sure you have built the native bindings with "npm run build"'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Example 3: Encrypt and decrypt a value
|
|
97
|
+
console.log("\n=== Example 3: Encrypt/Decrypt ===");
|
|
98
|
+
try {
|
|
99
|
+
const manager = dotenvage.JsSecretManagerGenerate();
|
|
100
|
+
const secret = "my-super-secret-api-key-12345";
|
|
101
|
+
|
|
102
|
+
const encrypted = manager.encryptValue(secret);
|
|
103
|
+
console.log("Original:", secret);
|
|
104
|
+
console.log("Encrypted:", encrypted);
|
|
105
|
+
|
|
106
|
+
const decrypted = manager.decryptValue(encrypted);
|
|
107
|
+
console.log("Decrypted:", decrypted);
|
|
108
|
+
console.log("Match:", secret === decrypted ? "✅" : "❌");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("Error:", error.message);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Example 4: Check if key should be encrypted (auto-detection patterns)
|
|
114
|
+
console.log("\n=== Example 4: Auto-detection Patterns ===");
|
|
115
|
+
const keys = [
|
|
116
|
+
"API_KEY",
|
|
117
|
+
"FLY_API_TOKEN",
|
|
118
|
+
"NODE_ENV",
|
|
119
|
+
"PORT",
|
|
120
|
+
"DATABASE_URL",
|
|
121
|
+
];
|
|
122
|
+
for (const key of keys) {
|
|
123
|
+
const shouldEncrypt = dotenvage.shouldEncrypt(key);
|
|
124
|
+
console.log(
|
|
125
|
+
`${key}: ${shouldEncrypt ? "🔒 Should encrypt" : "✅ Plain text"}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript example: Encryption and decryption
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as dotenvage from "../index.js";
|
|
6
|
+
|
|
7
|
+
// Generate a new key pair
|
|
8
|
+
const manager = dotenvage.JsSecretManagerGenerate();
|
|
9
|
+
|
|
10
|
+
// Get the public key (can be shared)
|
|
11
|
+
const publicKey: string = manager.publicKeyString();
|
|
12
|
+
console.log(`Public key: ${publicKey}`);
|
|
13
|
+
|
|
14
|
+
// Encrypt a secret value
|
|
15
|
+
const secret = "sk_live_abc123xyz";
|
|
16
|
+
const encrypted: string = manager.encryptValue(secret);
|
|
17
|
+
console.log(`\nEncrypted: ${encrypted}`);
|
|
18
|
+
|
|
19
|
+
// Decrypt it back
|
|
20
|
+
const decrypted: string = manager.decryptValue(encrypted);
|
|
21
|
+
console.log(`Decrypted: ${decrypted}`);
|
|
22
|
+
console.log(`Match: ${secret === decrypted ? "✅" : "❌"}`);
|
|
23
|
+
|
|
24
|
+
// Check if a value is encrypted
|
|
25
|
+
const isEncrypted: boolean = manager.isEncrypted(encrypted);
|
|
26
|
+
console.log(`\nIs encrypted: ${isEncrypted}`);
|
|
27
|
+
|
|
28
|
+
// Pass through unencrypted values
|
|
29
|
+
const plaintext = "not-encrypted";
|
|
30
|
+
const result: string = manager.decryptValue(plaintext);
|
|
31
|
+
console.log(`Plaintext passthrough: ${result}`);
|
|
32
|
+
|
|
33
|
+
// Type-safe wrapper function
|
|
34
|
+
function encryptSecret(
|
|
35
|
+
manager: dotenvage.JsSecretManager,
|
|
36
|
+
value: string
|
|
37
|
+
): string {
|
|
38
|
+
return manager.encryptValue(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function decryptSecret(
|
|
42
|
+
manager: dotenvage.JsSecretManager,
|
|
43
|
+
value: string
|
|
44
|
+
): string {
|
|
45
|
+
return manager.decryptValue(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Usage with type safety
|
|
49
|
+
const apiToken = "my-api-token-123";
|
|
50
|
+
const encryptedToken = encryptSecret(manager, apiToken);
|
|
51
|
+
const decryptedToken = decryptSecret(manager, encryptedToken);
|
|
52
|
+
|
|
53
|
+
console.log(`\nType-safe encryption:`);
|
|
54
|
+
console.log(`Original: ${apiToken}`);
|
|
55
|
+
console.log(`Encrypted: ${encryptedToken.substring(0, 30)}...`);
|
|
56
|
+
console.log(`Decrypted: ${decryptedToken}`);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript example: Loading environment variables
|
|
3
|
+
*
|
|
4
|
+
* This is the most common pattern - loading encrypted .env files
|
|
5
|
+
* into process.env (like dotenv.config())
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as dotenvage from "../index.js";
|
|
9
|
+
|
|
10
|
+
// Pattern 1: Load into process.env (most common, mutates environment)
|
|
11
|
+
// This matches how dotenv works - variables become available in process.env
|
|
12
|
+
const loader = dotenvage.JsEnvLoaderNew();
|
|
13
|
+
loader.load(); // Sets variables in process.env
|
|
14
|
+
|
|
15
|
+
// Now access via process.env
|
|
16
|
+
const apiKey = process.env.API_KEY;
|
|
17
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
18
|
+
|
|
19
|
+
console.log("Loaded variables into process.env");
|
|
20
|
+
console.log(`API_KEY exists: ${!!apiKey}`);
|
|
21
|
+
console.log(`DATABASE_URL exists: ${!!databaseUrl}`);
|
|
22
|
+
|
|
23
|
+
// Pattern 2: Get as object (non-mutating, functional style)
|
|
24
|
+
// Use this when you don't want to modify process.env
|
|
25
|
+
const loader2 = dotenvage.JsEnvLoaderNew();
|
|
26
|
+
const env = loader2.getAllVariables(); // Returns Record<string, string>
|
|
27
|
+
|
|
28
|
+
// Type-safe access
|
|
29
|
+
type EnvConfig = {
|
|
30
|
+
API_KEY?: string;
|
|
31
|
+
DATABASE_URL?: string;
|
|
32
|
+
PORT?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const config: EnvConfig = env as EnvConfig;
|
|
36
|
+
console.log("\nGot variables as object:");
|
|
37
|
+
console.log(`API_KEY: ${config.API_KEY ? "✅" : "❌"}`);
|
|
38
|
+
console.log(`DATABASE_URL: ${config.DATABASE_URL ? "✅" : "❌"}`);
|
|
39
|
+
console.log(`PORT: ${config.PORT || "default (8080)"}`);
|
|
40
|
+
|
|
41
|
+
// Pattern 3: Load from specific directory
|
|
42
|
+
const loader3 = dotenvage.JsEnvLoaderNew();
|
|
43
|
+
loader3.loadFromDir("./config"); // Load .env files from ./config directory
|
|
44
|
+
|
|
45
|
+
// Pattern 4: Get variable names only (without loading into environment)
|
|
46
|
+
const loader4 = dotenvage.JsEnvLoaderNew();
|
|
47
|
+
const variableNames: string[] = loader4.getAllVariableNames();
|
|
48
|
+
console.log(`\nFound ${variableNames.length} variables in .env files:`);
|
|
49
|
+
variableNames.slice(0, 5).forEach((name) => {
|
|
50
|
+
console.log(` - ${name}`);
|
|
51
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript example: Next.js integration
|
|
3
|
+
*
|
|
4
|
+
* Shows how to integrate dotenvage with Next.js for
|
|
5
|
+
* server-side configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as dotenvage from "../index.js";
|
|
9
|
+
|
|
10
|
+
// Load environment variables (do this early in your app)
|
|
11
|
+
// In Next.js, you'd typically do this in next.config.js or in your API routes
|
|
12
|
+
const loader = dotenvage.JsEnvLoaderNew();
|
|
13
|
+
loader.load();
|
|
14
|
+
|
|
15
|
+
// Type-safe Next.js environment configuration
|
|
16
|
+
interface NextjsEnv {
|
|
17
|
+
readonly NEXT_PUBLIC_APP_URL: string;
|
|
18
|
+
readonly DATABASE_URL: string;
|
|
19
|
+
readonly API_KEY: string;
|
|
20
|
+
readonly NEXT_PUBLIC_API_URL?: string;
|
|
21
|
+
readonly NODE_ENV: "development" | "production" | "test";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Get environment variables with type safety
|
|
25
|
+
function getNextjsEnv(): NextjsEnv {
|
|
26
|
+
const required = (key: keyof NextjsEnv): string => {
|
|
27
|
+
const value = process.env[key];
|
|
28
|
+
if (!value) {
|
|
29
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const optional = (key: string): string | undefined => {
|
|
35
|
+
return process.env[key];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
NEXT_PUBLIC_APP_URL: required("NEXT_PUBLIC_APP_URL"),
|
|
40
|
+
DATABASE_URL: required("DATABASE_URL"),
|
|
41
|
+
API_KEY: required("API_KEY"),
|
|
42
|
+
NEXT_PUBLIC_API_URL: optional("NEXT_PUBLIC_API_URL"),
|
|
43
|
+
NODE_ENV: (process.env.NODE_ENV || "development") as NextjsEnv["NODE_ENV"],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Example: Next.js API route handler
|
|
48
|
+
export function createApiHandler<T extends Record<string, unknown>>(
|
|
49
|
+
handler: (env: NextjsEnv, req: T) => Promise<Response>
|
|
50
|
+
) {
|
|
51
|
+
return async (req: T): Promise<Response> => {
|
|
52
|
+
try {
|
|
53
|
+
const env = getNextjsEnv();
|
|
54
|
+
return await handler(env, req);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error("API error:", error);
|
|
57
|
+
return new Response(
|
|
58
|
+
JSON.stringify({ error: "Internal server error" }),
|
|
59
|
+
{
|
|
60
|
+
status: 500,
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Example: Next.js API route
|
|
69
|
+
async function exampleApiRoute(
|
|
70
|
+
env: NextjsEnv,
|
|
71
|
+
req: { method: string }
|
|
72
|
+
): Promise<Response> {
|
|
73
|
+
if (req.method !== "GET") {
|
|
74
|
+
return new Response("Method not allowed", { status: 405 });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return new Response(
|
|
78
|
+
JSON.stringify({
|
|
79
|
+
message: "API is working",
|
|
80
|
+
environment: env.NODE_ENV,
|
|
81
|
+
// Don't expose secrets in API responses!
|
|
82
|
+
hasApiKey: !!env.API_KEY,
|
|
83
|
+
}),
|
|
84
|
+
{
|
|
85
|
+
status: 200,
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Export the handler (Next.js pattern)
|
|
92
|
+
export const handler = createApiHandler(exampleApiRoute);
|
|
93
|
+
|
|
94
|
+
// Example: Server-side component data fetching
|
|
95
|
+
export async function getServerSideData() {
|
|
96
|
+
const env = getNextjsEnv();
|
|
97
|
+
|
|
98
|
+
// Use the decrypted environment variables
|
|
99
|
+
const response = await fetch(`${env.NEXT_PUBLIC_API_URL}/api/data`, {
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: `Bearer ${env.API_KEY}`,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new Error("Failed to fetch data");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return response.json();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Example: Environment validation at startup
|
|
113
|
+
export function validateNextjsEnv(): void {
|
|
114
|
+
try {
|
|
115
|
+
const env = getNextjsEnv();
|
|
116
|
+
console.log("✅ All required environment variables are set");
|
|
117
|
+
console.log(` Environment: ${env.NODE_ENV}`);
|
|
118
|
+
console.log(` App URL: ${env.NEXT_PUBLIC_APP_URL}`);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof Error) {
|
|
121
|
+
console.error(`❌ Environment validation failed: ${error.message}`);
|
|
122
|
+
}
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Run validation if this is the main module
|
|
128
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
129
|
+
validateNextjsEnv();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { getNextjsEnv, type NextjsEnv };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"declaration": true,
|
|
17
|
+
"declarationMap": true,
|
|
18
|
+
"sourceMap": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["*.ts", "**/*.ts"],
|
|
21
|
+
"exclude": ["node_modules"]
|
|
22
|
+
}
|