@firtoz/worker-helper 1.1.0 → 1.3.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,22 @@
1
1
  # @firtoz/worker-helper
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`ef2b36e`](https://github.com/firtoz/fullstack-toolkit/commit/ef2b36e4be4fda049f02f1d000649e4c75ff08ec) Thanks [@firtoz](https://github.com/firtoz)! - Export `cf-typegen` as a CLI binary. Users can now run `cf-typegen $(pwd)` directly after installing the package as a dev dependency.
8
+
9
+ ## 1.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`2725815`](https://github.com/firtoz/fullstack-toolkit/commit/27258158dd318b34b44ed77b88b2ac9b2b4b6a3d) Thanks [@firtoz](https://github.com/firtoz)! - Improved workspace-wide type generation and environment setup
14
+
15
+ - Refactored `cf-typegen.ts` to automatically discover all wrangler configs using `git ls-files`
16
+ - Uses git for workspace discovery - fast, respects .gitignore, and finds all tracked configs
17
+ - Added `prepareEnvFiles` utility to handle .env file creation from .env.example templates
18
+ - Type generation now includes bindings from all workspace projects for better DX
19
+
3
20
  ## 1.1.0
4
21
 
5
22
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/worker-helper",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
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",
@@ -17,6 +17,9 @@
17
17
  "require": "./src/*.ts"
18
18
  }
19
19
  },
20
+ "bin": {
21
+ "cf-typegen": "./src/cf-typegen.ts"
22
+ },
20
23
  "files": [
21
24
  "src/**/*.ts",
22
25
  "README.md",
@@ -61,10 +64,10 @@
61
64
  "access": "public"
62
65
  },
63
66
  "dependencies": {
64
- "zod": "^4.3.5"
67
+ "zod": "^4.3.6"
65
68
  },
66
69
  "devDependencies": {
67
- "@types/node": "^25.0.9",
70
+ "@types/node": "^25.0.10",
68
71
  "@firtoz/maybe-error": "^1.5.2",
69
72
  "bun-types": "^1.3.6"
70
73
  }
package/src/cf-typegen.ts CHANGED
@@ -1,7 +1,9 @@
1
+ #!/usr/bin/env node
1
2
  import { execSync } from "node:child_process";
2
3
  import * as fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import process from "node:process";
6
+ import { prepareEnvFiles } from "./utils/prepare-env";
5
7
 
6
8
  // Use the current working directory
7
9
  const cwd = process.argv[2];
