@modootoday/envs 0.1.1 → 0.2.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 CHANGED
@@ -61,14 +61,14 @@ used it, most of this is already familiar.
61
61
 
62
62
  ## Documentation
63
63
 
64
- | | |
65
- | --- | --- |
66
- | Getting started | <https://envs.build/guide/> |
67
- | The format rule, measured against dotenv | <https://envs.build/format/> |
68
- | Recovery codes | <https://envs.build/recovery/> |
69
- | How it compares to dotenv, dotenvx, Doppler and Infisical | <https://envs.build/compare/> |
70
- | Sharing a catalog, and what the server cannot see | <https://envs.build/hosted/> |
71
- | All 28 commands | <https://envs.build/commands/> |
64
+ | | |
65
+ | --------------------------------------------------------- | ------------------------------ |
66
+ | Getting started | <https://envs.build/guide/> |
67
+ | The format rule, measured against dotenv | <https://envs.build/format/> |
68
+ | Recovery codes | <https://envs.build/recovery/> |
69
+ | How it compares to dotenv, dotenvx, Doppler and Infisical | <https://envs.build/compare/> |
70
+ | Sharing a catalog, and what the server cannot see | <https://envs.build/hosted/> |
71
+ | All 28 commands | <https://envs.build/commands/> |
72
72
 
73
73
  ## Requirements
74
74
 
@@ -264,7 +264,7 @@ function printHelp(ui, commands, notImplemented) {
264
264
  }
265
265
 
266
266
  // src/cli/version.ts
267
- var VERSION = true ? "0.1.1" : "0.0.0-source";
267
+ var VERSION = true ? "0.2.0" : "0.0.0-source";
268
268
 
269
269
  // src/commands/add.ts
270
270
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
@@ -2527,6 +2527,62 @@ function conflicts(entries) {
2527
2527
  }
2528
2528
  return findings;
2529
2529
  }
