crabenv 0.0.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 (4) hide show
  1. package/README.md +112 -0
  2. package/bin.js +45 -0
  3. package/install.js +158 -0
  4. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # crabenv
2
+
3
+ The simplest, opinionated way to keep .env files, schemas, and examples aligned.
4
+
5
+ crabenv is an **opinionated**, language-agnostic CLI that keeps your environment variables aligned across schema, template, and local files. No new config file required. It validates, copies, and checks for drift so your team doesn't have to.
6
+
7
+ This project will once and for all solve environment variables typesafe schema definition and documentation so it will never drift. This CLI only does what you can already do manually. It doesn't introduce any new config files so if your team doesn't want to use crabenv, it'll be completely fine!
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ brew install blankeos/tap/crabenv # Homebrew (macOS/Linux)
13
+ npm install -g crabenv # or npm
14
+ bun install -g crabenv # or bun
15
+ cargo binstall crabenv # or cargo-binstall (prebuilt binary, faster)
16
+ cargo install crabenv # or cargo (build from source)
17
+ curl -sSL https://raw.githubusercontent.com/Blankeos/crabenv/main/install.sh | sh # or linux/macos (via curl)
18
+ ```
19
+
20
+ ### 📁 Languages supported:
21
+
22
+ - [x] TypeScript/Javascript and Monorepos (includes React, Solid, Vue, Svelte, Vite, NextJS, ReactNative, Backends, and Cloudflare apps)
23
+ - [x] Python
24
+ - [x] Rust
25
+ - [x] Flutter
26
+ - [x] More? Request an adapter.
27
+
28
+ ### 🤒 Pains solved:
29
+
30
+ - [x] CRUD and Documentation drift
31
+ - [ ] Deployment/sink drift via explicit managed blocks, not arbitrary file inference
32
+ - [x] `.env.example` and `env.ts` drift
33
+ - [x] Validation
34
+ - [x] Client and Server Boundaries
35
+ - [x] Local Development (creating the first .env) to Actual Deployment (translating that into the env on deployment) stories
36
+ - [x] A better `cp .env.example .env` command (This is not enough!) Creating envs (local env or for new dev,staging,prod envs).
37
+ - [x] Use templating patterns like `"RSA_KEY=$(openssl rand -base64 32)` - the crabenv copy
38
+ - [x] ~Rotating??~ Kinda impossible actually.
39
+ - [ ] Cloudflare `.dev.vars` guidance/docs
40
+
41
+ ## Philosophy
42
+
43
+ - No new config file. Your team doesn't have to install crabenv, but it helps a lot! The CLI just abstracts the manual maintenance.
44
+ - Env config is synced across **surfaces**:
45
+ - 1. **Schema** - The validator language-specific schema. Multiple based on apps. i.e. `env.ts`, `env.private.ts`, `env.public.ts`, `config.rs`, `config.dart`.
46
+ - 2. **Template (`.env.example`)** - Safe example/template values for env values. Multiple based on apps.
47
+ - 3. **Local (`.env`)** - The actual local runtime values/secrets. 1 only.
48
+ - 4. **Sinks** - Reserved for future explicit integrations. crabenv does not currently infer or rewrite arbitrary deployment files.
49
+ - More? Make an adapter
50
+ - Packages in monorepos don't have .env.
51
+
52
+ ## Agent skill
53
+
54
+ Install the crabenv skill for coding agents with:
55
+
56
+ ```sh
57
+ npx skills add blankeos/crabenv
58
+ ```
59
+
60
+ This installs the `crabenv` agent skill from `skills/crabenv`, including language-specific references for TypeScript/JavaScript, Python, Rust, and Flutter/Dart.
61
+
62
+ ## Usage
63
+
64
+ ```sh
65
+ crabenv init
66
+ crabenv copy # or crabenv cp.
67
+ crabenv doctor # It's a checklist of common mistakes
68
+ crabenv doctor --fix
69
+
70
+ # CRUD
71
+ crabenv list # Lists variables
72
+ crabenv add # Wizard-like experience
73
+ crabenv update # Wizard-like experience
74
+ crabenv remove # Wizard-like experience
75
+
76
+ crabenv add {VARIABLE_NAME}
77
+ --shared # if monorepo, adds it to all apps
78
+ --example # optional, for .env.example
79
+ --optional # optional, required by default
80
+ --default # optional
81
+ # Type flags
82
+ --string # optional, passed by default
83
+ --numeric # optional, conflicts w/ string. It's a string, but validates as a number (a numeric string)
84
+ --number # optional, conflicts w/ string (implicitly z.preprocess(Number))
85
+ --boolean # optional, conflicts w/ string (implicitly z.preprocess(Boolean))
86
+ --enum # optional, i.e. --enum=development,production
87
+ # Custom regex
88
+ --testRegex # optional
89
+ --testRegexMessage # optional, required when used with `testRegex`
90
+ # ... essentially a cli version of zod
91
+ # Special environment overrides
92
+ --{}_development # Checks env=development, before applying the type
93
+ --{}_staging # Checks env=staging, before applying the type
94
+ --{}_production # Checks env=production, before applying the type
95
+ --{}_ci # Checks CI=true, before applying the type
96
+ crabenv remove {VARIABLE_NAME}
97
+ crabenv update {VARIABLE_NAME} # Same flags as add
98
+ ```
99
+
100
+ ## Current CLI
101
+
102
+ This repo currently has a first Rust CLI implementation that works against the sample projects in `_checks/sample-projects`.
103
+
104
+ ```sh
105
+ crabenv --root _checks/sample-projects/basic-npm list
106
+ crabenv --root _checks/sample-projects/basic-npm doctor
107
+ crabenv --root _checks/sample-projects/basic-npm copy
108
+
109
+ crabenv --root _checks/sample-projects/monorepo doctor --fix --yes
110
+ crabenv --root _checks/sample-projects/monorepo add NEXT_PUBLIC_ANALYTICS_ID --owner apps/next-web --public --example dev --optional
111
+ crabenv --root _checks/sample-projects/monorepo remove NEXT_PUBLIC_ANALYTICS_ID --owner apps/next-web --public
112
+ ```
package/bin.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const { install } = require("./install");
7
+
8
+ const binaryName = process.platform === "win32" ? "crabenv.exe" : "crabenv";
9
+ const binaryPath = path.join(__dirname, "bin", binaryName);
10
+
11
+ async function ensureBinary() {
12
+ if (fs.existsSync(binaryPath)) {
13
+ return;
14
+ }
15
+
16
+ console.error("crabenv binary not found. Attempting download...");
17
+
18
+ try {
19
+ await install();
20
+ } catch (error) {
21
+ process.exit(1);
22
+ }
23
+
24
+ if (!fs.existsSync(binaryPath)) {
25
+ console.error("❌ crabenv binary still missing after download.");
26
+ process.exit(1);
27
+ }
28
+ }
29
+
30
+ async function run() {
31
+ await ensureBinary();
32
+
33
+ const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
34
+
35
+ child.on("error", (err) => {
36
+ console.error("❌ Failed to start crabenv:", err.message);
37
+ process.exit(1);
38
+ });
39
+
40
+ child.on("exit", (code, signal) => {
41
+ process.exit(signal ? 1 : code || 0);
42
+ });
43
+ }
44
+
45
+ run();
package/install.js ADDED
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require("child_process");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const https = require("https");
7
+
8
+ // Version should match your Rust crate version.
9
+ const VERSION = require("./package.json").version;
10
+ const BINARY_NAME = "crabenv";
11
+
12
+ function getPlatformInfo() {
13
+ const platform = process.platform;
14
+ const arch = process.arch;
15
+
16
+ // Map Node.js platform/arch to Rust target triples.
17
+ const platformMap = {
18
+ darwin: {
19
+ x64: "x86_64-apple-darwin",
20
+ arm64: "aarch64-apple-darwin",
21
+ },
22
+ linux: {
23
+ x64: "x86_64-unknown-linux-gnu",
24
+ arm64: "aarch64-unknown-linux-gnu",
25
+ },
26
+ win32: {
27
+ x64: "x86_64-pc-windows-msvc",
28
+ },
29
+ };
30
+
31
+ if (!platformMap[platform]) {
32
+ throw new Error(`Unsupported platform: ${platform}`);
33
+ }
34
+
35
+ if (!platformMap[platform][arch]) {
36
+ throw new Error(`Unsupported architecture: ${arch} on ${platform}`);
37
+ }
38
+
39
+ const target = platformMap[platform][arch];
40
+ const extension = platform === "win32" ? ".zip" : ".tar.xz";
41
+ const binaryName = platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
42
+
43
+ return {
44
+ target,
45
+ extension,
46
+ binaryName,
47
+ filename: `${BINARY_NAME}-${target}${extension}`,
48
+ url: `https://github.com/Blankeos/crabenv/releases/download/v${VERSION}/${BINARY_NAME}-${target}${extension}`,
49
+ };
50
+ }
51
+
52
+ async function downloadFile(url, dest) {
53
+ console.log(`Downloading ${url}...`);
54
+
55
+ const file = fs.createWriteStream(dest);
56
+ const response = await new Promise((resolve, reject) => {
57
+ https
58
+ .get(url, (res) => {
59
+ if (res.statusCode === 302 || res.statusCode === 301) {
60
+ https.get(res.headers.location, resolve).on("error", reject);
61
+ } else if (res.statusCode === 200) {
62
+ resolve(res);
63
+ } else {
64
+ reject(new Error(`Failed to download: ${res.statusCode} ${res.statusMessage}`));
65
+ }
66
+ })
67
+ .on("error", reject);
68
+ });
69
+
70
+ response.pipe(file);
71
+ return new Promise((resolve, reject) => {
72
+ file.on("finish", () => {
73
+ file.close();
74
+ resolve();
75
+ });
76
+ file.on("error", (err) => {
77
+ fs.unlink(dest, () => {});
78
+ reject(err);
79
+ });
80
+ });
81
+ }
82
+
83
+ function extractArchive(archivePath, extractDir, platformInfo) {
84
+ console.log("Extracting binary...");
85
+
86
+ const cmd =
87
+ platformInfo.extension === ".zip"
88
+ ? `unzip -o "${archivePath}" -d "${extractDir}" 2>/dev/null || powershell -command "Expand-Archive -Path '${archivePath}' -DestinationPath '${extractDir}' -Force"`
89
+ : `tar -xf "${archivePath}" -C "${extractDir}"`;
90
+
91
+ execSync(cmd, { stdio: "inherit" });
92
+ }
93
+
94
+ function logInstallFailure(error) {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ console.error("Installation failed:", message);
97
+ console.error("\nYou can install crabenv directly using:");
98
+ console.error(
99
+ 'curl --proto "=https" --tlsv1.2 -LsSf https://github.com/Blankeos/crabenv/releases/latest/download/crabenv-installer.sh | sh',
100
+ );
101
+ }
102
+
103
+ async function install({ exitOnComplete = false } = {}) {
104
+ try {
105
+ const platformInfo = getPlatformInfo();
106
+ const binDir = path.join(__dirname, "bin");
107
+ const archivePath = path.join(__dirname, platformInfo.filename);
108
+ const binaryPath = path.join(binDir, platformInfo.binaryName);
109
+
110
+ if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true });
111
+
112
+ await downloadFile(platformInfo.url, archivePath);
113
+ extractArchive(archivePath, __dirname, platformInfo);
114
+
115
+ const extractedBinaryPath = path.join(__dirname, platformInfo.binaryName);
116
+ if (fs.existsSync(extractedBinaryPath)) {
117
+ fs.renameSync(extractedBinaryPath, binaryPath);
118
+ } else {
119
+ const subdirPath = path.join(__dirname, `${BINARY_NAME}-${platformInfo.target}`, platformInfo.binaryName);
120
+ if (fs.existsSync(subdirPath)) {
121
+ fs.renameSync(subdirPath, binaryPath);
122
+ fs.rmSync(path.dirname(subdirPath), { recursive: true, force: true });
123
+ } else {
124
+ throw new Error("Binary not found after extraction");
125
+ }
126
+ }
127
+
128
+ if (process.platform !== "win32") {
129
+ fs.chmodSync(binaryPath, 0o755);
130
+ }
131
+
132
+ fs.unlinkSync(archivePath);
133
+ console.log(`crabenv v${VERSION} installed successfully!`);
134
+
135
+ if (exitOnComplete) {
136
+ process.exit(0);
137
+ return binaryPath;
138
+ }
139
+
140
+ return binaryPath;
141
+ } catch (error) {
142
+ logInstallFailure(error);
143
+
144
+ if (exitOnComplete) {
145
+ process.exit(1);
146
+ return;
147
+ }
148
+
149
+ throw error;
150
+ }
151
+ }
152
+
153
+ // Only run install if this script is executed directly.
154
+ if (require.main === module) {
155
+ install({ exitOnComplete: true });
156
+ }
157
+
158
+ module.exports = { getPlatformInfo, install };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "crabenv",
3
+ "version": "0.0.1",
4
+ "description": "The simplest, opinionated way to keep .env files, schemas, and examples aligned.",
5
+ "main": "bin.js",
6
+ "bin": {
7
+ "crabenv": "./bin.js"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node install.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/blankeos/crabenv.git"
15
+ },
16
+ "keywords": [
17
+ "env",
18
+ "cli",
19
+ "config",
20
+ "dotenv",
21
+ "schema"
22
+ ],
23
+ "author": "Blankeos",
24
+ "license": "MIT",
25
+ "files": [
26
+ "install.js",
27
+ "bin.js",
28
+ "README.md"
29
+ ],
30
+ "engines": {
31
+ "node": ">=12"
32
+ },
33
+ "os": [
34
+ "darwin",
35
+ "linux",
36
+ "win32"
37
+ ]
38
+ }