@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 ADDED
@@ -0,0 +1,337 @@
1
+ # @dotenvage/node
2
+
3
+ [![npm version](https://badge.fury.io/js/%40dotenvage%2Fnode.svg)](https://www.npmjs.com/package/@dotenvage/node)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/dataroadinc/dotenvage/blob/main/LICENSE)
5
+
6
+ Node.js bindings for
7
+ [dotenvage](https://github.com/dataroadinc/dotenvage) - **Dotenv with
8
+ age encryption**.
9
+
10
+ ## Features
11
+
12
+ - 🔒 **Encrypt secrets in `.env` files** - Safely commit encrypted
13
+ secrets to version control
14
+ - 📦 **Native performance** - Built with NAPI-RS for fast Rust
15
+ performance
16
+ - 🔄 **Auto-detection** - Automatically identifies which keys should
17
+ be encrypted
18
+ - 🌳 **File layering** - Multiple `.env*` files with automatic
19
+ precedence
20
+ - 💻 **CLI support** - Includes `dotenvage` command-line tool
21
+ - 📝 **TypeScript support** - Full TypeScript definitions included
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @dotenvage/node
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Basic Usage (Like dotenv)
32
+
33
+ ```typescript
34
+ import * as dotenvage from "@dotenvage/node";
35
+
36
+ // Load encrypted .env files into process.env
37
+ const loader = dotenvage.JsEnvLoaderNew();
38
+ loader.load(); // Mutates process.env (like dotenv.config())
39
+
40
+ // Now access variables
41
+ const apiKey = process.env.API_KEY;
42
+ const dbUrl = process.env.DATABASE_URL;
43
+ ```
44
+
45
+ ### Get Variables as Object (Non-mutating)
46
+
47
+ ```typescript
48
+ import * as dotenvage from "@dotenvage/node";
49
+
50
+ const loader = dotenvage.JsEnvLoaderNew();
51
+ const env = loader.getAllVariables(); // Returns Record<string, string>
52
+
53
+ // Use without modifying process.env
54
+ console.log(env.API_KEY);
55
+ console.log(env.DATABASE_URL);
56
+ ```
57
+
58
+ ### Encrypt and Decrypt Values
59
+
60
+ ```typescript
61
+ import * as dotenvage from "@dotenvage/node";
62
+
63
+ // Generate a new key pair
64
+ const manager = dotenvage.JsSecretManagerGenerate();
65
+ const publicKey = manager.publicKeyString(); // Share this public key
66
+
67
+ // Encrypt a secret
68
+ const secret = "sk_live_abc123";
69
+ const encrypted = manager.encryptValue(secret);
70
+ // Returns: "ENC[AGE:b64:...]"
71
+
72
+ // Decrypt it back
73
+ const decrypted = manager.decryptValue(encrypted);
74
+ console.log(decrypted === secret); // true
75
+ ```
76
+
77
+ ## API Reference
78
+
79
+ ### JsEnvLoader
80
+
81
+ Loads and decrypts `.env` files.
82
+
83
+ #### `JsEnvLoaderNew(): JsEnvLoader`
84
+
85
+ Creates a new loader with a default SecretManager.
86
+
87
+ ```typescript
88
+ const loader = dotenvage.JsEnvLoaderNew();
89
+ ```
90
+
91
+ #### `load(): void`
92
+
93
+ Loads `.env` files from the current directory into `process.env`.
94
+
95
+ ```typescript
96
+ loader.load(); // Sets variables in process.env
97
+ ```
98
+
99
+ #### `loadFromDir(dir: string): void`
100
+
101
+ Loads `.env` files from a specific directory.
102
+
103
+ ```typescript
104
+ loader.loadFromDir("./config");
105
+ ```
106
+
107
+ #### `getAllVariableNames(): string[]`
108
+
109
+ Gets all variable names from all loaded `.env` files (without loading
110
+ into environment).
111
+
112
+ ```typescript
113
+ const names = loader.getAllVariableNames();
114
+ console.log(names); // ["API_KEY", "DATABASE_URL", ...]
115
+ ```
116
+
117
+ #### `getAllVariables(): Record<string, string>`
118
+
119
+ Gets all environment variables as an object (decrypted). Note: This
120
+ loads variables into the process environment first.
121
+
122
+ ```typescript
123
+ const env = loader.getAllVariables();
124
+ console.log(env.API_KEY);
125
+ ```
126
+
127
+ #### `resolveEnvPaths(dir: string): string[]`
128
+
129
+ Computes the ordered list of env file paths that would be loaded.
130
+
131
+ ```typescript
132
+ const paths = loader.resolveEnvPaths(".");
133
+ console.log(paths); // [".env", ".env.local", ...]
134
+ ```
135
+
136
+ ### JsSecretManager
137
+
138
+ Manages encryption and decryption of secrets.
139
+
140
+ #### `JsSecretManagerGenerate(): JsSecretManager`
141
+
142
+ Generates a new random encryption key pair.
143
+
144
+ ```typescript
145
+ const manager = dotenvage.JsSecretManagerGenerate();
146
+ ```
147
+
148
+ #### `JsSecretManagerNew(): JsSecretManager`
149
+
150
+ Creates a SecretManager by loading the key from standard locations:
151
+
152
+ 1. `DOTENVAGE_AGE_KEY` environment variable
153
+ 2. `AGE_KEY` environment variable
154
+ 3. `EKG_AGE_KEY` environment variable
155
+ 4. Key file at `~/.local/state/dotenvage/dotenvage.key`
156
+
157
+ ```typescript
158
+ const manager = dotenvage.JsSecretManagerNew();
159
+ ```
160
+
161
+ #### `publicKeyString(): string`
162
+
163
+ Gets the public key as a string (starts with `age1`).
164
+
165
+ ```typescript
166
+ const publicKey = manager.publicKeyString();
167
+ ```
168
+
169
+ #### `encryptValue(plaintext: string): string`
170
+
171
+ Encrypts a plaintext value.
172
+
173
+ ```typescript
174
+ const encrypted = manager.encryptValue("secret");
175
+ ```
176
+
177
+ #### `decryptValue(value: string): string`
178
+
179
+ Decrypts a value if it's encrypted; otherwise returns it unchanged.
180
+
181
+ ```typescript
182
+ const decrypted = manager.decryptValue(encrypted);
183
+ ```
184
+
185
+ #### `isEncrypted(value: string): boolean`
186
+
187
+ Checks if a value is in a recognized encrypted format.
188
+
189
+ ```typescript
190
+ const isEncrypted = manager.isEncrypted("ENC[AGE:b64:...]");
191
+ ```
192
+
193
+ ### Utility Functions
194
+
195
+ #### `shouldEncrypt(key: string): boolean`
196
+
197
+ Checks if a key name should be encrypted based on auto-detection
198
+ patterns.
199
+
200
+ ```typescript
201
+ dotenvage.shouldEncrypt("API_KEY"); // true
202
+ dotenvage.shouldEncrypt("PORT"); // false
203
+ ```
204
+
205
+ ## CLI Usage
206
+
207
+ The package includes a `dotenvage` CLI command:
208
+
209
+ ```bash
210
+ npx dotenvage list
211
+ npx dotenvage get API_KEY
212
+ npx dotenvage dump --export
213
+ ```
214
+
215
+ See the
216
+ [main dotenvage README](https://github.com/dataroadinc/dotenvage#usage)
217
+ for full CLI documentation.
218
+
219
+ ## Next.js Integration
220
+
221
+ For Next.js applications, use the dedicated integration utilities:
222
+
223
+ ```typescript
224
+ // In next.config.mjs
225
+ import { loadEnv } from "@dotenvage/node/nextjs";
226
+ loadEnv();
227
+ ```
228
+
229
+ Or use the pre-initialization loader to ensure environment variables
230
+ are available for Edge Runtime (middleware):
231
+
232
+ ```json
233
+ {
234
+ "scripts": {
235
+ "dev": "node -r @dotenvage/node/nextjs/preinit node_modules/.bin/next dev"
236
+ }
237
+ }
238
+ ```
239
+
240
+ See [`nextjs/README.md`](./nextjs/README.md) for complete Next.js
241
+ integration documentation.
242
+
243
+ ## TypeScript Examples
244
+
245
+ See the `examples/` directory for TypeScript examples:
246
+
247
+ - `load-env.ts` - Loading environment variables
248
+ - `encrypt-decrypt.ts` - Encryption and decryption
249
+ - `app-config.ts` - Type-safe application configuration
250
+ - `auto-encrypt.ts` - Auto-detection patterns
251
+ - `nextjs-config.ts` - Basic Next.js usage example
252
+
253
+ ## File Layering
254
+
255
+ dotenvage supports automatic file layering with precedence:
256
+
257
+ 1. `.env` - Base configuration
258
+ 2. `.env.<ENV>` - Environment-specific (e.g., `.env.production`)
259
+ 3. `.env.<ENV>.<OS>` - OS-specific (e.g., `.env.production.linux`)
260
+ 4. `.env.<ENV>.<OS>.<ARCH>` - Architecture-specific
261
+ 5. `.env.<ENV>.<OS>.<ARCH>.<USER>` - User-specific overrides
262
+
263
+ Files are loaded in specificity order - more specific files override
264
+ less specific ones.
265
+
266
+ ## Key Management
267
+
268
+ ### Setting the Encryption Key
269
+
270
+ Set one of these environment variables:
271
+
272
+ ```bash
273
+ export DOTENVAGE_AGE_KEY="AGE-SECRET-KEY-1..."
274
+ export AGE_KEY="AGE-SECRET-KEY-1..."
275
+ export EKG_AGE_KEY="AGE-SECRET-KEY-1..."
276
+ ```
277
+
278
+ ### Generating a Key
279
+
280
+ ```typescript
281
+ const manager = dotenvage.JsSecretManagerGenerate();
282
+ const publicKey = manager.publicKeyString();
283
+ console.log(`Public key: ${publicKey}`);
284
+ console.log(`Set: export DOTENVAGE_AGE_KEY="<private-key>"`);
285
+ ```
286
+
287
+ Or use the CLI:
288
+
289
+ ```bash
290
+ npx dotenvage keygen
291
+ ```
292
+
293
+ ## Comparison with dotenv
294
+
295
+ | Feature | dotenv | @dotenvage/node |
296
+ | -------------------- | ------ | --------------- |
297
+ | Load `.env` files | ✅ | ✅ |
298
+ | Mutate `process.env` | ✅ | ✅ |
299
+ | Encrypt secrets | ❌ | ✅ |
300
+ | Commit to git safely | ❌ | ✅ |
301
+ | File layering | ❌ | ✅ |
302
+ | Auto-detection | ❌ | ✅ |
303
+ | Native performance | ❌ | ✅ (Rust) |
304
+
305
+ ## Requirements
306
+
307
+ - Node.js >= 18.0.0
308
+ - Valid age encryption key (set via environment variable or key file)
309
+
310
+ ## Building from Source
311
+
312
+ From the `npm/` directory:
313
+
314
+ ```bash
315
+ cd npm
316
+ npm install
317
+ npm run build
318
+ ```
319
+
320
+ Or from the project root:
321
+
322
+ ```bash
323
+ npm run npm:install
324
+ npm run npm:build
325
+ ```
326
+
327
+ ## License
328
+
329
+ MIT - See
330
+ [LICENSE](https://github.com/dataroadinc/dotenvage/blob/main/LICENSE)
331
+
332
+ ## Links
333
+
334
+ - [GitHub Repository](https://github.com/dataroadinc/dotenvage)
335
+ - [Rust Crate](https://crates.io/crates/dotenvage)
336
+ - [Rust Documentation](https://docs.rs/dotenvage)
337
+ - [Main README](https://github.com/dataroadinc/dotenvage#readme)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Next.js wrapper for dotenvage - loads encrypted environment variables before Next.js starts.
4
+ *
5
+ * This bin script allows users to call dotenvage-next directly without specifying the full path.
6
+ *
7
+ * Usage:
8
+ * pnpm exec dotenvage-next dev
9
+ * pnpm exec dotenvage-next build
10
+ *
11
+ * Or via package.json scripts:
12
+ * "dev": "dotenvage-next dev"
13
+ * "build": "dotenvage-next build"
14
+ */
15
+
16
+ // Import and execute the wrapper script
17
+ // The wrapper.mjs file uses top-level await and has side effects, so importing it will execute it
18
+ import("../nextjs/wrapper.mjs");
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Node.js CLI wrapper for dotenvage
5
+ *
6
+ * This wrapper attempts to use the native Rust binary if available,
7
+ * otherwise falls back to using the NAPI bindings to implement CLI functionality.
8
+ */
9
+
10
+ const { existsSync } = require("fs");
11
+ const { join } = require("path");
12
+ const { spawn, execSync } = require("child_process");
13
+ const { platform, arch } = require("os");
14
+
15
+ // Determine the binary name based on platform
16
+ function getBinaryName() {
17
+ const plat = platform();
18
+ const architecture = arch();
19
+
20
+ // Map Node.js arch to Rust target arch
21
+ const archMap = {
22
+ x64: "x86_64",
23
+ arm64: "aarch64",
24
+ ia32: "i686",
25
+ };
26
+
27
+ const rustArch = archMap[architecture] || architecture;
28
+
29
+ if (plat === "win32") {
30
+ return `dotenvage-${rustArch}-pc-windows-msvc.exe`;
31
+ } else if (plat === "darwin") {
32
+ return `dotenvage-${rustArch}-apple-darwin`;
33
+ } else {
34
+ return `dotenvage-${rustArch}-unknown-linux-gnu`;
35
+ }
36
+ }
37
+
38
+ // Try to find the native binary
39
+ function findNativeBinary() {
40
+ // Check in common locations
41
+ const possiblePaths = [
42
+ // In the npm package directory
43
+ join(__dirname, "..", "bin", getBinaryName()),
44
+ // In node_modules/.bin
45
+ join(__dirname, "..", "..", "..", "bin", getBinaryName()),
46
+ // Global installation
47
+ join(
48
+ process.env.HOME || process.env.USERPROFILE || "",
49
+ ".cargo",
50
+ "bin",
51
+ "dotenvage"
52
+ ),
53
+ join(
54
+ process.env.HOME || process.env.USERPROFILE || "",
55
+ ".local",
56
+ "bin",
57
+ "dotenvage"
58
+ ),
59
+ // PATH
60
+ "dotenvage",
61
+ ];
62
+
63
+ for (const path of possiblePaths) {
64
+ try {
65
+ if (path === "dotenvage") {
66
+ // Check if it's in PATH
67
+ execSync(`which ${path}`, { stdio: "ignore" });
68
+ return path;
69
+ } else if (existsSync(path)) {
70
+ return path;
71
+ }
72
+ } catch (e) {
73
+ // Continue searching
74
+ }
75
+ }
76
+
77
+ return null;
78
+ }
79
+
80
+ // Fallback to NAPI implementation
81
+ function useNapiImplementation() {
82
+ try {
83
+ const dotenvage = require("../index.js");
84
+
85
+ // Parse command line arguments
86
+ const args = process.argv.slice(2);
87
+ const command = args[0];
88
+
89
+ switch (command) {
90
+ case "keygen":
91
+ case "gen":
92
+ {
93
+ const manager = dotenvage.JsSecretManagerGenerate();
94
+ console.log(`✓ Private key generated`);
95
+ console.log(
96
+ `Public recipient: ${manager.publicKeyString()}`
97
+ );
98
+ console.log(
99
+ `Note: Use DOTENVAGE_AGE_KEY or AGE_KEY environment variable to set the key`
100
+ );
101
+ }
102
+ break;
103
+
104
+ case "get":
105
+ {
106
+ const key = args[1];
107
+ if (!key) {
108
+ console.error("Error: key name required");
109
+ process.exit(1);
110
+ }
111
+
112
+ const loader = dotenvage.JsEnvLoaderNew();
113
+ loader.load();
114
+
115
+ const value = process.env[key];
116
+ if (value === undefined) {
117
+ console.error(
118
+ `Error: key '${key}' not found in any .env* file`
119
+ );
120
+ process.exit(1);
121
+ }
122
+
123
+ console.log(value);
124
+ }
125
+ break;
126
+
127
+ case "list":
128
+ {
129
+ const verbose =
130
+ args.includes("--verbose") || args.includes("-v");
131
+ const json = args.includes("--json");
132
+ const plain = args.includes("--plain");
133
+ const fileIndex = args.indexOf("--file");
134
+ const file = fileIndex >= 0 ? args[fileIndex + 1] : null;
135
+
136
+ const loader = dotenvage.JsEnvLoaderNew();
137
+ let names;
138
+
139
+ if (file) {
140
+ loader.loadFromDir(process.cwd());
141
+ names = loader.getAllVariableNamesFromDir(process.cwd());
142
+ } else {
143
+ loader.load();
144
+ names = loader.getAllVariableNames();
145
+ }
146
+
147
+ if (json) {
148
+ const output = {};
149
+ for (const name of names) {
150
+ const value = process.env[name];
151
+ if (value) {
152
+ output[name] = verbose
153
+ ? { value, encrypted: false }
154
+ : { encrypted: false };
155
+ }
156
+ }
157
+ console.log(JSON.stringify(output, null, 2));
158
+ } else {
159
+ for (const name of names) {
160
+ if (verbose) {
161
+ const value = process.env[name];
162
+ const lockIcon = " "; // Can't easily detect encryption without manager
163
+ console.log(`${lockIcon} ${name} = ${value}`);
164
+ } else if (plain) {
165
+ console.log(name);
166
+ } else {
167
+ console.log(` ${name}`);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ break;
173
+
174
+ case "dump":
175
+ {
176
+ const exportFlag =
177
+ args.includes("--export") || args.includes("-e");
178
+ const fileIndex = args.indexOf("--file");
179
+ const file = fileIndex >= 0 ? args[fileIndex + 1] : null;
180
+
181
+ const loader = dotenvage.JsEnvLoaderNew();
182
+
183
+ if (file) {
184
+ loader.loadFromDir(process.cwd());
185
+ } else {
186
+ loader.load();
187
+ }
188
+
189
+ const vars = loader.getAllVariables();
190
+ const prefix = exportFlag ? "export " : "";
191
+
192
+ for (const [key, value] of Object.entries(vars)) {
193
+ // Simple escaping for values
194
+ const needsQuotes =
195
+ value.includes(" ") ||
196
+ value.includes("$") ||
197
+ value.includes("`");
198
+ if (needsQuotes) {
199
+ const escaped = value
200
+ .replace(/"/g, '\\"')
201
+ .replace(/\$/g, "\\$");
202
+ console.log(`${prefix}${key}="${escaped}"`);
203
+ } else {
204
+ console.log(`${prefix}${key}=${value}`);
205
+ }
206
+ }
207
+ }
208
+ break;
209
+
210
+ default:
211
+ console.error(`Error: Unknown command '${command}'`);
212
+ console.error(`
213
+ Usage: dotenvage <command> [options]
214
+
215
+ Commands:
216
+ keygen, gen Generate a new encryption key pair
217
+ get <key> Get a decrypted secret value
218
+ list List environment variables
219
+ dump Dump all decrypted env vars
220
+
221
+ Note: This is a limited implementation using NAPI bindings.
222
+ For full functionality, install the native Rust binary:
223
+ cargo install dotenvage
224
+ `);
225
+ process.exit(1);
226
+ }
227
+ } catch (error) {
228
+ console.error("Error:", error.message);
229
+ console.error(`
230
+ Note: Native bindings not available. Install the native Rust binary:
231
+ cargo install dotenvage
232
+ `);
233
+ process.exit(1);
234
+ }
235
+ }
236
+
237
+ // Main entry point
238
+ function main() {
239
+ const binary = findNativeBinary();
240
+
241
+ if (binary) {
242
+ // Use native binary
243
+ const child = spawn(binary, process.argv.slice(2), {
244
+ stdio: "inherit",
245
+ shell: false,
246
+ });
247
+
248
+ child.on("error", (error) => {
249
+ console.error(`Error running dotenvage: ${error.message}`);
250
+ console.error("Falling back to NAPI implementation...");
251
+ useNapiImplementation();
252
+ });
253
+
254
+ child.on("exit", (code) => {
255
+ process.exit(code || 0);
256
+ });
257
+ } else {
258
+ // Fallback to NAPI implementation
259
+ useNapiImplementation();
260
+ }
261
+ }
262
+
263
+ if (require.main === module) {
264
+ main();
265
+ }
Binary file
Binary file