morphix-env 0.3.0 → 0.4.1

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.
Files changed (3) hide show
  1. package/README.md +163 -109
  2. package/dist/cli.js +33 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -26,7 +26,7 @@ npm install -D morphix-env
26
26
  ## Quick Start
27
27
 
28
28
  ```bash
29
- # Run a command with .env.local overrides
29
+ # Run a command with env injection
30
30
  morphix-env run -- next dev
31
31
 
32
32
  # Generate client-side __env.js
@@ -36,6 +36,126 @@ morphix-env generate --out public/__env.js
36
36
  morphix-env inspect
37
37
  ```
38
38
 
39
+ ## How It Works
40
+
41
+ ### Core Flow
42
+
43
+ ```
44
+ morphix-env run -- <command>
45
+ │
46
+ ├─ 1. Read mx-env.config.json
47
+ │
48
+ ├─ 2. Load Infisical secrets ──────────────────────────┐
49
+ │ │ │
50
+ │ ├─ INFISICAL_CLIENT_ID exists? │
51
+ │ │ ├─ Yes → SDK (Machine Identity) ── CI/Docker │
52
+ │ │ └─ No ──┐ │
53
+ │ │ ├─ infisical CLI installed? │
54
+ │ │ │ ├─ Yes → CLI (user login) ── Local │
55
+ │ │ │ └─ No → Skip │
56
+ │ │ │
57
+ │ └─ Inject into process.env (does NOT overwrite) │
58
+ │ │
59
+ ├─ 3. Load .env.local ─────────────────────────────────┐
60
+ │ └─ Inject into process.env (OVERWRITES all) │
61
+ │ │
62
+ ├─ 4. Generate __env.js (if configured) │
63
+ │ └─ Extract NEXT_PUBLIC_* / VITE_* → write file │
64
+ │ │
65
+ └─ 5. Spawn child command │
66
+ └─ Inherits fully assembled process.env │
67
+ ```
68
+
69
+ ### Priority (high → low)
70
+
71
+ ```
72
+ ┌─────────────────────────────────────────────────┐
73
+ │ .env.local ← HIGHEST │
74
+ │ Always wins. Developer's local overrides. │
75
+ ├─────────────────────────────────────────────────┤
76
+ │ Infisical secrets ← MEDIUM │
77
+ │ Pulled via SDK or CLI. Does not overwrite. │
78
+ ├─────────────────────────────────────────────────┤
79
+ │ process.env ← LOWEST │
80
+ │ Docker ENV, CI vars, shell exports. │
81
+ └─────────────────────────────────────────────────┘
82
+ ```
83
+
84
+ ### Authentication Flow
85
+
86
+ ```
87
+ ┌──────────────────────────────────────────────────────────┐
88
+ │ morphix-env starts │
89
+ │ │ │
90
+ │ INFISICAL_CLIENT_ID set? │
91
+ │ / \ │
92
+ │ Yes No │
93
+ │ │ │ │
94
+ │ ┌───────▼────────┐ infisical CLI installed? │
95
+ │ │ SDK Auth │ / \ │
96
+ │ │ (Machine ID) │ Yes No │
97
+ │ │ │ │ │ │
98
+ │ │ CI / Docker / │ ┌──▼───────────┐ │ │
99
+ │ │ Production │ │ CLI Auth │ ▼ │
100
+ │ └────────┬────────┘ │ (User Login) │ Skip │
101
+ │ │ │ │ Infisical │
102
+ │ │ │ Local Dev │ │
103
+ │ │ └──────┬────────┘ │
104
+ │ │ │ │
105
+ │ ▼ ▼ │
106
+ │ Pull secrets from Infisical │
107
+ │ Inject into process.env │
108
+ └──────────────────────────────────────────────────────────┘
109
+ ```
110
+
111
+ ### Local Development
112
+
113
+ ```
114
+ Developer machine:
115
+ 1. infisical login ← one-time, session cached
116
+ 2. pnpm dev ← morphix-env auto-detects CLI
117
+ └─ morphix-env run
118
+ ├─ infisical CLI pulls 69 secrets
119
+ ├─ .env.local overrides API_BASE_URL → localhost
120
+ └─ next dev starts with all vars
121
+ ```
122
+
123
+ ### CI / Docker
124
+
125
+ ```
126
+ Container / CI runner:
127
+ ENV INFISICAL_CLIENT_ID=xxx
128
+ ENV INFISICAL_CLIENT_SECRET=xxx
129
+ ENV DEPLOY_ENV=prod
130
+
131
+ CMD morphix-env run -- node server.js
132
+ └─ morphix-env run
133
+ ├─ SDK pulls secrets (no CLI needed)
134
+ ├─ .env.local not present → skip
135
+ └─ server starts with prod vars
136
+ ```
137
+
138
+ ### `__env.js` — Client-Side Runtime Injection
139
+
140
+ ```
141
+ Build phase (CI):
142
+ morphix-env run -- next build
143
+ ├─ NEXT_PUBLIC_* injected at build time → baked into JS bundle
144
+ └─ Works, but image is environment-specific
145
+
146
+ Runtime injection (Docker, optional):
147
+ morphix-env run -- node server.js
148
+ ├─ morphix-env generates public/__env.js:
149
+ │ window.__ENV = {
150
+ │ "NEXT_PUBLIC_API_URL": "https://api.prod.example.com",
151
+ │ "NEXT_PUBLIC_APP_NAME": "MyApp"
152
+ │ };
153
+ │
154
+ ├─ Browser loads <script src="/__env.js"> before app
155
+ └─ App reads: window.__ENV?.NEXT_PUBLIC_API_URL
156
+ → One build, deploy to any environment
157
+ ```
158
+
39
159
  ## Commands
