morphix-env 0.1.0 → 0.3.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 +38 -34
- package/dist/cli.js +64 -32
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://morphix.app/brand/logo-rounded.png" width="80" alt="MorphixAI" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# morphix-env
|
|
2
6
|
|
|
3
7
|
Environment variable toolkit for multi-project architectures. Combines [Infisical](https://infisical.com) secret management with local override files and client-side runtime injection.
|
|
4
8
|
|
|
@@ -6,7 +10,7 @@ Environment variable toolkit for multi-project architectures. Combines [Infisica
|
|
|
6
10
|
|
|
7
11
|
| Problem | Solution |
|
|
8
12
|
|---------|----------|
|
|
9
|
-
| `NEXT_PUBLIC_*` / `VITE_*` baked at build time | `
|
|
13
|
+
| `NEXT_PUBLIC_*` / `VITE_*` baked at build time | `morphix-env generate` creates `__env.js` for runtime injection |
|
|
10
14
|
| Scattered env vars across hosting platforms | Single source of truth in Infisical, pulled at startup |
|
|
11
15
|
| No local override when using remote config | `.env.local` always wins — edit one file, restart |
|
|
12
16
|
| Different tools for different needs (dotenv, cross-env, infisical CLI) | One tool, one command |
|
|
@@ -14,42 +18,42 @@ Environment variable toolkit for multi-project architectures. Combines [Infisica
|
|
|
14
18
|
## Install
|
|
15
19
|
|
|
16
20
|
```bash
|
|
17
|
-
pnpm add -D
|
|
21
|
+
pnpm add -D morphix-env
|
|
18
22
|
# or
|
|
19
|
-
npm install -D
|
|
23
|
+
npm install -D morphix-env
|
|
20
24
|
```
|
|
21
25
|
|
|
22
26
|
## Quick Start
|
|
23
27
|
|
|
24
28
|
```bash
|
|
25
29
|
# Run a command with .env.local overrides
|
|
26
|
-
|
|
30
|
+
morphix-env run -- next dev
|
|
27
31
|
|
|
28
32
|
# Generate client-side __env.js
|
|
29
|
-
|
|
33
|
+
morphix-env generate --out public/__env.js
|
|
30
34
|
|
|
31
35
|
# Debug: see what's loaded
|
|
32
|
-
|
|
36
|
+
morphix-env inspect
|
|
33
37
|
```
|
|
34
38
|
|
|
35
39
|
## Commands
|
|
36
40
|
|
|
37
|
-
### `
|
|
41
|
+
### `morphix-env run [options] -- <command>`
|
|
38
42
|
|
|
39
43
|
Load environment variables, then execute a command. The child process inherits all injected vars.
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
46
|
# Basic: load .env.local, run dev server
|
|
43
|
-
|
|
47
|
+
morphix-env run -- next dev --turbo -p 3004
|
|
44
48
|
|
|
45
49
|
# Custom env file
|
|
46
|
-
|
|
50
|
+
morphix-env run -f .env.staging -- npm start
|
|
47
51
|
|
|
48
52
|
# Skip Infisical (use only local files)
|
|
49
|
-
|
|
53
|
+
morphix-env run --no-infisical -- npm start
|
|
50
54
|
|
|
51
55
|
# Verbose: show which vars were loaded
|
|
52
|
-
|
|
56
|
+
morphix-env run -v -- node server.js
|
|
53
57
|
```
|
|
54
58
|
|
|
55
59
|
**Loading priority (high to low):**
|
|
@@ -58,19 +62,19 @@ mx-env run -v -- node server.js
|
|
|
58
62
|
2. Infisical secrets — pulled via SDK
|
|
59
63
|
3. Existing `process.env` — Docker ENV, CI vars, etc.
|
|
60
64
|
|
|
61
|
-
### `
|
|
65
|
+
### `morphix-env generate [options]`
|
|
62
66
|
|
|
63
67
|
Extract public environment variables (`NEXT_PUBLIC_*`, `VITE_*`, `EXPO_PUBLIC_*`) and write them to a JS file for browser runtime injection.
|
|
64
68
|
|
|
65
69
|
```bash
|
|
66
70
|
# Default: public/__env.js
|
|
67
|
-
|
|
71
|
+
morphix-env generate
|
|
68
72
|
|
|
69
73
|
# Custom output path (Vite projects)
|
|
70
|
-
|
|
74
|
+
morphix-env generate --out dist/__env.js
|
|
71
75
|
|
|
72
76
|
# Only include specific prefix
|
|
73
|
-
|
|
77
|
+
morphix-env generate --filter NEXT_PUBLIC_
|
|
74
78
|
```
|
|
75
79
|
|
|
76
80
|
Output file content:
|
|
@@ -94,14 +98,14 @@ function getEnv(key: string, fallback = ''): string {
|
|
|
94
98
|
}
|
|
95
99
|
```
|
|
96
100
|
|
|
97
|
-
### `
|
|
101
|
+
### `morphix-env inspect [options]`
|
|
98
102
|
|
|
99
103
|
Print env var values for debugging. Secrets are masked (first 4 chars shown).
|
|
100
104
|
|
|
101
105
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
morphix-env inspect
|
|
107
|
+
morphix-env inspect --filter NEXT_PUBLIC_
|
|
108
|
+
morphix-env inspect -f .env.production
|
|
105
109
|
```
|
|
106
110
|
|
|
107
111
|
## Options
|
|
@@ -116,7 +120,7 @@ mx-env inspect -f .env.production
|
|
|
116
120
|
|
|
117
121
|
## Config File
|
|
118
122
|
|
|
119
|
-
Create `
|
|
123
|
+
Create `morphix-env.config.json` in your project root to declare project-level settings. This file is committed to git.
|
|
120
124
|
|
|
121
125
|
```json
|
|
122
126
|
{
|
|
@@ -156,14 +160,14 @@ Create `mx-env.config.json` in your project root to declare project-level settin
|
|
|
156
160
|
```json
|
|
157
161
|
{
|
|
158
162
|
"scripts": {
|
|
159
|
-
"dev": "
|
|
160
|
-
"build": "
|
|
161
|
-
"start": "
|
|
163
|
+
"dev": "morphix-env run -- next dev --turbo -p 3004",
|
|
164
|
+
"build": "morphix-env run -- next build",
|
|
165
|
+
"start": "morphix-env run -- node server.js"
|
|
162
166
|
}
|
|
163
167
|
}
|
|
164
168
|
```
|
|
165
169
|
|
|
166
|
-
`
|
|
170
|
+
`morphix-env.config.json`:
|
|
167
171
|
|
|
168
172
|
```json
|
|
169
173
|
{
|
|
@@ -185,8 +189,8 @@ Create `mx-env.config.json` in your project root to declare project-level settin
|
|
|
185
189
|
```json
|
|
186
190
|
{
|
|
187
191
|
"scripts": {
|
|
188
|
-
"dev": "
|
|
189
|
-
"build": "
|
|
192
|
+
"dev": "morphix-env run -- vite",
|
|
193
|
+
"build": "morphix-env run -- vite build"
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
```
|
|
@@ -210,8 +214,8 @@ Create `mx-env.config.json` in your project root to declare project-level settin
|
|
|
210
214
|
```json
|
|
211
215
|
{
|
|
212
216
|
"scripts": {
|
|
213
|
-
"dev": "
|
|
214
|
-
"start": "
|
|
217
|
+
"dev": "morphix-env run -- tsx watch src/index.ts",
|
|
218
|
+
"start": "morphix-env run -- node dist/index.js"
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
```
|
|
@@ -241,19 +245,19 @@ ENV INFISICAL_CLIENT_ID=""
|
|
|
241
245
|
ENV INFISICAL_CLIENT_SECRET=""
|
|
242
246
|
ENV DEPLOY_ENV="prod"
|
|
243
247
|
|
|
244
|
-
CMD ["npx", "
|
|
248
|
+
CMD ["npx", "morphix-env", "run", "--", "node", "server.js"]
|
|
245
249
|
```
|
|
246
250
|
|
|
247
251
|
No Infisical CLI binary needed in the image.
|
|
248
252
|
|
|
249
253
|
### Local Development with Infisical CLI
|
|
250
254
|
|
|
251
|
-
If you already use `infisical run` locally,
|
|
255
|
+
If you already use `infisical run` locally, morphix-env still adds value as the override layer:
|
|
252
256
|
|
|
253
257
|
```json
|
|
254
258
|
{
|
|
255
|
-
"dev": "infisical run --path=/ai --env=dev --
|
|
256
|
-
"dev:local": "infisical run --path=/ai --env=dev --
|
|
259
|
+
"dev": "infisical run --path=/ai --env=dev -- morphix-env run -- next dev",
|
|
260
|
+
"dev:local": "infisical run --path=/ai --env=dev -- morphix-env run -- next dev"
|
|
257
261
|
}
|
|
258
262
|
```
|
|
259
263
|
|
|
@@ -261,7 +265,7 @@ If you already use `infisical run` locally, mx-env still adds value as the overr
|
|
|
261
265
|
|
|
262
266
|
## How It Works
|
|
263
267
|
|
|
264
|
-
1. Read `
|
|
268
|
+
1. Read `morphix-env.config.json` for project settings
|
|
265
269
|
2. If `INFISICAL_CLIENT_ID` + `INFISICAL_CLIENT_SECRET` exist → fetch secrets via SDK, inject into `process.env` (does not overwrite existing vars)
|
|
266
270
|
3. Read `.env.local` → inject into `process.env` (overwrites everything, highest priority)
|
|
267
271
|
4. If `generate` is configured → write `__env.js` with public vars
|
package/dist/cli.js
CHANGED
|
@@ -26,32 +26,23 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/env.ts
|
|
27
27
|
var import_fs = require("fs");
|
|
28
28
|
var import_path = require("path");
|
|
29
|
+
var import_dotenv = require("dotenv");
|
|
29
30
|
function parseEnvFile(filePath) {
|
|
30
31
|
const absPath = (0, import_path.resolve)(filePath);
|
|
31
32
|
if (!(0, import_fs.existsSync)(absPath)) return {};
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
for (const raw of content.split("\n")) {
|
|
35
|
-
const line = raw.trim();
|
|
36
|
-
if (!line || line.startsWith("#")) continue;
|
|
37
|
-
const eq = line.indexOf("=");
|
|
38
|
-
if (eq === -1) continue;
|
|
39
|
-
const key = line.slice(0, eq).trim();
|
|
40
|
-
let value = line.slice(eq + 1).trim();
|
|
41
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
42
|
-
value = value.slice(1, -1);
|
|
43
|
-
}
|
|
44
|
-
vars[key] = value;
|
|
45
|
-
}
|
|
46
|
-
return vars;
|
|
33
|
+
const result = (0, import_dotenv.config)({ path: absPath, override: false });
|
|
34
|
+
return result.parsed || {};
|
|
47
35
|
}
|
|
48
36
|
function loadEnvFiles(files) {
|
|
49
37
|
const overrides = [];
|
|
50
38
|
for (const file of files) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
const absPath = (0, import_path.resolve)(file);
|
|
40
|
+
if (!(0, import_fs.existsSync)(absPath)) continue;
|
|
41
|
+
const result = (0, import_dotenv.config)({ path: absPath, override: true });
|
|
42
|
+
if (result.parsed) {
|
|
43
|
+
for (const key of Object.keys(result.parsed)) {
|
|
44
|
+
overrides.push({ key, source: file });
|
|
45
|
+
}
|
|
55
46
|
}
|
|
56
47
|
}
|
|
57
48
|
return overrides;
|
|
@@ -69,6 +60,8 @@ function extractPublicVars() {
|
|
|
69
60
|
|
|
70
61
|
// src/infisical.ts
|
|
71
62
|
var import_sdk = require("@infisical/sdk");
|
|
63
|
+
var import_child_process = require("child_process");
|
|
64
|
+
var import_dotenv2 = require("dotenv");
|
|
72
65
|
function getInfisicalConfig() {
|
|
73
66
|
const clientId = process.env.INFISICAL_CLIENT_ID;
|
|
74
67
|
const clientSecret = process.env.INFISICAL_CLIENT_SECRET;
|
|
@@ -107,6 +100,32 @@ async function fetchInfisicalSecrets(config) {
|
|
|
107
100
|
}
|
|
108
101
|
return count;
|
|
109
102
|
}
|
|
103
|
+
function hasInfisicalCLI() {
|
|
104
|
+
try {
|
|
105
|
+
(0, import_child_process.execSync)("infisical --version", { stdio: "pipe" });
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function fetchSecretsViaCLI(environment, paths) {
|
|
112
|
+
let count = 0;
|
|
113
|
+
for (const secretPath of paths) {
|
|
114
|
+
try {
|
|
115
|
+
const output = (0, import_child_process.execSync)(
|
|
116
|
+
`infisical export --path=${secretPath} --env=${environment} --format=dotenv`,
|
|
117
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
118
|
+
);
|
|
119
|
+
const vars = (0, import_dotenv2.parse)(output);
|
|
120
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
121
|
+
process.env[key] = value;
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
110
129
|
|
|
111
130
|
// src/config.ts
|
|
112
131
|
var import_fs2 = require("fs");
|
|
@@ -137,7 +156,7 @@ function loadConfig() {
|
|
|
137
156
|
var import_cross_spawn = __toESM(require("cross-spawn"));
|
|
138
157
|
var import_fs3 = require("fs");
|
|
139
158
|
var import_path3 = require("path");
|
|
140
|
-
var VERSION = "0.
|
|
159
|
+
var VERSION = "0.3.0";
|
|
141
160
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
142
161
|
function parseArgs(argv) {
|
|
143
162
|
const args = {
|
|
@@ -197,28 +216,41 @@ function mergeWithConfig(args, config) {
|
|
|
197
216
|
}
|
|
198
217
|
async function loadAllEnv(args, config) {
|
|
199
218
|
if (!args.noInfisical) {
|
|
219
|
+
const env = config.infisical?.env || process.env.DEPLOY_ENV || process.env.INFISICAL_ENV || "dev";
|
|
220
|
+
const paths = config.infisical?.paths || ["/"];
|
|
200
221
|
const infisicalConfig = config.infisical ? {
|
|
201
222
|
clientId: process.env.INFISICAL_CLIENT_ID || "",
|
|
202
223
|
clientSecret: process.env.INFISICAL_CLIENT_SECRET || "",
|
|
203
224
|
projectId: config.infisical.projectId,
|
|
204
|
-
environment:
|
|
205
|
-
paths
|
|
225
|
+
environment: env,
|
|
226
|
+
paths,
|
|
206
227
|
siteUrl: config.infisical.siteUrl
|
|
207
228
|
} : getInfisicalConfig();
|
|
208
229
|
if (infisicalConfig && infisicalConfig.clientId && infisicalConfig.clientSecret) {
|
|
209
230
|
try {
|
|
210
231
|
const count = await fetchInfisicalSecrets(infisicalConfig);
|
|
211
|
-
console.log(`[
|
|
232
|
+
console.log(`[morphix-env] Infisical SDK: loaded ${count} secrets (${env}: ${paths.join(", ")})`);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
console.warn(`[morphix-env] Infisical SDK: failed - ${e.message}`);
|
|
235
|
+
}
|
|
236
|
+
} else if (hasInfisicalCLI()) {
|
|
237
|
+
try {
|
|
238
|
+
const count = fetchSecretsViaCLI(env, paths);
|
|
239
|
+
if (count > 0) {
|
|
240
|
+
console.log(`[morphix-env] Infisical CLI: loaded ${count} secrets (${env}: ${paths.join(", ")})`);
|
|
241
|
+
} else {
|
|
242
|
+
console.log(`[morphix-env] Infisical CLI: no secrets found (run 'infisical login' first?)`);
|
|
243
|
+
}
|
|
212
244
|
} catch (e) {
|
|
213
|
-
console.warn(`[
|
|
245
|
+
console.warn(`[morphix-env] Infisical CLI: failed - ${e.message}`);
|
|
214
246
|
}
|
|
215
247
|
} else if (args.verbose) {
|
|
216
|
-
console.log("[
|
|
248
|
+
console.log("[morphix-env] Infisical: skipped (no SDK credentials, no CLI)");
|
|
217
249
|
}
|
|
218
250
|
}
|
|
219
251
|
const overrides = loadEnvFiles(args.envFiles);
|
|
220
252
|
if (overrides.length > 0) {
|
|
221
|
-
console.log(`[
|
|
253
|
+
console.log(`[morphix-env] Loaded ${overrides.length} overrides from ${args.envFiles.join(", ")}`);
|
|
222
254
|
if (args.verbose) {
|
|
223
255
|
for (const o of overrides) {
|
|
224
256
|
console.log(` ${o.key} (from ${o.source})`);
|
|
@@ -228,7 +260,7 @@ async function loadAllEnv(args, config) {
|
|
|
228
260
|
}
|
|
229
261
|
async function cmdRun(args, config) {
|
|
230
262
|
if (args.subArgs.length === 0) {
|
|
231
|
-
console.error("[
|
|
263
|
+
console.error("[morphix-env] No command specified. Usage: mx-env run -- <command>");
|
|
232
264
|
process.exit(1);
|
|
233
265
|
}
|
|
234
266
|
await loadAllEnv(args, config);
|
|
@@ -241,7 +273,7 @@ async function cmdRun(args, config) {
|
|
|
241
273
|
env: process.env
|
|
242
274
|
});
|
|
243
275
|
if (result.error) {
|
|
244
|
-
console.error(`[
|
|
276
|
+
console.error(`[morphix-env] Failed to execute: ${cmd}`, result.error.message);
|
|
245
277
|
process.exit(1);
|
|
246
278
|
}
|
|
247
279
|
process.exit(result.status ?? 1);
|
|
@@ -256,7 +288,7 @@ function generateClientEnv(outFile, filter) {
|
|
|
256
288
|
const js = `window.__ENV=${JSON.stringify(vars)};`;
|
|
257
289
|
(0, import_fs3.mkdirSync)((0, import_path3.dirname)(outFile), { recursive: true });
|
|
258
290
|
(0, import_fs3.writeFileSync)(outFile, js);
|
|
259
|
-
console.log(`[
|
|
291
|
+
console.log(`[morphix-env] Generated ${outFile} (${Object.keys(vars).length} client vars)`);
|
|
260
292
|
}
|
|
261
293
|
async function cmdGenerate(args, config) {
|
|
262
294
|
const outFile = args.outFile || "public/__env.js";
|
|
@@ -297,7 +329,7 @@ process.env public vars: (${filtered.length})`);
|
|
|
297
329
|
}
|
|
298
330
|
function showHelp() {
|
|
299
331
|
console.log(`
|
|
300
|
-
|
|
332
|
+
morphix-env v${VERSION} \u2014 MorphixAI environment variable toolkit
|
|
301
333
|
|
|
302
334
|
Usage:
|
|
303
335
|
mx-env run [options] -- <command> Load env + exec command
|
|
@@ -364,13 +396,13 @@ async function main() {
|
|
|
364
396
|
break;
|
|
365
397
|
default:
|
|
366
398
|
if (args.command) {
|
|
367
|
-
console.error(`[
|
|
399
|
+
console.error(`[morphix-env] Unknown command: ${args.command}`);
|
|
368
400
|
}
|
|
369
401
|
showHelp();
|
|
370
402
|
process.exit(args.command ? 1 : 0);
|
|
371
403
|
}
|
|
372
404
|
}
|
|
373
405
|
main().catch((e) => {
|
|
374
|
-
console.error("[
|
|
406
|
+
console.error("[morphix-env] Fatal:", e.message);
|
|
375
407
|
process.exit(1);
|
|
376
408
|
});
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morphix-env",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Environment variable toolkit — Infisical integration, local overrides, and client-side runtime injection for Next.js / Vite / Express.",
|
|
5
5
|
"author": "MorphixAI <dev@morphixai.com>",
|
|
6
|
-
"homepage": "https://github.com/Morphicai/
|
|
6
|
+
"homepage": "https://github.com/Morphicai/morphix-env#readme",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/Morphicai/
|
|
9
|
+
"url": "git+https://github.com/Morphicai/morphix-env.git"
|
|
10
10
|
},
|
|
11
11
|
"bugs": {
|
|
12
|
-
"url": "https://github.com/Morphicai/
|
|
12
|
+
"url": "https://github.com/Morphicai/morphix-env/issues"
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
15
|
"morphix-env": "./dist/cli.js"
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@infisical/sdk": "^5.0.0",
|
|
52
|
-
"cross-spawn": "^7.0.6"
|
|
52
|
+
"cross-spawn": "^7.0.6",
|
|
53
|
+
"dotenv": "^17.3.1"
|
|
53
54
|
}
|
|
54
55
|
}
|