@firtoz/worker-helper 1.0.0 → 1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @firtoz/worker-helper
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`2725815`](https://github.com/firtoz/fullstack-toolkit/commit/27258158dd318b34b44ed77b88b2ac9b2b4b6a3d) Thanks [@firtoz](https://github.com/firtoz)! - Improved workspace-wide type generation and environment setup
8
+
9
+ - Refactored `cf-typegen.ts` to automatically discover all wrangler configs using `git ls-files`
10
+ - Uses git for workspace discovery - fast, respects .gitignore, and finds all tracked configs
11
+ - Added `prepareEnvFiles` utility to handle .env file creation from .env.example templates
12
+ - Type generation now includes bindings from all workspace projects for better DX
13
+
14
+ ## 1.1.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [`b0f7893`](https://github.com/firtoz/fullstack-toolkit/commit/b0f789314c4ee85d8c08466b968baad2977a2581) Thanks [@firtoz](https://github.com/firtoz)! - Added cf-typegen script utility for generating Cloudflare Workers TypeScript types
19
+
20
+ - Added cf-typegen script that runs wrangler types command for specified worker directory
21
+ - Utility used by test packages to generate worker-configuration.d.ts
22
+ - Simplified type generation workflow for Cloudflare Workers projects
23
+
3
24
  ## 1.0.0
4
25
 
5
26
  ### Major Changes
package/README.md CHANGED
@@ -6,19 +6,79 @@ Type-safe Web Worker helper with Zod validation for input and output messages. T
6
6
 
7
7
  ## Features
8
8
 
9
+ ### Web Workers
10
+
9
11
  - šŸ”’ **Type-safe**: Full TypeScript support with automatic type inference
10
12
  - āœ… **Zod Validation**: Automatic validation of both input and output messages
11
13
  - šŸŽÆ **Custom Error Handlers**: Mandatory error handlers give you complete control over error handling
12
14
  - šŸ”„ **Async Support**: Built-in support for async message handlers
13
15
  - 🧩 **Discriminated Unions**: Works great with Zod's discriminated unions for type-safe message routing
14
16
 
17
+ ### Cloudflare Workers
18
+
19
+ - šŸ”§ **cf-typegen**: Automatic `.env.local` creation from `wrangler.jsonc` vars
20
+ - šŸ“ **Type Generation**: Wrapper around `wrangler types` with env preparation
21
+
15
22
  ## Installation
16
23
 
17
24
  ```bash
18
25
  bun add @firtoz/worker-helper zod
19
26
  ```
20
27
 
21
- ## Usage
28
+ ## cf-typegen (Cloudflare Workers Utility)
29
+
30
+ Automatic TypeScript type generation and `.env.local` management for Cloudflare Workers projects.
31
+
32
+ ### Setup
33
+
34
+ Add the script to your Cloudflare Workers package:
35
+
36
+ ```json
37
+ {
38
+ "scripts": {
39
+ "cf-typegen": "bun --cwd ../../packages/worker-helper cf-typegen $(pwd)"
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### What It Does
45
+
46
+ 1. **Reads `.env.local.example`** to find required env vars
47
+ 2. **Creates/updates `.env.local`** with any missing vars (as empty strings)
48
+ 3. **Runs `wrangler types`** to generate TypeScript definitions
49
+
50
+ ### Example
51
+
52
+ ```bash
53
+ cd your-worker-package
54
+ bun run cf-typegen
55
+ ```
56
+
57
+ **Output:**
58
+ ```
59
+ Running CF typegen for: /path/to/your-worker
60
+ āœ“ Added missing env vars: OPENROUTER_API_KEY, DATABASE_URL
61
+ Running wrangler types...
62
+ āœ“ Wrangler types generated
63
+ āœ“ CF typegen completed successfully
64
+ ```
65
+
66
+ **Generated `.env.local`:**
67
+ ```env
68
+ OPENROUTER_API_KEY=
69
+ DATABASE_URL=
70
+ ```
71
+
72
+ ### Why This Matters
73
+
74
+ - Ensures `wrangler types` always succeeds (needs `.env.local` or `.dev.vars`)
75
+ - Keeps `.env.local` in sync with `.env.local.example`
76
+ - Avoids accidentally binding empty vars at runtime via `wrangler.jsonc` `vars`
77
+ - Developers can fill in actual values without committing them to git
78
+ - CI/CD can generate types without needing actual secrets
79
+ - `.env.local.example` serves as documentation for required env vars
80
+
81
+ ## Web Worker Usage
22
82
 
23
83
  ### 1. Define Your Schemas
24
84
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firtoz/worker-helper",
3
- "version": "1.0.0",
4
- "description": "Type-safe Web Worker helper with Zod validation for input and output messages",
3
+ "version": "1.2.0",
4
+ "description": "Type-safe Web Worker helper with Zod validation and Cloudflare Workers utilities (cf-typegen)",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
7
7
  "types": "./src/index.ts",
@@ -28,15 +28,20 @@
28
28
  "lint:ci": "biome ci src",
29
29
  "format": "biome format src --write",
30
30
  "test": "bun test",
31
- "test:watch": "bun test --watch"
31
+ "test:watch": "bun test --watch",
32
+ "cf-typegen": "bun ./src/cf-typegen.ts"
32
33
  },
33
34
  "keywords": [
34
35
  "typescript",
35
36
  "web-worker",
36
37
  "worker",
38
+ "cloudflare-workers",
39
+ "cloudflare",
37
40
  "zod",
38
41
  "validation",
39
- "type-safe"
42
+ "type-safe",
43
+ "wrangler",
44
+ "typegen"
40
45
  ],
41
46
  "author": "Firtina Ozbalikchi <firtoz@github.com>",
42
47
  "license": "MIT",
@@ -56,11 +61,11 @@
56
61
  "access": "public"
57
62
  },
58
63
  "dependencies": {
59
- "zod": "^4.1.12"
64
+ "zod": "^4.3.6"
60
65
  },
61
66
  "devDependencies": {
62
- "@types/node": "^24.10.1",
63
- "@firtoz/maybe-error": "^1.5.1",
64
- "bun-types": "^1.3.2"
67
+ "@types/node": "^25.0.10",
68
+ "@firtoz/maybe-error": "^1.5.2",
69
+ "bun-types": "^1.3.6"
65
70
  }
66
71
  }
@@ -0,0 +1,126 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { prepareEnvFiles } from "./utils/prepare-env";
6
+
7
+ // Use the current working directory
8
+ const cwd = process.argv[2];
9
+ if (!cwd || !fs.existsSync(cwd)) {
10
+ console.error(
11
+ "Please specify a directory as the first parameter. Usually $(pwd).",
12
+ );
13
+ process.exit(1);
14
+ }
15
+
16
+ console.log(`Running CF typegen for: ${cwd}`);
17
+
18
+ /**
19
+ * Find the git root directory using git command
20
+ */
21
+ function findGitRoot(startPath: string): string | null {
22
+ try {
23
+ const output = execSync("git rev-parse --show-toplevel", {
24
+ cwd: startPath,
25
+ encoding: "utf8",
26
+ });
27
+ return output.trim();
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Find all wrangler config files using git ls-files
35
+ * This is more efficient and respects .gitignore
36
+ */
37
+ function findAllWranglerConfigs(gitRoot: string): string[] {
38
+ try {
39
+ // Use git ls-files to find all tracked wrangler configs
40
+ const output = execSync('git ls-files "**/wrangler.json*"', {
41
+ cwd: gitRoot,
42
+ encoding: "utf8",
43
+ });
44
+
45
+ return output
46
+ .trim()
47
+ .split("\n")
48
+ .filter((line) => line.length > 0)
49
+ .map((relativePath) => path.join(gitRoot, relativePath));
50
+ } catch (err) {
51
+ console.warn("⚠ Failed to run git ls-files:", err);
52
+ return [];
53
+ }
54
+ }
55
+
56
+ function runWranglerTypes() {
57
+ const envFiles = prepareEnvFiles(cwd);
58
+
59
+ console.log("Running wrangler types...");
60
+
61
+ // Find git root to discover all wrangler configs
62
+ const gitRoot = findGitRoot(cwd);
63
+ if (!gitRoot) {
64
+ console.warn(
65
+ "⚠ Could not find git root, skipping workspace config discovery",
66
+ );
67
+ console.log(" Generating types for current directory only");
68
+ }
69
+
70
+ // Find all wrangler configs in the workspace
71
+ const allConfigs = gitRoot ? findAllWranglerConfigs(gitRoot) : [];
72
+
73
+ if (allConfigs.length > 0) {
74
+ console.log(` Found ${allConfigs.length} wrangler config(s) in workspace`);
75
+ }
76
+
77
+ // Build the command with multiple -c flags
78
+ // The first config should be the current directory's wrangler.jsonc
79
+ const configFlags = ["-c wrangler.jsonc"];
80
+
81
+ // Add other configs (relative to cwd for better readability)
82
+ const currentWranglerJsonc = path.join(cwd, "wrangler.jsonc");
83
+ const currentWranglerJson = path.join(cwd, "wrangler.json");
84
+
85
+ for (const configPath of allConfigs) {
86
+ const resolvedPath = path.resolve(configPath);
87
+ // Skip if it's the current directory's config
88
+ if (
89
+ resolvedPath === currentWranglerJsonc ||
90
+ resolvedPath === currentWranglerJson
91
+ ) {
92
+ continue;
93
+ }
94
+ // Make path relative to cwd
95
+ const relativePath = path.relative(cwd, configPath);
96
+ configFlags.push(`-c ${relativePath}`);
97
+ }
98
+
99
+ for (const envFile of envFiles) {
100
+ configFlags.push(`--env-file ${envFile}`);
101
+ }
102
+
103
+ const command = `wrangler types ${configFlags.join(" ")}`;
104
+
105
+ console.log(` Command: ${command}`);
106
+
107
+ try {
108
+ execSync(command, {
109
+ cwd,
110
+ stdio: "inherit",
111
+ });
112
+ console.log("āœ“ Wrangler types generated with all workspace bindings");
113
+ } catch {
114
+ console.error("Failed to run wrangler types");
115
+ process.exit(1);
116
+ }
117
+ }
118
+
119
+ // Run all steps
120
+ try {
121
+ runWranglerTypes();
122
+ console.log("\nāœ“ CF typegen completed successfully");
123
+ } catch (error: unknown) {
124
+ console.error("\nāœ— CF typegen failed:", error);
125
+ process.exit(1);
126
+ }
@@ -0,0 +1,58 @@
1
+ import * as fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * Ensures a .env and .env.local file exists in the target directory.
6
+ * If it doesn't exist, copies from .env.example and .env.local.example.
7
+ *
8
+ * @param targetDir - The directory where the .env and .env.local files should exist
9
+ * @returns An array of the files that were created or already existed
10
+ */
11
+ export function prepareEnvFiles(targetDir: string): string[] {
12
+ const exampleEnvPath = path.join(targetDir, ".env.example");
13
+ const exampleEnvLocalPath = path.join(targetDir, ".env.local.example");
14
+
15
+ const exampleEnvExists = fs.existsSync(exampleEnvPath);
16
+ const exampleLocalEnvExists = fs.existsSync(exampleEnvLocalPath);
17
+
18
+ const envPath = path.join(targetDir, ".env");
19
+ const envLocalPath = path.join(targetDir, ".env.local");
20
+
21
+ let envExists = fs.existsSync(envPath);
22
+ let envLocalExists = fs.existsSync(envLocalPath);
23
+
24
+ if (exampleEnvExists) {
25
+ if (!envExists) {
26
+ fs.cpSync(exampleEnvPath, envPath);
27
+
28
+ console.log("āœ“ Created .env from .env.example");
29
+ envExists = true;
30
+ }
31
+ }
32
+
33
+ if (exampleLocalEnvExists) {
34
+ if (!envLocalExists) {
35
+ fs.cpSync(exampleEnvLocalPath, envLocalPath);
36
+
37
+ console.log("āœ“ Created .env.local from .env.local.example");
38
+ envLocalExists = true;
39
+ }
40
+ }
41
+
42
+ const result: string[] = [];
43
+ // The order matters, because latter files' values will override former files' values
44
+ if (exampleEnvExists) {
45
+ result.push(".env.example");
46
+ }
47
+ if (exampleLocalEnvExists) {
48
+ result.push(".env.local.example");
49
+ }
50
+ if (envExists) {
51
+ result.push(".env");
52
+ }
53
+ if (envLocalExists) {
54
+ result.push(".env.local");
55
+ }
56
+
57
+ return result;
58
+ }