40
160
 
41
161
  ### `morphix-env run [options] -- <command>`
@@ -43,7 +163,7 @@ morphix-env inspect
43
163
  Load environment variables, then execute a command. The child process inherits all injected vars.
44
164
 
45
165
  ```bash
46
- # Basic: load .env.local, run dev server
166
+ # Basic: load env, run dev server
47
167
  morphix-env run -- next dev --turbo -p 3004
48
168
 
49
169
  # Custom env file
@@ -56,46 +176,14 @@ morphix-env run --no-infisical -- npm start
56
176
  morphix-env run -v -- node server.js
57
177
  ```
58
178
 
59
- **Loading priority (high to low):**
60
-
61
- 1. `.env.local` (or `--env-file`) — always wins
62
- 2. Infisical secrets — pulled via SDK
63
- 3. Existing `process.env` — Docker ENV, CI vars, etc.
64
-
65
179
  ### `morphix-env generate [options]`
66
180
 
67
- Extract public environment variables (`NEXT_PUBLIC_*`, `VITE_*`, `EXPO_PUBLIC_*`) and write them to a JS file for browser runtime injection.
181
+ Extract public environment variables and write to a JS file for browser runtime injection.
68
182
 
69
183
  ```bash
70
- # Default: public/__env.js
71
- morphix-env generate
72
-
73
- # Custom output path (Vite projects)
74
- morphix-env generate --out dist/__env.js
75
-
76
- # Only include specific prefix
77
- morphix-env generate --filter NEXT_PUBLIC_
78
- ```
79
-
80
- Output file content:
81
-
82
- ```js
83
- window.__ENV={"NEXT_PUBLIC_API_URL":"https://api.example.com","NEXT_PUBLIC_APP_NAME":"MyApp"};
84
- ```
85
-
86
- Load it in your HTML before your app bundle:
87
-
88
- ```html
89
- <script src="/__env.js"></script>
90
- ```
91
-
92
- Read in your app:
93
-
94
- ```ts
95
- function getEnv(key: string, fallback = ''): string {
96
- if (typeof window === 'undefined') return process.env[key] || fallback
97
- return window.__ENV?.[key] || process.env[key] || fallback
98
- }
184
+ morphix-env generate # → public/__env.js
185
+ morphix-env generate --out dist/__env.js # Vite projects
186
+ morphix-env generate --filter NEXT_PUBLIC_ # Only Next.js vars
99
187
  ```
100
188
 
101
189
  ### `morphix-env inspect [options]`
@@ -115,19 +203,18 @@ morphix-env inspect -f .env.production
115
203
  | `--env-file <path>` | `-f` | Env file to load (default: `.env.local`, repeatable) |
116
204
  | `--out <path>` | `-o` | Output path for generate (default: `public/__env.js`) |
117
205
  | `--filter <prefix>` | | Only include vars with this prefix |
118
- | `--no-infisical` | | Skip Infisical SDK fetch |
206
+ | `--no-infisical` | | Skip Infisical fetch entirely |
119
207
  | `--verbose` | `-v` | Show loaded variable names |
120
208
 
121
209
  ## Config File
122
210
 
123
- Create `morphix-env.config.json` in your project root to declare project-level settings. This file is committed to git.
211
+ Create `mx-env.config.json` in your project root. Committed to git.
124
212
 
