@morphixai/mxenv-core 0.12.0 → 0.12.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.
package/dist/index.d.ts CHANGED
@@ -9,7 +9,11 @@ import * as keytar from 'keytar';
9
9
  */
10
10
  declare function warnDeprecatedBinInvocation(argv1?: string): boolean;
11
11
 
12
- /** 审计目录——读写单一事实源(随命名切换 `~/.mxenv/audit`,未迁移回落旧目录);`audit` 命令读面共用本函数。 */
12
+ /**
13
+ * 审计目录——读写单一事实源(0.12.1 起恒为 `~/.mxenv/audit`,写只落新名)。
14
+ * 旧 `~/.mx-env/audit` 的历史事件随 `mxenv migrate --home` 并入;迁移前
15
+ * `audit tail` 只看新目录(与 writeAudit 同源,避免读写两侧分裂)。
16
+ */
13
17
  declare function auditDir(homeDir?: string): string;
14
18
  /** Best-effort local audit that intentionally has no values or command arguments. */
15
19
  declare function writeAudit(event: AuditEvent, homeDir?: string): void;
@@ -57,19 +61,35 @@ declare function extractPublicVars(environment?: NodeJS.ProcessEnv): Record<stri
57
61
 
58
62
  /**
59
63
  * 全局目录兼容读取(home-migration):`~/.mxenv/` 是命名切换后的家;兼容窗口内
60
- * 它不存在时读回落旧 `~/.mx-env/`(不断供),两目录并存只认新目录。迁移由
61
- * `mxenv migrate --home` 完成(copy → 备份改名 → 值级等价验证 → 失败回滚)。
64
+ * 旧 `~/.mx-env/` 仍作为数据读回落(不断供),两目录都有真实数据时只认新目录。
65
+ * 迁移由 `mxenv migrate --home` 完成(copy → 备份改名 → 值级等价验证 → 失败回滚)。
62
66
  *
63
- * 读写同一解析:兼容窗内写也落旧目录(否则审计/shim 写会先建出 `~/.mxenv/`,
64
- * 令 migrate --home 误判"目标已存在")。未迁移回落触发一次性迁移提示(stderr,
65
- * 值不落输出),migrate --home 自身抑制。
67
+ * 写侧纪律(0.12.1 起):**写只落新名**——audit/shim 一律写 `~/.mxenv/`,哪怕
68
+ * 数据读面还在回落旧目录。为防止"脚手架目录建出即翻转读面/卡死迁移",判定
69
+ * 新目录是否生效改用 hasGlobalData(有无真实数据)而非存在性:
70
+ * - 只有 `audit/`/`shims/` 脚手架的新目录不翻转读面(数据仍在旧目录,不断供);
71
+ * - `migrate --home` 对脚手架-only 目标不再误判 conflict,走合并迁移。
66
72
  */
67
73
  declare const LEGACY_GLOBAL_CONFIG_DIR = ".mx-env";
68
74
  /** migrate --home 自身(及其等价验证)不提示迁移。 */
69
75
  declare function suppressLegacyHomeHint(): void;
70
76
  /** 测试可观测:一次性提示是否已发出。 */
71
77
  declare function legacyHomeHintShown(): boolean;
72
- /** 解析当前生效的全局目录:新目录优先,旧目录兼容回落,皆无则返回新目录(新装)。 */
78
+ /**
79
+ * 新目录是否持有真实数据(配置/值文件等,脚手架除外)。
80
+ * migrate --home 的目标存在性判定与读面翻转共用这一谓词。
81
+ */
82
+ declare function hasGlobalData(homeDir?: string): boolean;
83
+ /**
84
+ * 指定目录内是否持有真实数据。脚手架(audit/shims/迁移标记)、系统垃圾与
85
+ * 点文件(`.env` 除外——它是全局隐式值文件)都不算:`.DS_Store`/编辑器暂存
86
+ * 不得把读面翻转到空新目录(review F2)。
87
+ */
88
+ declare function hasGlobalDataAt(dir: string): boolean;
89
+ /**
90
+ * 解析当前生效的全局数据目录:新目录有真实数据 → 新目录;否则旧目录存在 →
91
+ * 旧目录(一次性迁移提示,每机一次);皆无则返回新目录(新装/脚手架)。
92
+ */
73
93
  declare function resolveGlobalDir(homeDir?: string): string;
74
94
 