@@ -15,112 +17,100 @@ if (!cwd || !fs.existsSync(cwd)) {
15
17
  console.log(`Running CF typegen for: ${cwd}`);
16
18
 
17
19
  /**
18
- * Extracts required env vars from .env.local.example
20
+ * Find the git root directory using git command
19
21
  */
20
- function getRequiredEnvVars(examplePath: string): string[] {
22
+ function findGitRoot(startPath: string): string | null {
21
23
  try {
22
- if (!fs.existsSync(examplePath)) {
23
- return [];
24
- }
25
-
26
- const content = fs.readFileSync(examplePath, "utf8");
27
- const vars: string[] = [];
28
-
29
- // Parse .env format: VAR_NAME=value
30
- for (const line of content.split("\n")) {
31
- const trimmed = line.trim();
32
- // Skip comments and empty lines
33
- if (!trimmed || trimmed.startsWith("#")) continue;
24
+ const output = execSync("git rev-parse --show-toplevel", {
25
+ cwd: startPath,
26
+ encoding: "utf8",
27
+ });
28
+ return output.trim();
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
34
33
 
35
- const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)=/);
36
- if (match) {
37
- vars.push(match[1]);
38
- }
39
- }
34
+ /**
35
+ * Find all wrangler config files using git ls-files
36
+ * This is more efficient and respects .gitignore
37
+ */
38
+ function findAllWranglerConfigs(gitRoot: string): string[] {
39
+ try {
40
+ // Use git ls-files to find all tracked wrangler configs
41
+ const output = execSync('git ls-files "**/wrangler.json*"', {
42
+ cwd: gitRoot,
43
+ encoding: "utf8",
44
+ });
40
45
 
41
- return vars;
46
+ return output
47
+ .trim()
48
+ .split("\n")
49
+ .filter((line) => line.length > 0)
50
+ .map((relativePath) => path.join(gitRoot, relativePath));
42
51
  } catch (err) {
43
- console.warn(`Failed to read ${examplePath}:`, err);
52
+ console.warn("⚠ Failed to run git ls-files:", err);
44
53
  return [];
45
54
  }
46
55
  }
47
56
 
48
- /**
49
- * Ensures .env.local exists with required env vars from .env.local.example
50
- */
51
- function prepareEnvLocal(): { created: boolean; added: string[] } {
52
- const envPath = path.join(cwd, ".env.local");
53
- const examplePath = path.join(cwd, ".env.local.example");
57
+ function runWranglerTypes() {
58
+ const envFiles = prepareEnvFiles(cwd);
54
59
 
55
- if (!fs.existsSync(examplePath)) {
56
- console.log("No .env.local.example found, skipping env preparation");
57
- return { created: false, added: [] };
58
- }
60
+ console.log("Running wrangler types...");
59
61
 
60
- const requiredVars = getRequiredEnvVars(examplePath);
61
- if (requiredVars.length === 0) {
62
- console.log("No vars found in .env.local.example");
63
- return { created: false, added: [] };
62
+ // Find git root to discover all wrangler configs
63
+ const gitRoot = findGitRoot(cwd);
64
+ if (!gitRoot) {
65
+ console.warn(
66
+ "⚠ Could not find git root, skipping workspace config discovery",
67
+ );
68
+ console.log(" Generating types for current directory only");
64
69
  }
65
70
 
66
- let content = "";
67
- let created = false;
68
- const added: string[] = [];
71
+ // Find all wrangler configs in the workspace
72
+ const allConfigs = gitRoot ? findAllWranglerConfigs(gitRoot) : [];
69
73
 
70
- if (fs.existsSync(envPath)) {
71
- content = fs.readFileSync(envPath, "utf8");
72
- } else {
73
- created = true;
74
+ if (allConfigs.length > 0) {
75
+ console.log(` Found ${allConfigs.length} wrangler config(s) in workspace`);
74
76
  }
75
77
 
76
- // Check which vars are missing
77
- for (const varName of requiredVars) {
78
- const regex = new RegExp(`^${varName}=`, "m");
79
- if (!regex.test(content)) {
80
- if (content && !content.endsWith("\n")) {
81
- content += "\n";
82
- }
83
- content += `${varName}=\n`;
84
- added.push(varName);
78
+ // Build the command with multiple -c flags
79
+ // The first config should be the current directory's wrangler.jsonc
80
+ const configFlags = ["-c wrangler.jsonc"];
81
+
82
+ // Add other configs (relative to cwd for better readability)
83
+ const currentWranglerJsonc = path.join(cwd, "wrangler.jsonc");
84
+ const currentWranglerJson = path.join(cwd, "wrangler.json");
85
+
86
+ for (const configPath of allConfigs) {
87
+ const resolvedPath = path.resolve(configPath);
88
+ // Skip if it's the current directory's config
89
+ if (
90
+ resolvedPath === currentWranglerJsonc ||
91
+ resolvedPath === currentWranglerJson
92
+ ) {
93
+ continue;
85
94
  }
95
+ // Make path relative to cwd
96
+ const relativePath = path.relative(cwd, configPath);
97
+ configFlags.push(`-c ${relativePath}`);
86
98
  }
87
99
 
88
- // Write if there were any changes
89
- if (created || added.length > 0) {
90
- fs.writeFileSync(envPath, content);
100
+ for (const envFile of envFiles) {
101
+ configFlags.push(`--env-file ${envFile}`);
91
102
  }
92
103
 
93
- return { created, added };
94
- }
104
+ const command = `wrangler types ${configFlags.join(" ")}`;
95
105
 
96
- // Step 1: Prepare .env.local with required vars from wrangler.jsonc
97
- function prepareEnv() {
98
- try {
99
- const updates = prepareEnvLocal();
100
- if (updates.created) {
101
- console.log("✓ Created .env.local");
102
- }
103
- if (updates.added.length > 0) {
104
- console.log(`✓ Added missing env vars: ${updates.added.join(", ")}`);
105
- }
106
- if (!updates.created && updates.added.length === 0) {
107
- console.log("✓ .env.local file already has all required vars");
108
- }
109
- } catch (error) {
110
- console.error(String(error));
111
- process.exit(1);
112
- }
113
- }
106
+ console.log(` Command: ${command}`);
114
107
 
115
- // Step 2: Run wrangler types
116
- function runWranglerTypes() {
117
- console.log("Running wrangler types...");
118
108
  try {
119
- execSync("wrangler types -c wrangler.jsonc --env-file .env.local", {
109
+ execSync(command, {
120
110
  cwd,
121
111
  stdio: "inherit",
122
112
  });
123
- console.log("✓ Wrangler types generated");
113
+ console.log("✓ Wrangler types generated with all workspace bindings");
124
114
  } catch {
125
115
  console.error("Failed to run wrangler types");
126
116
  process.exit(1);
@@ -129,7 +119,6 @@ function runWranglerTypes() {
129
119
 
130
120
  // Run all steps
131
121
  try {
132
- prepareEnv();
133
122
  runWranglerTypes();
134
123
  console.log("\n✓ CF typegen completed successfully");
135
124
  } catch (error: unknown) {
@@ -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
+ }