125
213
  ```json
126
214
  {
127
215
  "infisical": {
128
- "projectId": "your-project-id",
129
- "paths": ["/ai/shared", "/ai/web"],
130
- "env": "$DEPLOY_ENV"
216
+ "paths": ["/ai"],
217
+ "env": "dev"
131
218
  },
132
219
  "envFiles": [".env.local"],
133
220
  "generate": {
@@ -137,56 +224,55 @@ Create `morphix-env.config.json` in your project root to declare project-level s
137
224
  }
138
225
  ```
139
226
 
140
- | Field | Description | Committed to git? |
141
- |-------|-------------|-------------------|
142
- | `infisical.projectId` | Infisical project ID | Yes — not a secret |
143
- | `infisical.paths` | Secret paths to pull | Yes — not a secret |
144
- | `infisical.env` | Environment name, supports `$VAR` references | Yes |
145
- | `envFiles` | Local override files to load | Yes |
146
- | `generate` | Client-side __env.js output config | Yes |
227
+ ### Config vs Environment Variables
147
228
 
148
- **Infisical authentication** is always via environment variables — never in the config file:
149
-
150
- | Env Var | Description |
151
- |---------|-------------|
152
- | `INFISICAL_CLIENT_ID` | Machine Identity client ID |
153
- | `INFISICAL_CLIENT_SECRET` | Machine Identity client secret |
154
- | `DEPLOY_ENV` | Environment name (`dev` / `staging` / `prod`) |
229
+ ```
230
+ ┌──────────────────────────────────────────────────────┐
231
+ │ mx-env.config.json (committed to git) │
232
+ │ ├─ paths → which secrets to pull │
233
+ │ ├─ env → which environment │
234
+ │ ├─ envFiles → which override files to load │
235
+ │ └─ generate → __env.js output config │
236
+ │ │
237
+ │ These are PROJECT CONFIG, not secrets. │
238
+ ├──────────────────────────────────────────────────────┤
239
+ │ Environment Variables (NEVER committed) │
240
+ │ ├─ INFISICAL_CLIENT_ID → Machine Identity │
241
+ │ ├─ INFISICAL_CLIENT_SECRET → Machine Identity │
242
+ │ └─ DEPLOY_ENV → prod / staging / dev │
243
+ │ │
244
+ │ These are CREDENTIALS, set in CI/Docker only. │
245
+ │ Local dev uses infisical CLI login instead. │
246
+ └──────────────────────────────────────────────────────┘
247
+ ```
155
248
 
156
249
  ## Usage Examples
157
250
 
158
251
  ### Next.js
159
252
 
160
- ```json
253
+ ```jsonc
254
+ // package.json
161
255
  {
162
256
  "scripts": {
163
257
  "dev": "morphix-env run -- next dev --turbo -p 3004",
164
258
  "build": "morphix-env run -- next build",
165
- "start": "morphix-env run -- node server.js"
259
+ "start": "morphix-env run -- next start"
166
260
  }
167
261
  }
168
262
  ```
169
263
 
170
- `morphix-env.config.json`:
171
-
172
264
  ```json
265
+ // mx-env.config.json
173
266
  {
174
- "infisical": {
175
- "projectId": "xxx",
176
- "paths": ["/ai/shared", "/ai/web"],
177
- "env": "$DEPLOY_ENV"
178
- },
267
+ "infisical": { "paths": ["/ai"], "env": "dev" },
179
268
  "envFiles": [".env.local"],
180
- "generate": {
181
- "out": "public/__env.js",
182
- "filter": "NEXT_PUBLIC_"
183
- }
269
+ "generate": { "out": "public/__env.js", "filter": "NEXT_PUBLIC_" }
184
270
  }
185
271
  ```
186
272
 
187
273
  ### Vite (React / Vue / Ionic)
188
274
 
189
- ```json
275
+ ```jsonc
190
276
  {
191
277
  "scripts": {
192
278
  "dev": "morphix-env run -- vite",
@@ -197,21 +283,14 @@ Create `morphix-env.config.json` in your project root to declare project-level s
197
283
 
198
284
  ```json
199
285
  {
200
- "infisical": {
201
- "projectId": "xxx",
202
- "paths": ["/ai/shared", "/ai/shell"],
203
- "env": "$DEPLOY_ENV"
204
- },
205
- "generate": {
206
- "out": "dist/__env.js",
207
- "filter": "VITE_"
208
- }
286
+ "infisical": { "paths": ["/frontend"], "env": "dev" },
287
+ "generate": { "out": "dist/__env.js", "filter": "VITE_" }
209
288
  }
210
289
  ```
211
290
 
212
291
  ### Express API
213
292
 
214
- ```json
293
+ ```jsonc
215
294
  {
216
295
  "scripts": {
217
296
  "dev": "morphix-env run -- tsx watch src/index.ts",
@@ -222,15 +301,12 @@ Create `morphix-env.config.json` in your project root to declare project-level s
222
301
 
223
302
  ```json
