morphix-env 0.8.0 → 0.10.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 +49 -1
- package/dist/SKILL.md +7 -0
- package/dist/cli.js +888 -105
- package/dist/index.d.ts +8 -0
- package/dist/index.js +32 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,14 @@ interface MxEnvConfig {
|
|
|
69
69
|
profiles?: Record<string, ProfileInput>;
|
|
70
70
|
/** Profile selected when a caller does not pass --profile. */
|
|
71
71
|
defaultProfile?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Commands that get a PATH shim via `mx-env shell-init` (global and project
|
|
74
|
+
* lists are unioned). Wiring only — never values. Deliberately excluded from
|
|
75
|
+
* validateConfig's hard errors: a convenience layer must not break the core
|
|
76
|
+
* injection path, so `shell-init` validates and hard-fails while composition
|
|
77
|
+
* paths only warn (see shims.ts validateShimList).
|
|
78
|
+
*/
|
|
79
|
+
shims?: string[];
|
|
72
80
|
}
|
|
73
81
|
interface LoadedConfig {
|
|
74
82
|
config: MxEnvConfig;
|
package/dist/index.js
CHANGED
|
@@ -140,12 +140,6 @@ function validateConfig(config, label = CONFIG_FILE) {
|
|
|
140
140
|
return errors;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// src/sources.ts
|
|
144
|
-
var import_child_process2 = require("child_process");
|
|
145
|
-
var import_fs4 = require("fs");
|
|
146
|
-
var import_path4 = require("path");
|
|
147
|
-
var import_dotenv3 = require("dotenv");
|
|
148
|
-
|
|
149
143
|
// src/env.ts
|
|
150
144
|
var import_fs2 = require("fs");
|
|
151
145
|
var import_path2 = require("path");
|
|
@@ -155,6 +149,20 @@ function parseEnvFile(filePath) {
|
|
|
155
149
|
if (!(0, import_fs2.existsSync)(absPath)) return {};
|
|
156
150
|
return (0, import_dotenv.parse)((0, import_fs2.readFileSync)(absPath, "utf8"));
|
|
157
151
|
}
|
|
152
|
+
function isDotenvxEncrypted(filePath) {
|
|
153
|
+
try {
|
|
154
|
+
const content = (0, import_fs2.readFileSync)((0, import_path2.resolve)(filePath), "utf8");
|
|
155
|
+
return content.includes("DOTENV_PUBLIC_KEY") && /^[A-Za-z_][A-Za-z0-9_]*="?encrypted:/m.test(content);
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/sources.ts
|
|
162
|
+
var import_child_process2 = require("child_process");
|
|
163
|
+
var import_fs4 = require("fs");
|
|
164
|
+
var import_path4 = require("path");
|
|
165
|
+
var import_dotenv3 = require("dotenv");
|
|
158
166
|
|
|
159
167
|
// src/infisical.ts
|
|
160
168
|
var import_sdk = require("@infisical/sdk");
|
|
@@ -266,7 +274,7 @@ function assertSafeGlobalFile(file, context, entry) {
|
|
|
266
274
|
if ((mode & 63) !== 0) {
|
|
267
275
|
throw new SourceError(
|
|
268
276
|
entry.name,
|
|
269
|
-
|
|
277
|
+
entry.source.provider,
|
|
270
278
|
`[morphix-env] Global env file ${file} is readable by group or other users; set mode 0600 or pass --allow-insecure-global.`
|
|
271
279
|
);
|
|
272
280
|
}
|
|
@@ -358,6 +366,16 @@ function resolveDotenvx(entry, source, context) {
|
|
|
358
366
|
if (existingFiles.length !== files.length) {
|
|
359
367
|
throw new SourceError(entry.name, source.provider, `[morphix-env] dotenvx source "${entry.name}" is missing a declared env file.`);
|
|
360
368
|
}
|
|
369
|
+
if (entry.globalImplicit && !context.allowInsecureGlobal && process.platform !== "win32") {
|
|
370
|
+
const keysFile = (0, import_path4.join)((0, import_path4.dirname)(existingFiles[0]), ".env.keys");
|
|
371
|
+
if ((0, import_fs4.existsSync)(keysFile) && ((0, import_fs4.statSync)(keysFile).mode & 63) !== 0) {
|
|
372
|
+
throw new SourceError(
|
|
373
|
+
entry.name,
|
|
374
|
+
source.provider,
|
|
375
|
+
`[morphix-env] Global dotenvx keys file ${keysFile} is readable by group or other users; set mode 0600 or pass --allow-insecure-global.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
361
379
|
const command = ensureCommand("dotenvx", entry.name, source.provider);
|
|
362
380
|
const keys = [...new Set(existingFiles.flatMap((file) => Object.keys(parseEnvFile(file))))].filter((key) => !key.startsWith("DOTENV_PUBLIC_KEY"));
|
|
363
381
|
if (!keys.length) return { name: entry.name, provider: source.provider, values: {}, availability: "loaded" };
|
|
@@ -365,8 +383,10 @@ function resolveDotenvx(entry, source, context) {
|
|
|
365
383
|
let stage = "run";
|
|
366
384
|
try {
|
|
367
385
|
const output = (0, import_child_process2.execFileSync)(
|
|
386
|
+
// --overload aligns dotenvx with every other provider: composed file
|
|
387
|
+
// values win over same-named variables already in the parent env.
|
|
368
388
|
command,
|
|
369
|
-
["run", "-f", existingFiles.join(","), "--", process.execPath, "-e", helper, JSON.stringify(keys)],
|
|
389
|
+
["run", "--overload", "-f", existingFiles.join(","), "--", process.execPath, "-e", helper, JSON.stringify(keys)],
|
|
370
390
|
{
|
|
371
391
|
encoding: "utf8",
|
|
372
392
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -389,7 +409,8 @@ function resolveDotenvx(entry, source, context) {
|
|
|
389
409
|
return { name: entry.name, provider: source.provider, values, availability: "loaded" };
|
|
390
410
|
} catch (error) {
|
|
391
411
|
const category = typeof error?.code === "string" ? error.code : "failed";
|
|
392
|
-
|
|
412
|
+
const sniffHint = entry.globalImplicit ? " The global file looks dotenvx-encrypted; if it is actually plaintext, remove the DOTENV_PUBLIC_KEY / encrypted: markers." : "";
|
|
413
|
+
throw new SourceError(entry.name, source.provider, `[morphix-env] dotenvx source "${entry.name}" could not decrypt or load (${stage}:${category}).${sniffHint}`);
|
|
393
414
|
}
|
|
394
415
|
}
|
|
395
416
|
var keytarModule;
|
|
@@ -576,9 +597,10 @@ function resolveEntries(layers, options) {
|
|
|
576
597
|
...includeGlobal ? Object.entries(layers.global.config.profiles || {}) : [],
|
|
577
598
|
...Object.entries(layers.project.config.profiles || {})
|
|
578
599
|
]);
|
|
600
|
+
const globalEncrypted = isDotenvxEncrypted(layers.globalEnvFile);
|
|
579
601
|
const globalDotenv = includeGlobal ? [{
|
|
580
602
|
name: GLOBAL_DOTENV_SOURCE,
|
|
581
|
-
source: { provider: "local", files: [layers.globalEnvFile], optional: true },
|
|
603
|
+
source: globalEncrypted ? { provider: "dotenvx", files: [layers.globalEnvFile], optional: true } : { provider: "local", files: [layers.globalEnvFile], optional: true },
|
|
582
604
|
baseDir: layers.global.baseDir,
|
|
583
605
|
globalImplicit: true
|
|
584
606
|
}] : [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morphix-env",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Secret composition CLI — Infisical, Doppler, macOS Keychain, dotenvx, and local files stay value owners; composes them into one value-safe child environment and ships a built-in Agent Skill (mx-env skill install).",
|
|
5
5
|
"author": "MorphixAI <dev@morphixai.com>",
|
|
6
6
|
"homepage": "https://github.com/Morphicai/morphix-env#readme",
|