@@ -10,6 +10,92 @@ interface WorkerDeployOptions {
1010 db ?: string [ ] ;
1111 bucket ?: string [ ] ;
1212 enableAnalytics ?: boolean ;
13+ env ?: string [ ] ;
14+ envFile ?: string ;
15+ }
16+
17+ // Reserved env var names that conflict with existing bindings
18+ const RESERVED_ENV_NAMES = [ 'DB' , 'BUCKET' , 'ANALYTICS' , 'USER_NAMESPACE' ] ;
19+
20+ // Patterns that suggest sensitive data (warn user about plain text storage)
21+ const SENSITIVE_PATTERNS = [ / S E C R E T / i, / P A S S W O R D / i, / K E Y / i, / T O K E N / i, / C R E D E N T I A L / i] ;
22+
23+ /**
24+ * Validate an environment variable name
25+ * Must be a valid identifier and not reserved
26+ */
27+ function validateEnvVarName ( name : string ) : { valid : boolean ; error ?: string } {
28+ // Check if it's a valid identifier (starts with letter or underscore, contains only alphanumeric and underscores)
29+ if ( ! / ^ [ a - z A - Z _ ] [ a - z A - Z 0 - 9 _ ] * $ / . test ( name ) ) {
30+ return { valid : false , error : `Invalid env var name "${ name } ": must be a valid identifier (letters, numbers, underscores, cannot start with number)` } ;
31+ }
32+
33+ // Check if it's reserved
34+ if ( RESERVED_ENV_NAMES . includes ( name . toUpperCase ( ) ) ) {
35+ return { valid : false , error : `Reserved env var name "${ name } ": conflicts with existing bindings (${ RESERVED_ENV_NAMES . join ( ', ' ) } )` } ;
36+ }
37+
38+ return { valid : true } ;
39+ }
40+
41+ /**
42+ * Parse a KEY=VALUE string into key and value
43+ */
44+ function parseEnvArg ( arg : string ) : { key : string ; value : string } | null {
45+ const eqIndex = arg . indexOf ( '=' ) ;
46+ if ( eqIndex === - 1 ) {
47+ return null ;
48+ }
49+ const key = arg . slice ( 0 , eqIndex ) ;
50+ const value = arg . slice ( eqIndex + 1 ) ;
51+ return { key, value } ;
52+ }
53+
54+ /**
55+ * Parse a .env file into a key-value record
56+ * Supports:
57+ * - KEY=VALUE format
58+ * - Comments starting with #
59+ * - Empty lines
60+ * - Quoted values (single or double quotes)
61+ */
62+ function parseEnvFile ( filePath : string ) : Record < string , string > {
63+ const absolutePath = path . resolve ( filePath ) ;
64+ if ( ! fs . existsSync ( absolutePath ) ) {
65+ throw new Error ( `Env file not found: ${ absolutePath } ` ) ;
66+ }
67+
68+ const content = fs . readFileSync ( absolutePath , 'utf-8' ) ;
69+ const result : Record < string , string > = { } ;
70+
71+ for ( const line of content . split ( '\n' ) ) {
72+ const trimmed = line . trim ( ) ;
73+
74+ // Skip empty lines and comments
75+ if ( ! trimmed || trimmed . startsWith ( '#' ) ) {
76+ continue ;
77+ }
78+
79+ const eqIndex = trimmed . indexOf ( '=' ) ;
80+ if ( eqIndex === - 1 ) {
81+ continue ;
82+ }
83+
84+ const key = trimmed . slice ( 0 , eqIndex ) . trim ( ) ;
85+ let value = trimmed . slice ( eqIndex + 1 ) . trim ( ) ;
86+
87+ // Remove surrounding quotes if present
88+ if ( ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) ||
89+ ( value . startsWith ( "'" ) && value . endsWith ( "'" ) ) ) {
90+ value = value . slice ( 1 , - 1 ) ;
91+ }
92+
93+ if ( key ) {
94+ result [ key ] = value ;
95+ }
96+ }
97+
98+ return result ;
1399}
14100
15101interface WorkerLogsOptions {
@@ -55,6 +141,51 @@ export async function workerDeployCommand(
55141 } ;
56142 } ) ;
57143
144+ // Process environment variables
145+ // Start with env file (lower precedence)
146+ const envVars : Record < string , string > = { } ;
147+
148+ if ( options . envFile ) {
149+ try {
150+ const fileEnvVars = parseEnvFile ( options . envFile ) ;
151+ Object . assign ( envVars , fileEnvVars ) ;
152+ } catch ( error ) {
153+ console . error ( chalk . red ( `Error: ${ ( error as Error ) . message } ` ) ) ;
154+ process . exit ( 1 ) ;
155+ }
156+ }
157+
158+ // Process --env flags (higher precedence, overrides file)
159+ if ( options . env && options . env . length > 0 ) {
160+ for ( const envArg of options . env ) {
161+ const parsed = parseEnvArg ( envArg ) ;
162+ if ( ! parsed ) {
163+ console . error ( chalk . red ( `Error: Invalid env var format "${ envArg } ". Expected KEY=VALUE` ) ) ;
164+ process . exit ( 1 ) ;
165+ }
166+ envVars [ parsed . key ] = parsed . value ;
167+ }
168+ }
169+
170+ // Validate all env var names
171+ for ( const key of Object . keys ( envVars ) ) {
172+ const validation = validateEnvVarName ( key ) ;
173+ if ( ! validation . valid ) {
174+ console . error ( chalk . red ( `Error: ${ validation . error } ` ) ) ;
175+ process . exit ( 1 ) ;
176+ }
177+ }
178+
179+ // Warn about sensitive-looking variables
180+ const sensitiveVars = Object . keys ( envVars ) . filter ( ( key ) =>
181+ SENSITIVE_PATTERNS . some ( ( pattern ) => pattern . test ( key ) )
182+ ) ;
183+ if ( sensitiveVars . length > 0 ) {
184+ console . log ( chalk . yellow ( `Warning: The following env vars may contain sensitive data and will be stored as plain text:` ) ) ;
185+ console . log ( chalk . yellow ( ` ${ sensitiveVars . join ( ', ' ) } ` ) ) ;
186+ console . log ( chalk . yellow ( ` Consider using Cloudflare Secrets for sensitive values.` ) ) ;
187+ }
188+
58189 const args : Record < string , unknown > = { name, code } ;
59190 if ( databaseBindings && databaseBindings . length > 0 ) {
60191 args . database_bindings = databaseBindings ;
@@ -65,6 +196,9 @@ export async function workerDeployCommand(
65196 if ( options . enableAnalytics ) {
66197 args . enable_analytics = true ;
67198 }
199+ if ( Object . keys ( envVars ) . length > 0 ) {
200+ args . env_vars = envVars ;
201+ }
68202
69203 const result = await callTool ( SERVER , 'deploy_worker' , args ) ;
70204 console . log ( result ) ;
0 commit comments