envprism 0.0.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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/bin/envprism.mjs +121 -0
- package/dist/bin/envprism.mjs.map +1 -0
- package/dist/chunks/app-d_wGTwvJ.mjs +1701 -0
- package/dist/chunks/app-d_wGTwvJ.mjs.map +1 -0
- package/dist/chunks/mask-Bv9W6Ei8.mjs +71 -0
- package/dist/chunks/mask-Bv9W6Ei8.mjs.map +1 -0
- package/dist/chunks/matrix-nBq_GY2v.mjs +421 -0
- package/dist/chunks/matrix-nBq_GY2v.mjs.map +1 -0
- package/dist/index.mjs +16 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +88 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Titus Kirch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ป envprism
|
|
4
|
+
|
|
5
|
+
**One set of variables, refracted into many environment views โ a TUI for managing `.env*` files side by side**
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bunx envprism
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it. Point `envprism` at a directory containing `.env*` files and it opens a side-by-side matrix view: rows are variable keys, columns are files. Differences light up, missing keys are obvious, and you can edit cells in place โ comments, blank lines, and key order survive the round trip.
|
|
18
|
+
|
|
19
|
+
> [!IMPORTANT]
|
|
20
|
+
> `envprism` runs on **[Bun](https://bun.sh/)** 1.3+. The TUI is powered by [opentui](https://opentui.com/), which links to a native Zig core via `bun:ffi`. Node has no equivalent built-in FFI, so `npx envprism` will not work โ install Bun first.
|
|
21
|
+
|
|
22
|
+
## โจ Features
|
|
23
|
+
|
|
24
|
+
### Discovery & comparison
|
|
25
|
+
|
|
26
|
+
- **๐ Auto-discovery** โ finds every `.env*` file in the current directory (or in `--paths a b c`); skips editor swap files and backups.
|
|
27
|
+
- **๐งฎ Matrix view** โ rows are the union of every variable, columns are the files; the prism metaphor, literal.
|
|
28
|
+
- **๐ฏ Smart base resolution** โ `.env.example` is auto-promoted to base if present, otherwise the alphabetically-first file. Override with `--base file`.
|
|
29
|
+
- **๐จ Per-cell diff icons** โ `โ ` value differs (yellow), `โ missing` (red), `โ
` extra (key not in base, yellow), `โ ` placeholder (orange) โ only the icon is coloured, values stay neutral.
|
|
30
|
+
- **๐ Secret masking** โ keys whose name contains `TOKEN`, `SECRET`, `PASSWORD`, `KEY`, `PRIVATE`, โฆ render as `โขโขโขโข (N)`; allow-list for `PUBLIC_*` and `*_ID`.
|
|
31
|
+
- **๐ต๏ธ Placeholder detection** โ values like `TODO`, `FIXME`, `CHANGEME`, `xxx`, `your_secret_here`, `replace_me` flag a `โ ` so you know a secret was never filled in.
|
|
32
|
+
- **๐ Section grouping** โ comment banners like `# === Database ===` (inline or three-line block, with `=`, `-`, `~`, `*`, or `#` separators) become section dividers; `g` toggles to grouping by key prefix (`APP_*`, `DB_*`).
|
|
33
|
+
- **๐ช Collapsible sections** โ `c` folds the focused section; section dividers show drift count (`โ 2 missing ยท โ 3/5 drift`) so you scan the worst groups first; `Shift-C` expands all.
|
|
34
|
+
- **๐ Live filter** โ `/` opens a popover that filters keys by substring, with a `matching N of M` counter.
|
|
35
|
+
- **๐ฃ๏ธ Drift-only view** โ `v` hides keys that already agree with the base, so only the work-to-do remains.
|
|
36
|
+
|
|
37
|
+
### Editing & write-back
|
|
38
|
+
|
|
39
|
+
- **โ๏ธ Edit-or-add** โ `e` / `Enter` opens an edit popover on any cell; if the key isn't in that file yet, save creates it. The popover renders every file's current value as context next to the input.
|
|
40
|
+
- **โ Add variable** โ `a` walks key + value across two prompts; the parser's `[A-Za-z_][A-Za-z0-9_]*` rule guards the key name.
|
|
41
|
+
- **โ Delete variable** โ `d` removes the focused key from the focused file.
|
|
42
|
+
- **๐ New `.env*` file** โ `n` scaffolds a new file next to the base; saved with the rest on `Ctrl-S`.
|
|
43
|
+
- **๐ Sync-to-all** โ `=` copies the focused cell's value into every file (create or update); `Ctrl-A` inside the edit popover applies what you're typing to every file at once.
|
|
44
|
+
- **๐ข Modified marker** โ every cell you touch this session gets a green `โ`; clears on save so unsaved local work stands out from "this file just disagrees with base".
|
|
45
|
+
- **โฉ๏ธ Undo** โ `Ctrl-Z` walks back the last 50 edits / adds / deletes.
|
|
46
|
+
- **๐พ Save** โ `Ctrl-S` writes every dirty file. **Round-trip preserving**: comments, blank lines, key order, quoting, `export ` prefixes, and inline `#` comments survive byte-for-byte โ only the keys you changed are rewritten.
|
|
47
|
+
- **โ ๏ธ Quit guard** โ `q` asks once before quitting if you have unsaved changes; `Ctrl-C` always force-quits.
|
|
48
|
+
|
|
49
|
+
### Navigation & panes
|
|
50
|
+
|
|
51
|
+
- **๐งญ Two panes** โ `Tab` switches between the matrix and the files sidebar; arrow-left at the leftmost matrix column hops into the sidebar.
|
|
52
|
+
- **โ๏ธ Enable / disable files** โ `Space` in the sidebar drops a file out of the matrix (without deleting from disk); `b` promotes the selected file to base (auto-enables it if disabled).
|
|
53
|
+
- **๐ฑ๏ธ Mouse** โ wheel scrolls the matrix in both axes; the focused cell auto-scrolls into view when you reach the viewport edge.
|
|
54
|
+
- **โ Help overlay** โ `?` (or `ร` for QWERTZ) opens a keybinding reference; renders as a two-column grid on wide terminals and a single scrollable column on narrow / short ones.
|
|
55
|
+
|
|
56
|
+
### CI & scripting
|
|
57
|
+
|
|
58
|
+
- **๐งช `envprism diff`** โ non-interactive subcommand that prints a text drift table, JSON (`--json`), or just sets the exit code (`--check`). Drop it into a pre-commit hook or CI to fail builds that drift from `.env.example`.
|
|
59
|
+
|
|
60
|
+
## ๐ฆ Installation
|
|
61
|
+
|
|
62
|
+
Install Bun (one-time): see [bun.sh](https://bun.sh/).
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bun add -g envprism
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Or run without installing:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bunx envprism
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Requirements: **Bun 1.3+**.
|
|
75
|
+
|
|
76
|
+
## ๐ Quick start
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bunx envprism # open the TUI in the current directory
|
|
80
|
+
bunx envprism tui path/to/repo # TUI scanning another directory
|
|
81
|
+
bunx envprism diff path/to/repo # non-interactive drift report
|
|
82
|
+
bunx envprism diff --json | jq # structured drift report
|
|
83
|
+
bunx envprism diff --check; echo $? # exits 1 if any file drifts from base
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Inside the TUI, press `?` for the full keybinding reference.
|
|
87
|
+
|
|
88
|
+
## ๐ค Contributing
|
|
89
|
+
|
|
90
|
+
PRs welcome. Conventional Commits required (enforced via commitlint). Husky runs the project's linters/formatters on `git commit`.
|
|
91
|
+
|
|
92
|
+
> [!TIP]
|
|
93
|
+
> Run `pnpm check:fix` before pushing โ CI will catch what husky missed.
|
|
94
|
+
|
|
95
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.
|
|
96
|
+
|
|
97
|
+
## ๐ฃ๏ธ Versioning
|
|
98
|
+
|
|
99
|
+
[Semantic Versioning](https://semver.org/) via [release-please](https://github.com/googleapis/release-please) โ see [CHANGELOG.md](CHANGELOG.md).
|
|
100
|
+
|
|
101
|
+
## ๐ License
|
|
102
|
+
|
|
103
|
+
[MIT](LICENSE) ยฉ [Titus Kirch](https://github.com/TitusKirch/) / [IT-Dienstleistungen Titus Kirch](https://kirch.dev)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { defineCommand, runMain } from "citty";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { resolve } from "pathe";
|
|
5
|
+
import { d as discoverEnvFiles, r as resolveBase, b as buildMatrix, c as computeDiff, f as formatDiffText } from "../chunks/matrix-nBq_GY2v.mjs";
|
|
6
|
+
const diffCommand = defineCommand({
|
|
7
|
+
meta: {
|
|
8
|
+
name: "diff",
|
|
9
|
+
description: "Print the drift between .env* files compared to a base."
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
paths: {
|
|
13
|
+
type: "positional",
|
|
14
|
+
required: false,
|
|
15
|
+
description: "Directory or files to scan (defaults to cwd)."
|
|
16
|
+
},
|
|
17
|
+
base: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Base file to diff against (defaults to .env.example)."
|
|
20
|
+
},
|
|
21
|
+
json: {
|
|
22
|
+
type: "boolean",
|
|
23
|
+
description: "Emit JSON instead of a text table."
|
|
24
|
+
},
|
|
25
|
+
check: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "Suppress output and exit 1 when any file drifts from the base."
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async run({ args }) {
|
|
31
|
+
const inputs = collectPaths$1(args.paths);
|
|
32
|
+
const files = await discoverEnvFiles(inputs.map((p) => resolve(p)));
|
|
33
|
+
if (files.length === 0) {
|
|
34
|
+
if (!args.check) consola.warn("No .env* files found.");
|
|
35
|
+
process.exit(args.check ? 0 : 1);
|
|
36
|
+
}
|
|
37
|
+
const base = resolveBase(files, args.base);
|
|
38
|
+
if (!base) {
|
|
39
|
+
consola.error("Could not resolve a base file.");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const matrix = buildMatrix(files, base);
|
|
43
|
+
const report = computeDiff(matrix);
|
|
44
|
+
if (args.check) {
|
|
45
|
+
process.exit(report.inSync ? 0 : 1);
|
|
46
|
+
}
|
|
47
|
+
if (args.json) {
|
|
48
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(formatDiffText(report));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
function collectPaths$1(arg) {
|
|
55
|
+
if (Array.isArray(arg)) return arg.map(String);
|
|
56
|
+
if (typeof arg === "string" && arg.length > 0) return [arg];
|
|
57
|
+
return ["."];
|
|
58
|
+
}
|
|
59
|
+
async function runTui(args) {
|
|
60
|
+
const inputs = collectPaths(args.paths);
|
|
61
|
+
const files = await discoverEnvFiles(inputs.map((p) => resolve(p)));
|
|
62
|
+
if (files.length === 0) {
|
|
63
|
+
consola.warn("No .env* files found.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const base = resolveBase(files, args.base);
|
|
67
|
+
if (!base) {
|
|
68
|
+
consola.error("Could not resolve a base file.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const matrix = buildMatrix(files, base);
|
|
72
|
+
const { runMatrixTui } = await import("../chunks/app-d_wGTwvJ.mjs");
|
|
73
|
+
await runMatrixTui(matrix);
|
|
74
|
+
}
|
|
75
|
+
const tuiCommand = defineCommand({
|
|
76
|
+
meta: {
|
|
77
|
+
name: "tui",
|
|
78
|
+
description: "Open the interactive matrix view (default command)."
|
|
79
|
+
},
|
|
80
|
+
args: {
|
|
81
|
+
paths: {
|
|
82
|
+
type: "positional",
|
|
83
|
+
required: false,
|
|
84
|
+
description: "Directory or files to scan (defaults to cwd)."
|
|
85
|
+
},
|
|
86
|
+
base: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "Base file to diff against (defaults to .env.example)."
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
async run({ args }) {
|
|
92
|
+
await runTui({ paths: args.paths, base: args.base });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
function collectPaths(arg) {
|
|
96
|
+
if (Array.isArray(arg)) return arg.map(String);
|
|
97
|
+
if (typeof arg === "string" && arg.length > 0) return [arg];
|
|
98
|
+
return ["."];
|
|
99
|
+
}
|
|
100
|
+
const SUBCOMMANDS = /* @__PURE__ */ new Set(["tui", "diff"]);
|
|
101
|
+
const rootCommand = defineCommand({
|
|
102
|
+
meta: {
|
|
103
|
+
name: "envprism",
|
|
104
|
+
description: "TUI-based env file manager โ refract one set of variables into many environment views."
|
|
105
|
+
},
|
|
106
|
+
subCommands: {
|
|
107
|
+
tui: tuiCommand,
|
|
108
|
+
diff: diffCommand
|
|
109
|
+
},
|
|
110
|
+
// citty 0.1.6 invokes the root `run` after a matched subcommand returns,
|
|
111
|
+
// so guard against double-execution by bailing when the first positional
|
|
112
|
+
// arg names a known subcommand. With no args at all we launch the TUI in
|
|
113
|
+
// the cwd, which is the documented default.
|
|
114
|
+
async run({ rawArgs }) {
|
|
115
|
+
const firstPositional = rawArgs.find((a) => !a.startsWith("-"));
|
|
116
|
+
if (firstPositional && SUBCOMMANDS.has(firstPositional)) return;
|
|
117
|
+
await runTui({});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
runMain(rootCommand);
|
|
121
|
+
//# sourceMappingURL=envprism.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envprism.mjs","sources":["../../src/commands/diff.ts","../../src/commands/tui.ts","../../src/cli.ts","../../src/bin/envprism.ts"],"sourcesContent":["import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { resolve } from 'pathe';\nimport { resolveBase } from '../core/base.ts';\nimport { computeDiff, formatDiffText } from '../core/diff.ts';\nimport { discoverEnvFiles } from '../core/discover.ts';\nimport { buildMatrix } from '../core/matrix.ts';\n\nexport const diffCommand = defineCommand({\n meta: {\n name: 'diff',\n description: 'Print the drift between .env* files compared to a base.'\n },\n args: {\n paths: {\n type: 'positional',\n required: false,\n description: 'Directory or files to scan (defaults to cwd).'\n },\n base: {\n type: 'string',\n description: 'Base file to diff against (defaults to .env.example).'\n },\n json: {\n type: 'boolean',\n description: 'Emit JSON instead of a text table.'\n },\n check: {\n type: 'boolean',\n description:\n 'Suppress output and exit 1 when any file drifts from the base.'\n }\n },\n async run({ args }) {\n const inputs = collectPaths(args.paths);\n const files = await discoverEnvFiles(inputs.map((p) => resolve(p)));\n\n if (files.length === 0) {\n if (!args.check) consola.warn('No .env* files found.');\n process.exit(args.check ? 0 : 1);\n }\n\n const base = resolveBase(files, args.base);\n if (!base) {\n consola.error('Could not resolve a base file.');\n process.exit(1);\n }\n\n const matrix = buildMatrix(files, base);\n const report = computeDiff(matrix);\n\n if (args.check) {\n process.exit(report.inSync ? 0 : 1);\n }\n\n if (args.json) {\n process.stdout.write(JSON.stringify(report, null, 2) + '\\n');\n return;\n }\n\n process.stdout.write(formatDiffText(report));\n }\n});\n\nfunction collectPaths(arg: unknown): string[] {\n if (Array.isArray(arg)) return arg.map(String);\n if (typeof arg === 'string' && arg.length > 0) return [arg];\n return ['.'];\n}\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { resolve } from 'pathe';\nimport { resolveBase } from '../core/base.ts';\nimport { discoverEnvFiles } from '../core/discover.ts';\nimport { buildMatrix } from '../core/matrix.ts';\n\ninterface TuiInvocation {\n paths?: string | string[];\n base?: string;\n}\n\n/**\n * Resolve the matrix and boot the TUI. Shared between the explicit `tui`\n * subcommand and the default-no-subcommand path in `rootCommand`.\n */\nexport async function runTui(args: TuiInvocation): Promise<void> {\n const inputs = collectPaths(args.paths);\n const files = await discoverEnvFiles(inputs.map((p) => resolve(p)));\n\n if (files.length === 0) {\n consola.warn('No .env* files found.');\n process.exit(1);\n }\n\n const base = resolveBase(files, args.base);\n if (!base) {\n consola.error('Could not resolve a base file.');\n process.exit(1);\n }\n\n const matrix = buildMatrix(files, base);\n\n // Dynamic import keeps @opentui/core out of the bundle path used by the\n // `diff` subcommand, so users running diff on Node never hit Bun-only code.\n const { runMatrixTui } = await import('../tui/app.ts');\n await runMatrixTui(matrix);\n}\n\nexport const tuiCommand = defineCommand({\n meta: {\n name: 'tui',\n description: 'Open the interactive matrix view (default command).'\n },\n args: {\n paths: {\n type: 'positional',\n required: false,\n description: 'Directory or files to scan (defaults to cwd).'\n },\n base: {\n type: 'string',\n description: 'Base file to diff against (defaults to .env.example).'\n }\n },\n async run({ args }) {\n await runTui({ paths: args.paths, base: args.base });\n }\n});\n\nfunction collectPaths(arg: unknown): string[] {\n if (Array.isArray(arg)) return arg.map(String);\n if (typeof arg === 'string' && arg.length > 0) return [arg];\n return ['.'];\n}\n","import { defineCommand } from 'citty';\nimport { diffCommand } from './commands/diff.ts';\nimport { runTui, tuiCommand } from './commands/tui.ts';\n\nconst SUBCOMMANDS = new Set(['tui', 'diff']);\n\nexport const rootCommand = defineCommand({\n meta: {\n name: 'envprism',\n description:\n 'TUI-based env file manager โ refract one set of variables into many environment views.'\n },\n subCommands: {\n tui: tuiCommand,\n diff: diffCommand\n },\n // citty 0.1.6 invokes the root `run` after a matched subcommand returns,\n // so guard against double-execution by bailing when the first positional\n // arg names a known subcommand. With no args at all we launch the TUI in\n // the cwd, which is the documented default.\n async run({ rawArgs }) {\n const firstPositional = rawArgs.find((a) => !a.startsWith('-'));\n if (firstPositional && SUBCOMMANDS.has(firstPositional)) return;\n await runTui({});\n }\n});\n","import { runMain } from 'citty';\nimport { rootCommand } from '../cli.ts';\n\nrunMain(rootCommand);\n"],"names":["collectPaths"],"mappings":"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"}
|