ezbun 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Shutock
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,100 @@
1
+ # ezBun
2
+
3
+ Make running Bun sandboxes easy. `ezbun` is a CLI tool that scans your project for scripts and provides an interactive menu to run them. It also includes built-in environment variable validation and type generation.
4
+
5
+ ## Features
6
+
7
+ - 🔍 **Auto-discovery**: Automatically finds scripts in your source directory.
8
+ - 🖥️ **Interactive Menu**: Select scripts to run via a CLI interface.
9
+ - 🛡️ **Env Validation**: Validates environment variables using Zod before running scripts.
10
+ - ⚡ **Type Safety**: Automatically generates `env.d.ts` for type-safe `process.env`.
11
+ - 🔄 **Watch Mode**: Supports running scripts in watch mode.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ bun add -D ezbun
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ Add a script to your `package.json`:
22
+
23
+ ```json
24
+ {
25
+ "scripts": {
26
+ "dev": "ezbun"
27
+ }
28
+ }
29
+ ```
30
+
31
+ Then run:
32
+
33
+ ```bash
34
+ bun dev
35
+ ```
36
+
37
+ Or run directly with `bunx`:
38
+
39
+ ```bash
40
+ bunx ezbun
41
+ ```
42
+
43
+ ### Watch Mode
44
+
45
+ You can run scripts in watch mode by passing the `--watch` flag:
46
+
47
+ ```bash
48
+ bun dev --watch
49
+ ```
50
+
51
+ ## Configuration
52
+
53
+ Create a `ezbun.config.ts` file in your project root to customize behavior:
54
+
55
+ ```ts
56
+ import { defineConfig } from "ezbun";
57
+
58
+ export default defineConfig({
59
+ /** Source directory to scan for scripts */
60
+ sourceDir: "./src", // default: "./src"
61
+
62
+ /** File extensions to include */
63
+ extensions: ["ts", "tsx", "js", "jsx"], // default: ["ts", "tsx", "js", "jsx", "cjs", "mjs"]
64
+
65
+ /** Whether to show a success message after loading env vars */
66
+ showSuccessMessage: false, // default: false
67
+ });
68
+ ```
69
+
70
+ ## Environment Variables
71
+
72
+ `ezbun` makes handling environment variables easy and type-safe.
73
+
74
+ 1. Create an `env.schema.ts` file in your project root:
75
+
76
+ ```ts
77
+ import { defineEnv, z } from "ezbun";
78
+
79
+ export default defineEnv({
80
+ DATABASE_URL: z.url().startsWith("postgres://"),
81
+ API_KEY: z.string().startsWith("sk-"),
82
+ PORT: z.coerce.number(),
83
+ });
84
+ ```
85
+
86
+ 2. When you run a script via `ezbun`, it will:
87
+ - Validate `process.env` against your schema.
88
+ - Print helpful error messages if validation fails.
89
+ - Automatically generate/update `env.d.ts` for global type definitions.
90
+
91
+ Now you can use `process.env` with full type safety in your code!
92
+
93
+ ```ts
94
+ // src/index.ts
95
+ console.log(process.env.PORT); // typed as number
96
+ ```
97
+
98
+ ## License
99
+
100
+ MIT
package/cli.ts ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bun
2
+ import prompts from "prompts";
3
+
4
+ import { loadConfig } from "./utils/config";
5
+
6
+ export const run = async () => {
7
+ const config = await loadConfig();
8
+
9
+ const args = process.argv.slice(2);
10
+ const isWatch = args.includes("--watch");
11
+
12
+ const extPattern = `*.{${config.extensions.join(",")}}`;
13
+ const glob = new Bun.Glob(extPattern);
14
+ const scripts: string[] = [];
15
+
16
+ const absoluteSourceDir = `${process.cwd()}/${config.sourceDir}`;
17
+
18
+ try {
19
+ const exitCode = await Bun.spawn(["test", "-d", absoluteSourceDir]).exited;
20
+ if (exitCode === 0) {
21
+ for await (const file of glob.scan(absoluteSourceDir)) {
22
+ scripts.push(file);
23
+ }
24
+ } else {
25
+ console.error(`Source directory ${config.sourceDir} does not exist.`);
26
+ process.exit(1);
27
+ }
28
+ } catch (e) {
29
+ console.error(`Error scanning directory ${absoluteSourceDir}:`, e);
30
+ process.exit(1);
31
+ }
32
+
33
+ if (scripts.length === 0) {
34
+ console.error(`No scripts found in ${config.sourceDir}/ directory.`);
35
+ process.exit(1);
36
+ }
37
+
38
+ const response = await prompts({
39
+ type: "autocomplete",
40
+ name: "script",
41
+ message: "Select a script to run",
42
+ choices: scripts.map((script) => ({
43
+ title: script,
44
+ value: script,
45
+ })),
46
+ suggest: async (input, choices) => {
47
+ const lowercaseInput = input.toLowerCase();
48
+ return choices.filter((choice) =>
49
+ choice.title.toLowerCase().includes(lowercaseInput),
50
+ );
51
+ },
52
+ });
53
+
54
+ if (!response.script) {
55
+ console.log("No script selected.");
56
+ process.exit(0);
57
+ }
58
+
59
+ const selectedScript = response.script;
60
+
61
+ const preloadPath = `${import.meta.dir}/preload.ts`;
62
+
63
+ const cmd = [
64
+ "bun",
65
+ "--bun",
66
+ ...(isWatch ? ["--watch"] : []),
67
+ "--preload",
68
+ preloadPath,
69
+ `${absoluteSourceDir}/${selectedScript}`,
70
+ ...args.filter((arg) => arg !== "--watch"),
71
+ ];
72
+
73
+ console.log(
74
+ `\nRunning ${selectedScript} ${isWatch ? "(watch mode)" : ""}...\n`,
75
+ );
76
+
77
+ const proc = Bun.spawn({
78
+ cmd,
79
+ stdio: ["inherit", "inherit", "inherit"],
80
+ });
81
+
82
+ await proc.exited;
83
+ process.exit(0);
84
+ };
85
+
86
+ run();
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { type Config, defineConfig } from "./utils/config";
2
+ export { defineEnv, z } from "./utils/env";
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "ezbun",
3
+ "description": "Make run bund sandboxes easy",
4
+ "author": "Denis Sh. <yo@shutock.com>",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "bun",
8
+ "sandbox"
9
+ ],
10
+ "files": [
11
+ "*.ts",
12
+ "utils"
13
+ ],
14
+ "type": "module",
15
+ "version": "0.1.0",
16
+ "exports": {
17
+ ".": "./index.ts"
18
+ },
19
+ "bin": "./cli.ts",
20
+ "scripts": {
21
+ "lint": "eslint . --max-warnings 0",
22
+ "check-types": "tsc --noEmit"
23
+ },
24
+ "devDependencies": {
25
+ "@config/eslint": "*",
26
+ "@config/typescript": "*",
27
+ "@types/bun": "^1.3.5",
28
+ "@types/node": "^22.15.3",
29
+ "@types/prompts": "^2.4.9",
30
+ "eslint": "^9.39.1",
31
+ "typescript": "5.9.2"
32
+ },
33
+ "dependencies": {
34
+ "prompts": "^2.4.2",
35
+ "zod": "^4.3.5"
36
+ },
37
+ "peerDependencies": {
38
+ "bun": "^1.3.5"
39
+ }
40
+ }
package/preload.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { validateEnv } from "./utils/env";
2
+
3
+ await validateEnv();
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+
3
+ const schema = z.object({
4
+ sourceDir: z.string().default("./src"),
5
+ extensions: z
6
+ .array(z.string())
7
+ .default(["ts", "tsx", "js", "jsx", "cjs", "mjs"]),
8
+ showSuccessMessage: z.boolean().default(false),
9
+ });
10
+
11
+ export type Config = {
12
+ /** Source directory
13
+ * @default "./src"
14
+ */
15
+ sourceDir?: string;
16
+ /** File extensions to parse
17
+ * @default ["ts", "tsx", "js", "jsx", "cjs", "mjs"]
18
+ */
19
+ extensions?: string[];
20
+ /** Whether to print a success message after loading the envs
21
+ * @default true
22
+ */
23
+ showSuccessMessage?: boolean;
24
+ };
25
+
26
+ export const loadConfig = async () => {
27
+ const configPath = `${process.cwd()}/ezbun.config.ts`;
28
+ const configFile = Bun.file(configPath);
29
+
30
+ if (await configFile.exists()) {
31
+ try {
32
+ const configModule = await import(configPath);
33
+ return schema.parse(configModule.default || {});
34
+ } catch (error) {
35
+ console.error("Error loading ezbun.config.ts:", error);
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ return schema.parse({});
41
+ };
42
+
43
+ export const defineConfig = (config: Config) => config;
@@ -0,0 +1,4 @@
1
+ export const red = (message: string) => `\x1b[31m${message}\x1b[0m`;
2
+ export const green = (message: string) => `\x1b[32m${message}\x1b[0m`;
3
+ export const bold = (message: string) => `\x1b[1m${message}\x1b[0m`;
4
+ export const icon = { check: "✔", cross: "⨯" };
package/utils/env.ts ADDED
@@ -0,0 +1,65 @@
1
+ import z from "zod";
2
+
3
+ import { loadConfig } from "./config";
4
+ import { bold, green, icon, red } from "./console";
5
+
6
+ export { z };
7
+
8
+ export const defineEnv = <T extends z.ZodRawShape>(schema: T) =>
9
+ z.object(schema);
10
+
11
+ export const loadUserEnv = async () => {
12
+ try {
13
+ const schemaPath = `${process.cwd()}/env.schema.ts`;
14
+ const module = await import(schemaPath);
15
+ return module.default as z.ZodObject;
16
+ } catch {
17
+ return null;
18
+ }
19
+ };
20
+
21
+ export const validateEnv = async () => {
22
+ const envSchema = await loadUserEnv();
23
+ if (!envSchema) return;
24
+
25
+ const envValidation = envSchema.safeParse(process.env);
26
+
27
+ if (envValidation.error) {
28
+ const message = [
29
+ `${red(icon.cross)} ${bold("Error in loading environment variables:")}`,
30
+ ...envValidation.error.issues.map(
31
+ ({ message, path }) =>
32
+ ` ${path.map((p) => red(String(p))).join(", ")}: ${message}`,
33
+ ),
34
+ ].join("\n");
35
+
36
+ console.error(message);
37
+ process.exit(1);
38
+ }
39
+
40
+ await generateEnvDts();
41
+
42
+ const { showSuccessMessage } = await loadConfig();
43
+ if (!showSuccessMessage) return;
44
+
45
+ return console.info(
46
+ `${green(icon.check)} ${bold("Environment variables loaded successfully")}\n`,
47
+ );
48
+ };
49
+
50
+ const generateEnvDts = async () => {
51
+ const dtsPath = `${process.cwd()}/env.d.ts`;
52
+
53
+ const content = `import type { z } from "zod";
54
+
55
+ import type envSchema from "./env.schema";
56
+
57
+ declare global {
58
+ namespace NodeJS {
59
+ interface ProcessEnv extends Omit<z.infer<typeof envSchema>, "NODE_ENV"> {}
60
+ }
61
+ }
62
+ `;
63
+
64
+ await Bun.write(dtsPath, content);
65
+ };
@@ -0,0 +1,14 @@
1
+ import z from "zod";
2
+
3
+ export const defineEnv = <T extends z.ZodRawShape>(schema: T) =>
4
+ z.object(schema);
5
+
6
+ export const loadUserEnv = async () => {
7
+ try {
8
+ const schemaPath = `${process.cwd()}/env.schema.ts`;
9
+ const module = await import(schemaPath);
10
+ return module.default as z.ZodObject;
11
+ } catch {
12
+ return null;
13
+ }
14
+ };