75
95
  interface InfisicalConfig {
@@ -176,8 +196,10 @@ declare class ReferenceResolver {
176
196
  * commands cannot drift past it.
177
197
  */
178
198
  declare const RESERVED_SHIM_NAMES: string[];
179
- /** Shims live under the renamed global dir `~/.mxenv/shims`; before `migrate --home` the legacy dir is the active library (home-migration compat read). */
199
+ /** Shims live under the renamed global dir `~/.mxenv/shims`(0.12.1 起写只落新名,不随数据读面回落)。 */
180
200
  declare function shimsDir(homeDir?: string): string;
201
+ /** 旧目录 shim 库(0.12.x 兼容窗):PATH 剥离与 `--remove` 清扫仍须覆盖,防旧 shim 递归。 */
202
+ declare function legacyShimsDir(homeDir?: string): string;
181
203
  /**
182
204
  * Validates one declared shim list. Command names are configuration, not
183
205
  * secrets, so echoing the offending entry is safe. Callers decide the blast
@@ -186,8 +208,9 @@ declare function shimsDir(homeDir?: string): string;
186
208
  */
187
209
  declare function validateShimList(list: unknown, label: string): string[];
188
210
  /**
189
- * Removes every PATH entry that resolves to the shims directory (filter-all,
190
- * so rc files that add it twice are both cleaned). Entries are compared after
211
+ * Removes every PATH entry that resolves to a shims directory — the current
212
+ * `~/.mxenv/shims` **and** the legacy `~/.mx-env/shims` (filter-all, so rc
213
+ * files that add it twice are both cleaned). Entries are compared after
191
214
  * realpath normalization on both sides, case-insensitively on macOS (whose
192
215
  * default filesystem would resolve `Run` to the `run` shim file).
193
216
  */
@@ -232,4 +255,4 @@ type KeytarModule = typeof keytar;
232
255
  declare function loadKeytar(): Promise<KeytarModule | null>;
233
256
  declare function loadSource(entry: SourceEntry, context: SourceContext): Promise<LoadedSource>;
234
257
 
235
- export { AuditEvent, ComposeOptions, ConfigLayers, EnvironmentComposition, GLOBAL_DOTENV_SOURCE, type InfisicalCliError, type InfisicalCliErrorKind, type InfisicalConfig, LEGACY_GLOBAL_CONFIG_DIR, LEGACY_RC_BLOCK_BEGIN, LEGACY_RC_BLOCK_END, LoadConfigOptions, LoadedConfig, LoadedSource, MxEnvConfig, RC_BLOCK_BEGIN, RC_BLOCK_END, RESERVED_SHIM_NAMES, ReferenceResolution, ReferenceResolver, type ResolvedSecrets, SecretSource, SourceContext, SourceEntry, SourceError, UnresolvedReference, auditDir, composeEnvironment, ensureCommand, expandHomePath, extractPublicVars, fetchInfisicalSecrets, fetchSecretsViaCLI, getInfisicalConfig, hasInfisicalCLI, hasRcBlock, isDotenvxEncrypted, isInfisicalLoggedIn, legacyHomeHintShown, loadConfig, loadConfigAt, loadConfigLayers, loadEnvFiles, loadKeytar, loadSource, parseEnvFile, rcBlock, rcBlockPattern, readInfisicalJson, replaceRcBlocks, resolveGlobalDir, resolveInfisicalSecrets, sanitizeBootEnv, shimsDir, stripShimsFromPath, suppressLegacyHomeHint, validateShimList, warnDeprecatedBinInvocation, writeAudit };
258
+ export { AuditEvent, ComposeOptions, ConfigLayers, EnvironmentComposition, GLOBAL_DOTENV_SOURCE, type InfisicalCliError, type InfisicalCliErrorKind, type InfisicalConfig, LEGACY_GLOBAL_CONFIG_DIR, LEGACY_RC_BLOCK_BEGIN, LEGACY_RC_BLOCK_END, LoadConfigOptions, LoadedConfig, LoadedSource, MxEnvConfig, RC_BLOCK_BEGIN, RC_BLOCK_END, RESERVED_SHIM_NAMES, ReferenceResolution, ReferenceResolver, type ResolvedSecrets, SecretSource, SourceContext, SourceEntry, SourceError, UnresolvedReference, auditDir, composeEnvironment, ensureCommand, expandHomePath, extractPublicVars, fetchInfisicalSecrets, fetchSecretsViaCLI, getInfisicalConfig, hasGlobalData, hasGlobalDataAt, hasInfisicalCLI, hasRcBlock, isDotenvxEncrypted, isInfisicalLoggedIn, legacyHomeHintShown, legacyShimsDir, loadConfig, loadConfigAt, loadConfigLayers, loadEnvFiles, loadKeytar, loadSource, parseEnvFile, rcBlock, rcBlockPattern, readInfisicalJson, replaceRcBlocks, resolveGlobalDir, resolveInfisicalSecrets, sanitizeBootEnv, shimsDir, stripShimsFromPath, suppressLegacyHomeHint, validateShimList, warnDeprecatedBinInvocation, writeAudit };
package/dist/index.js CHANGED
@@ -55,6 +55,8 @@ __export(src_exports, {
55
55
  fetchSecretsViaCLI: () => fetchSecretsViaCLI,
56
56
  findMxEnvReferences: () => findMxEnvReferences,
57
57
  getInfisicalConfig: () => getInfisicalConfig,
58
+ hasGlobalData: () => hasGlobalData,
59
+ hasGlobalDataAt: () => hasGlobalDataAt,
58
60
  hasInfisicalCLI: () => hasInfisicalCLI,
59
61
  hasMxEnvReference: () => hasMxEnvReference,
60
62
  hasRcBlock: () => hasRcBlock,
@@ -62,6 +64,7 @@ __export(src_exports, {
62
64
  isInfisicalLoggedIn: () => isInfisicalLoggedIn,
63
65
  isPublicKey: () => isPublicKey,
64
66
  legacyHomeHintShown: () => legacyHomeHintShown,
67
+ legacyShimsDir: () => legacyShimsDir,
65
68
  loadConfig: () => loadConfig,
66
69
  loadConfigAt: () => loadConfigAt,
67
70
  loadConfigLayers: () => loadConfigLayers,
@@ -265,50 +268,18 @@ var StreamingRedactor = class {
265
268
  };
266
269
 
267
270
  // src/audit.ts
268
- var import_fs2 = require("fs");
269
- var import_os2 = require("os");
270
- var import_path3 = require("path");
271
-
272
- // src/global-home.ts
273
271
  var import_fs = require("fs");
274
272
  var import_os = require("os");
275
273
  var import_path2 = require("path");
276
- var LEGACY_GLOBAL_CONFIG_DIR = ".mx-env";
277
- var legacyHintSuppressed = false;
278
- var legacyHintShown = false;
279
- function suppressLegacyHomeHint() {
280
- legacyHintSuppressed = true;
274
+ function auditDir(homeDir = (0, import_os.homedir)()) {
275
+ return (0, import_path2.join)(homeDir, GLOBAL_CONFIG_DIR, "audit");
281
276
  }
282
- function legacyHomeHintShown() {
283
- return legacyHintShown;
284
- }
285
- function resolveGlobalDir(homeDir = (0, import_os.homedir)()) {
286
- const next = (0, import_path2.resolve)(homeDir, GLOBAL_CONFIG_DIR);
287
- const legacy = (0, import_path2.resolve)(homeDir, LEGACY_GLOBAL_CONFIG_DIR);
288
- if ((0, import_fs.existsSync)(next)) return next;
289
- if ((0, import_fs.existsSync)(legacy)) {
290
- if (!legacyHintSuppressed && !legacyHintShown) {
291
- legacyHintShown = true;
292
- process.stderr.write(
293
- `[mxenv] using legacy global directory ${legacy} \u2014 run \`mxenv migrate --home\` to move it to ${next}.
294
- `
295
- );
296
- }
297
- return legacy;
298
- }
299
- return next;
300
- }
301
-
302
- // src/audit.ts
303
- function auditDir(homeDir = (0, import_os2.homedir)()) {
304
- return (0, import_path3.join)(resolveGlobalDir(homeDir), "audit");
305
- }
306
- function writeAudit(event, homeDir = (0, import_os2.homedir)()) {
277
+ function writeAudit(event, homeDir = (0, import_os.homedir)()) {
307
278
  try {
308
279
  const dir = auditDir(homeDir);
309
- (0, import_fs2.mkdirSync)(dir, { recursive: true, mode: 448 });
280
+ (0, import_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
310
281
  const day = event.timestamp.slice(0, 10);
311
- (0, import_fs2.appendFileSync)((0, import_path3.join)(dir, `${day}.jsonl`), `${JSON.stringify(event)}
282
+ (0, import_fs.appendFileSync)((0, import_path2.join)(dir, `${day}.jsonl`), `${JSON.stringify(event)}
312
283
  `, { mode: 384 });
313
284
  } catch {
314
285
  }
@@ -318,17 +289,17 @@ function writeAudit(event, homeDir = (0, import_os2.homedir)()) {
318
289
  var import_os4 = require("os");
319
290
 
320
291
  // src/env.ts
321
- var import_fs3 = require("fs");
322
- var import_path4 = require("path");
292
+ var import_fs2 = require("fs");
293
+ var import_path3 = require("path");
323
294
  var import_dotenv = require("dotenv");
324
295
  function parseEnvFile(filePath) {
325
- const absPath = (0, import_path4.resolve)(filePath);
326
- if (!(0, import_fs3.existsSync)(absPath)) return {};
327
- return (0, import_dotenv.parse)((0, import_fs3.readFileSync)(absPath, "utf8"));
296
+ const absPath = (0, import_path3.resolve)(filePath);
297
+ if (!(0, import_fs2.existsSync)(absPath)) return {};
298
+ return (0, import_dotenv.parse)((0, import_fs2.readFileSync)(absPath, "utf8"));
328
299
  }
329
300
  function isDotenvxEncrypted(filePath) {
330
301
  try {
331
- const content = (0, import_fs3.readFileSync)((0, import_path4.resolve)(filePath), "utf8");
302
+ const content = (0, import_fs2.readFileSync)((0, import_path3.resolve)(filePath), "utf8");
332
303
  return content.includes("DOTENV_PUBLIC_KEY") && /^[A-Za-z_][A-Za-z0-9_.]*="?encrypted:/m.test(content);
333
304
  } catch {
334
305
  return false;
@@ -337,9 +308,9 @@ function isDotenvxEncrypted(filePath) {
337
308
  function loadEnvFiles(files) {
338
309
  const overrides = [];
339
310
  for (const file of files) {
340
- const absPath = (0, import_path4.resolve)(file);
341
- if (!(0, import_fs3.existsSync)(absPath)) continue;
342
- const parsed = (0, import_dotenv.parse)((0, import_fs3.readFileSync)(absPath, "utf8"));
311
+ const absPath = (0, import_path3.resolve)(file);
312
+ if (!(0, import_fs2.existsSync)(absPath)) continue;
313
+ const parsed = (0, import_dotenv.parse)((0, import_fs2.readFileSync)(absPath, "utf8"));
343
314
  if (parsed) {
344
315
  Object.assign(process.env, parsed);
345
316
  for (const key of Object.keys(parsed)) {
@@ -370,6 +341,63 @@ var import_dotenv3 = require("dotenv");
370
341
  var import_fs4 = require("fs");
371
342
  var import_path5 = require("path");
372
343
  var import_os3 = require("os");
344
+
345
+ // src/global-home.ts
346
+ var import_fs3 = require("fs");
347
+ var import_os2 = require("os");
348
+ var import_path4 = require("path");
349
+ var LEGACY_GLOBAL_CONFIG_DIR = ".mx-env";
350
+ var GLOBAL_SCAFFOLDING = /* @__PURE__ */ new Set(["audit", "shims", ".mxenv-verify-pending"]);
351
+ var JUNK_NAMES = /* @__PURE__ */ new Set(["Thumbs.db", "desktop.ini"]);
352
+ var HINT_MARKER_NAME = ".mxenv-legacy-hint-shown";
353
+ var legacyHintSuppressed = false;
354
+ var legacyHintShown = false;
355
+ function suppressLegacyHomeHint() {
356
+ legacyHintSuppressed = true;
357
+ }
358
+ function legacyHomeHintShown() {
359
+ return legacyHintShown;
360
+ }
361
+ function hasGlobalData(homeDir = (0, import_os2.homedir)()) {
362
+ return hasGlobalDataAt((0, import_path4.resolve)(homeDir, GLOBAL_CONFIG_DIR));
363
+ }
364
+ function hasGlobalDataAt(dir) {
365
+ let entries;
366
+ try {
367
+ entries = (0, import_fs3.readdirSync)(dir);
368
+ } catch {
369
+ return false;
370
+ }
371
+ return entries.some(
372
+ (name) => !GLOBAL_SCAFFOLDING.has(name) && !JUNK_NAMES.has(name) && (!name.startsWith(".") || name === ".env")
373
+ );
374
+ }
375
+ function resolveGlobalDir(homeDir = (0, import_os2.homedir)()) {
376
+ const next = (0, import_path4.resolve)(homeDir, GLOBAL_CONFIG_DIR);
377
+ const legacy = (0, import_path4.resolve)(homeDir, LEGACY_GLOBAL_CONFIG_DIR);
378
+ if (hasGlobalDataAt(next)) return next;
379
+ if ((0, import_fs3.existsSync)(legacy)) {
380
+ hintLegacyHomeOnce(legacy, next);
381
+ return legacy;
382
+ }
383
+ return next;
384
+ }
385
+ function hintLegacyHomeOnce(legacy, next) {
386
+ if (legacyHintSuppressed || legacyHintShown) return;
387
+ const marker = (0, import_path4.resolve)(legacy, HINT_MARKER_NAME);
388
+ try {
389
+ if ((0, import_fs3.existsSync)(marker)) return;
390
+ (0, import_fs3.writeFileSync)(marker, "", { mode: 384 });
391
+ } catch {
392
+ }
393
+ legacyHintShown = true;
394
+ process.stderr.write(
395
+ `[mxenv] using legacy global directory ${legacy} \u2014 run \`mxenv migrate --home\` to move it to ${next}.
396
+ `
397
+ );
398
+ }
399
+
400
+ // src/config.ts
373
401
  function safeParseConfig(path) {
374
402
  try {
375
403
  const value = JSON.parse((0, import_fs4.readFileSync)(path, "utf8"));
@@ -1037,7 +1065,10 @@ var RESERVED_SHIM_NAMES = [
1037
1065
  ];
1038
1066
  var SHIM_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
1039
1067
  function shimsDir(homeDir = (0, import_os5.homedir)()) {
1040
- return (0, import_path8.join)(resolveGlobalDir(homeDir), "shims");
1068
+ return (0, import_path8.join)(homeDir, GLOBAL_CONFIG_DIR, "shims");
1069
+ }
1070
+ function legacyShimsDir(homeDir = (0, import_os5.homedir)()) {
1071
+ return (0, import_path8.join)(homeDir, LEGACY_GLOBAL_CONFIG_DIR, "shims");
1041
1072
  }
1042
1073
  function validateShimList(list, label) {
1043
1074
  if (list === void 0 || list === null) return [];
@@ -1068,13 +1099,16 @@ function validateShimList(list, label) {
1068
1099
  }
1069
1100
  function stripShimsFromPath(pathValue, homeDir = (0, import_os5.homedir)()) {
1070
1101
  if (!pathValue) return pathValue;
1071
- const target = shimsDir(homeDir);
1072
- let targetReal = target;
1073
- try {
1074
- targetReal = (0, import_fs7.realpathSync)(target);
1075
- } catch {
1076
- }
1077
1102
  const equal = (a, b) => process.platform === "darwin" ? a.toLowerCase() === b.toLowerCase() : a === b;
1103
+ const targets = [shimsDir(homeDir), legacyShimsDir(homeDir)];
1104
+ const targetPairs = targets.map((target) => {
1105
+ let real = target;
1106
+ try {
1107
+ real = (0, import_fs7.realpathSync)(target);
1108
+ } catch {
1109
+ }
1110
+ return [target, real];
1111
+ });
1078
1112
  const entries = pathValue.split(import_path8.delimiter).filter((entry) => {
1079
1113
  if (!entry) return true;
1080
1114
  const literal = entry.replace(/\/+$/, "");
@@ -1083,7 +1117,7 @@ function stripShimsFromPath(pathValue, homeDir = (0, import_os5.homedir)()) {
1083
1117
  resolved = (0, import_fs7.realpathSync)(literal);
1084
1118
  } catch {
1085
1119
  }
1086
- return !equal(literal, target) && !equal(resolved, targetReal);
1120
+ return !targetPairs.some(([target, real]) => equal(literal, target) || equal(resolved, real));
1087
1121
  });
1088
1122
  return entries.join(import_path8.delimiter);
1089
1123
  }
@@ -1156,6 +1190,8 @@ function escapeRegExp(text) {
1156
1190
  fetchSecretsViaCLI,
1157
1191
  findMxEnvReferences,
1158
1192
  getInfisicalConfig,
1193
+ hasGlobalData,
1194
+ hasGlobalDataAt,
1159
1195
  hasInfisicalCLI,
1160
1196
  hasMxEnvReference,
1161
1197
  hasRcBlock,
@@ -1163,6 +1199,7 @@ function escapeRegExp(text) {
1163
1199
  isInfisicalLoggedIn,
1164
1200
  isPublicKey,
1165
1201
  legacyHomeHintShown,
1202
+ legacyShimsDir,
1166
1203
  loadConfig,
1167
1204
  loadConfigAt,
1168
1205
  loadConfigLayers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphixai/mxenv-core",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "L0 contracts + L1 shared capabilities for mxenv — composition engine, providers, value-safe compose (revealSingle 归 Wave B).",
5
5
  "license": "MIT",
6
6
  "author": "MorphixAI <dev@morphixai.com>",