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 +21 -0
- package/README.md +100 -0
- package/cli.ts +86 -0
- package/index.ts +2 -0
- package/package.json +40 -0
- package/preload.ts +3 -0
- package/utils/config.ts +43 -0
- package/utils/console.ts +4 -0
- package/utils/env.ts +65 -0
- package/utils/schema.ts +14 -0
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
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
package/utils/config.ts
ADDED
|
@@ -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;
|
package/utils/console.ts
ADDED
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
|
+
};
|
package/utils/schema.ts
ADDED
|
@@ -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
|
+
};
|