2530
+ var MIN_TWIN_LENGTH = 12;
2531
+ function twins(entries) {
2532
+ const byValue = /* @__PURE__ */ new Map();
2533
+ for (const entry of entries) {
2534
+ if (entry.value.length < MIN_TWIN_LENGTH) continue;
2535
+ byValue.set(entry.value, [...byValue.get(entry.value) ?? [], entry]);
2536
+ }
2537
+ const findings = [];
2538
+ const reported = /* @__PURE__ */ new Set();
2539
+ for (const group of byValue.values()) {
2540
+ const names = [...new Set(group.map((entry) => entry.key))].sort();
2541
+ if (names.length < 2) continue;
2542
+ const pair = names.join(" and ");
2543
+ if (reported.has(pair)) continue;
2544
+ reported.add(pair);
2545
+ findings.push({
2546
+ level: "warn",
2547
+ what: names[0],
2548
+ // The value is never printed, here least of all: the finding is that one
2549
+ // secret is in two places, and naming both places is the whole answer.
2550
+ detail: `holds the same value as ${names.slice(1).join(", ")} \u2014 one secret in two names, free to drift apart`
2551
+ });
2552
+ }
2553
+ return findings;
2554
+ }
2555
+ function exampleDrift(root, keys) {
2556
+ const findings = [];
2557
+ const known = new Set(keys);
2558
+ for (const [file, parse] of [
2559
+ [".env.example", (text) => namesFromEnv(text)],
2560
+ ["envs.requires", (text) => namesFromList(text)]
2561
+ ]) {
2562
+ const path = join3(root, file);
2563
+ if (!existsSync10(path)) continue;
2564
+ const listed = new Set(parse(readFileSync7(path, "utf8")));
2565
+ const missing = [...known].filter((key) => !listed.has(key)).sort();
2566
+ const stale = [...listed].filter((key) => !known.has(key)).sort();
2567
+ if (missing.length > 0) {
2568
+ findings.push({
2569
+ level: "warn",
2570
+ what: file,
2571
+ detail: `does not list ${missing.join(", ")} \u2014 someone starting from it begins short`
2572
+ });
2573
+ }
2574
+ if (stale.length > 0) {
2575
+ findings.push({
2576
+ level: "warn",
2577
+ what: file,
2578
+ detail: `still lists ${stale.join(", ")} \u2014 nothing reads those now`
2579
+ });
2580
+ }
2581
+ }
2582
+ return findings;
2583
+ }
2584
+ var namesFromEnv = (text) => text.split("\n").map((line) => /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)).filter((match) => match !== null).map((match) => match[1]);
2585
+ var namesFromList = (text) => text.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
2530
2586
  function globalOnly(entries) {
2531
2587
  const inProject = new Set(
2532
2588
  entries.filter((e) => e.layer === "project").map((e) => e.key)
@@ -2718,6 +2774,11 @@ var doctorCommand = {
2718
2774
  const findings = [
2719
2775
  ...catalogIgnored(located.project, located.projectRoot),
2720
2776
  ...conflicts(entries),
2777
+ ...twins(entries),
2778
+ ...exampleDrift(
2779
+ located.projectRoot ?? cwd,
2780
+ entries.map((entry) => entry.key)
2781
+ ),
2721
2782
  ...globalOnly(entries),
2722
2783
  ...ghosts(entries),
2723
2784
  ...templateFindings(
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  dispatch
4
- } from "./chunk-NQHT5UCU.js";
4
+ } from "./chunk-GPWDRKCY.js";
5
5
  import "./chunk-STHFIZRP.js";
6
6
 
7
7
  // src/cli.ts
package/dist/index.cjs CHANGED
@@ -1574,7 +1574,7 @@ function printHelp(ui, commands, notImplemented) {
1574
1574
  }
1575
1575
 
1576
1576
  // src/cli/version.ts
1577
- var VERSION = true ? "0.1.1" : "0.0.0-source";
1577
+ var VERSION = true ? "0.2.0" : "0.0.0-source";
1578
1578
 
1579
1579
  // src/commands/add.ts
1580
1580
  var import_node_fs5 = require("fs");
@@ -3811,6 +3811,62 @@ function conflicts(entries) {
3811
3811
  }
3812
3812
  return findings;
3813
3813
  }
3814
+ var MIN_TWIN_LENGTH = 12;
3815
+ function twins(entries) {
3816
+ const byValue = /* @__PURE__ */ new Map();
3817
+ for (const entry of entries) {
3818
+ if (entry.value.length < MIN_TWIN_LENGTH) continue;
3819
+ byValue.set(entry.value, [...byValue.get(entry.value) ?? [], entry]);
3820
+ }
3821
+ const findings = [];
3822
+ const reported = /* @__PURE__ */ new Set();
3823
+ for (const group of byValue.values()) {
3824
+ const names = [...new Set(group.map((entry) => entry.key))].sort();
3825
+ if (names.length < 2) continue;
3826
+ const pair = names.join(" and ");
3827
+ if (reported.has(pair)) continue;
3828
+ reported.add(pair);
3829
+ findings.push({
3830
+ level: "warn",
3831
+ what: names[0],
3832
+ // The value is never printed, here least of all: the finding is that one
3833
+ // secret is in two places, and naming both places is the whole answer.
3834
+ detail: `holds the same value as ${names.slice(1).join(", ")} \u2014 one secret in two names, free to drift apart`
3835
+ });
3836
+ }
3837
+ return findings;
3838
+ }
3839
+ function exampleDrift(root, keys) {
3840
+ const findings = [];
3841
+ const known = new Set(keys);
3842
+ for (const [file, parse] of [
3843
+ [".env.example", (text) => namesFromEnv(text)],
3844
+ ["envs.requires", (text) => namesFromList(text)]
3845
+ ]) {
3846
+ const path = (0, import_node_path7.join)(root, file);
3847
+ if (!(0, import_node_fs15.existsSync)(path)) continue;
3848
+ const listed = new Set(parse((0, import_node_fs15.readFileSync)(path, "utf8")));
3849
+ const missing = [...known].filter((key) => !listed.has(key)).sort();
3850
+ const stale = [...listed].filter((key) => !known.has(key)).sort();
3851
+ if (missing.length > 0) {
3852
+ findings.push({
3853
+ level: "warn",
3854
+ what: file,
3855
+ detail: `does not list ${missing.join(", ")} \u2014 someone starting from it begins short`
3856
+ });
3857
+ }
3858
+ if (stale.length > 0) {
3859
+ findings.push({
3860
+ level: "warn",
3861
+ what: file,
3862
+ detail: `still lists ${stale.join(", ")} \u2014 nothing reads those now`
3863
+ });
3864
+ }
3865
+ }
3866
+ return findings;
3867
+ }
3868
+ var namesFromEnv = (text) => text.split("\n").map((line) => /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)).filter((match) => match !== null).map((match) => match[1]);
3869
+ var namesFromList = (text) => text.split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
3814
3870
  function globalOnly(entries) {
3815
3871
  const inProject = new Set(
3816
3872
  entries.filter((e) => e.layer === "project").map((e) => e.key)
@@ -4002,6 +4058,11 @@ var doctorCommand = {
4002
4058
  const findings = [
4003
4059
  ...catalogIgnored(located.project, located.projectRoot),
4004
4060
  ...conflicts(entries),
4061
+ ...twins(entries),
4062
+ ...exampleDrift(
4063
+ located.projectRoot ?? cwd,
4064
+ entries.map((entry) => entry.key)
4065
+ ),
4005
4066
  ...globalOnly(entries),
4006
4067
  ...ghosts(entries),
4007
4068
  ...templateFindings(
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  csvField,
6
6
  dispatch,
7
7
  exportValues
8
- } from "./chunk-NQHT5UCU.js";
8
+ } from "./chunk-GPWDRKCY.js";
9
9
  import {
10
10
  CatalogVersionError,
11
11
  ConflictError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/envs",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "license": "Elastic-2.0",
5
5
  "author": "modootoday",
6
6
  "description": "Environment values in an encrypted catalog rather than scattered files — provenance, releases, rollback and a strict .env format gate. dotenv-compatible surface.",