224
303
  {
225
- "infisical": {
226
- "projectId": "xxx",
227
- "paths": ["/ai/shared", "/ai/api"],
228
- "env": "$DEPLOY_ENV"
229
- }
304
+ "infisical": { "paths": ["/ai"], "env": "dev" },
305
+ "envFiles": [".env.local"]
230
306
  }
231
307
  ```
232
308
 
233
- No `generate` field — server-side apps don't need `__env.js`.
309
+ No `generate` — server-side apps don't need `__env.js`.
234
310
 
235
311
  ### Docker
236
312
 
@@ -240,7 +316,6 @@ WORKDIR /app
240
316
  COPY . .
241
317
  RUN pnpm install && pnpm build
242
318
 
243
- # Only these 3 vars needed at runtime
244
319
  ENV INFISICAL_CLIENT_ID=""
245
320
  ENV INFISICAL_CLIENT_SECRET=""
246
321
  ENV DEPLOY_ENV="prod"
@@ -250,27 +325,6 @@ CMD ["npx", "morphix-env", "run", "--", "node", "server.js"]
250
325
 
251
326
  No Infisical CLI binary needed in the image.
252
327
 
253
- ### Local Development with Infisical CLI
254
-
255
- If you already use `infisical run` locally, morphix-env still adds value as the override layer:
256
-
257
- ```json
258
- {
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"
261
- }
262
- ```
263
-
264
- `.env.local` overrides take effect on top of Infisical CLI injection.
265
-
266
- ## How It Works
267
-
268
- 1. Read `morphix-env.config.json` for project settings
269
- 2. If `INFISICAL_CLIENT_ID` + `INFISICAL_CLIENT_SECRET` exist → fetch secrets via SDK, inject into `process.env` (does not overwrite existing vars)
270
- 3. Read `.env.local` → inject into `process.env` (overwrites everything, highest priority)
271
- 4. If `generate` is configured → write `__env.js` with public vars
272
- 5. Spawn child command — it inherits the fully assembled `process.env`
273
-
274
328
  ## License
275
329
 
276
330
  MIT
package/dist/cli.js CHANGED
@@ -61,11 +61,13 @@ function extractPublicVars() {
61
61
  // src/infisical.ts
62
62
  var import_sdk = require("@infisical/sdk");
63
63
  var import_child_process = require("child_process");
64
+ var import_fs2 = require("fs");
65
+ var import_path2 = require("path");
64
66
  var import_dotenv2 = require("dotenv");
65
67
  function getInfisicalConfig() {
66
68
  const clientId = process.env.INFISICAL_CLIENT_ID;
67
69
  const clientSecret = process.env.INFISICAL_CLIENT_SECRET;
68
- const projectId = process.env.INFISICAL_PROJECT_ID;
70
+ const projectId = process.env.INFISICAL_PROJECT_ID || readInfisicalJson().workspaceId;
69
71
  if (!clientId || !clientSecret || !projectId) return null;
70
72
  return {
71
73
  clientId,
@@ -108,6 +110,15 @@ function hasInfisicalCLI() {
108
110
  return false;
109
111
  }
110
112
  }
113
+ function readInfisicalJson() {
114
+ const filePath = (0, import_path2.resolve)(".infisical.json");
115
+ if (!(0, import_fs2.existsSync)(filePath)) return {};
116
+ try {
117
+ return JSON.parse((0, import_fs2.readFileSync)(filePath, "utf8"));
118
+ } catch {
119
+ return {};
120
+ }
121
+ }
111
122
  function fetchSecretsViaCLI(environment, paths) {
112
123
  let count = 0;
113
124
  for (const secretPath of paths) {
@@ -128,35 +139,25 @@ function fetchSecretsViaCLI(environment, paths) {
128
139
  }
129
140
 
130
141
  // src/config.ts
131
- var import_fs2 = require("fs");
132
- var import_path2 = require("path");
142
+ var import_fs3 = require("fs");
143
+ var import_path3 = require("path");
133
144
  var CONFIG_FILE = "mx-env.config.json";
134
- function resolveEnvRef(value) {
135
- if (value.startsWith("$")) {
136
- return process.env[value.slice(1)] || "";
137
- }
138
- return value;
139
- }
140
145
  function loadConfig() {
141
- const configPath = (0, import_path2.resolve)(CONFIG_FILE);
142
- if (!(0, import_fs2.existsSync)(configPath)) return {};
146
+ const configPath = (0, import_path3.resolve)(CONFIG_FILE);
147
+ if (!(0, import_fs3.existsSync)(configPath)) return {};
143
148
  try {
144
- const raw = JSON.parse((0, import_fs2.readFileSync)(configPath, "utf8"));
145
- if (raw.infisical?.env) {
146
- raw.infisical.env = resolveEnvRef(raw.infisical.env);
147
- }
148
- return raw;
149
+ return JSON.parse((0, import_fs3.readFileSync)(configPath, "utf8"));
149
150
  } catch (e) {
150
- console.warn(`[mx-env] Failed to parse ${CONFIG_FILE}: ${e.message}`);
151
+ console.warn(`[morphix-env] Failed to parse ${CONFIG_FILE}: ${e.message}`);
151
152
  return {};
152
153
  }
153
154
  }
154
155
 
155
156
  // src/cli.ts
156
157
  var import_cross_spawn = __toESM(require("cross-spawn"));
157
- var import_fs3 = require("fs");
158
- var import_path3 = require("path");
159
- var VERSION = "0.3.0";
158
+ var import_fs4 = require("fs");
159
+ var import_path4 = require("path");
160
+ var VERSION = "0.4.1";
160
161
  var DEFAULT_ENV_FILE = ".env.local";
161
162
  function parseArgs(argv) {
162
163
  const args = {
@@ -166,7 +167,8 @@ function parseArgs(argv) {
166
167
  outFile: null,
167
168
  verbose: false,
168
169
  filter: null,
169
- noInfisical: false
170
+ noInfisical: false,
171
+ env: null
170
172
  };
171
173
  let i = 2;
172
174
  const command = argv[i];
@@ -188,6 +190,9 @@ function parseArgs(argv) {
188
190
  } else if (arg === "--filter") {
189
191
  i++;
190
192
  if (argv[i]) args.filter = argv[i];
193
+ } else if (arg === "--env" || arg === "-e") {
194
+ i++;
195
+ if (argv[i]) args.env = argv[i];
191
196
  } else if (arg === "--verbose" || arg === "-v") {
192
197
  args.verbose = true;
193
198
  } else if (arg === "--no-infisical") {
@@ -216,12 +221,13 @@ function mergeWithConfig(args, config) {
216
221
  }
217
222
  async function loadAllEnv(args, config) {
218
223
  if (!args.noInfisical) {
219
- const env = config.infisical?.env || process.env.DEPLOY_ENV || process.env.INFISICAL_ENV || "dev";
224
+ const env = args.env || process.env.DEPLOY_ENV || process.env.INFISICAL_ENV || "prod";
220
225
  const paths = config.infisical?.paths || ["/"];
226
+ const resolvedProjectId = process.env.INFISICAL_PROJECT_ID || config.infisical?.projectId || readInfisicalJson().workspaceId || "";
221
227
  const infisicalConfig = config.infisical ? {
222
228
  clientId: process.env.INFISICAL_CLIENT_ID || "",
223
229
  clientSecret: process.env.INFISICAL_CLIENT_SECRET || "",
224
- projectId: config.infisical.projectId,
230
+ projectId: resolvedProjectId,
225
231
  environment: env,
226
232
  paths,
227
233
  siteUrl: config.infisical.siteUrl
@@ -286,8 +292,8 @@ function generateClientEnv(outFile, filter) {
286
292
  );
287
293
  }
288
294
  const js = `window.__ENV=${JSON.stringify(vars)};`;
289
- (0, import_fs3.mkdirSync)((0, import_path3.dirname)(outFile), { recursive: true });
290
- (0, import_fs3.writeFileSync)(outFile, js);
295
+ (0, import_fs4.mkdirSync)((0, import_path4.dirname)(outFile), { recursive: true });
296
+ (0, import_fs4.writeFileSync)(outFile, js);
291
297
  console.log(`[morphix-env] Generated ${outFile} (${Object.keys(vars).length} client vars)`);
292
298
  }
293
299
  async function cmdGenerate(args, config) {
@@ -339,8 +345,9 @@ Usage:
339
345
  Options:
340
346
  -f, --env-file <path> Env file to load (default: .env.local, repeatable)
341
347
  -o, --out <path> Output path for generate (default: public/__env.js)
348
+ -e, --env <name> Infisical environment (dev/staging/prod), overrides config
342
349
  --filter <prefix> Only include vars with this prefix
343
- --no-infisical Skip Infisical SDK fetch
350
+ --no-infisical Skip Infisical fetch entirely
344
351
  -v, --verbose Show loaded variable names
345
352
  --help, -h Show this help
346
353
  --version Show version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morphix-env",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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
6
  "homepage": "https://github.com/Morphicai/morphix-env#readme",