@@ -255,14 +255,10 @@ export function generateShellScript(
255255 progressTracker . complete ( ) ;
256256 }
257257
258- const provisionalScript = scriptParts . filter ( part => part . trim ( ) !== '' ) . join ( '\n\n' ) ;
259- const internalVariableExports = collectInternalVariableExports ( provisionalScript ) ;
260- if ( internalVariableExports ) {
261- const insertIndex = variableSetup . trim ( ) ? 2 : 1 ;
262- scriptParts . splice ( insertIndex , 0 , internalVariableExports ) ;
263- }
264-
265258 const script = scriptParts . filter ( part => part . trim ( ) !== '' ) . join ( '\n\n' ) ;
259+ const declaredVariables = collectDeclaredVariables ( workflow , allVariables ) ;
260+ const unboundWarnings = detectPotentialUnboundVariables ( script , declaredVariables ) ;
261+ warnings . push ( ...unboundWarnings ) ;
266262
267263 if ( monitor ) {
268264 monitor . finish ( true ) ;
@@ -475,11 +471,64 @@ function generateVariableSetup(workflow: FlowshWorkflow, variables: Map<string,
475471}
476472
477473/**
478- * Collect internal variable assignments and export them for subshell usage .
474+ * Collect variables declared through workflow configuration and node registration .
479475 */
480- function collectInternalVariableExports ( shellScript : string ) : string {
481- const exportPattern = / ^ \s * e x p o r t \s + ( [ A - Z _ ] [ A - Z 0 - 9 _ ] * ) / gm;
482- const assignmentPattern = / ^ (? ! \s * (?: l o c a l | d e c l a r e | t y p e s e t ) \b ) \s * ( [ A - Z _ ] [ A - Z 0 - 9 _ ] * ) = / ;
476+ function collectDeclaredVariables (
477+ workflow : FlowshWorkflow ,
478+ variables : Map < string , string >
479+ ) : Set < string > {
480+ const declared = new Set < string > ( variables . keys ( ) ) ;
481+ const envVars = workflow . environment_variables || workflow . spec ?. environment_variables || [ ] ;
482+ const conversationVars =
483+ workflow . conversation_variables || workflow . spec ?. conversation_variables || [ ] ;
484+
485+ for ( const envVar of envVars ) {
486+ if ( envVar . variable ) {
487+ declared . add ( envVar . variable ) ;
488+ }
489+ }
490+
491+ for ( const convVar of conversationVars ) {
492+ if ( convVar . variable ) {
493+ declared . add ( convVar . variable ) ;
494+ }
495+ }
496+
497+ return declared ;
498+ }
499+
500+ function detectPotentialUnboundVariables (
501+ shellScript : string ,
502+ declaredVariables : Set < string >
503+ ) : string [ ] {
504+ const exportPattern = / ^ \s * e x p o r t \s + ( [ A - Z _ ] [ A - Z 0 - 9 _ ] * ) (?: = | $ ) / gm;
505+ const assignmentPattern = / ^ (? ! \s * (?: l o c a l | d e c l a r e | t y p e s e t | r e a d o n l y ) \b ) \s * ( [ A - Z _ ] [ A - Z 0 - 9 _ ] * ) = / ;
506+ const referencePattern = / \$ \{ ? ( [ A - Z _ ] [ A - Z 0 - 9 _ ] * ) \} ? / g;
507+ const knownShellVars = new Set ( [
508+ 'BASH_VERSION' ,
509+ 'BASH_SOURCE' ,
510+ 'BASH_LINENO' ,
511+ 'FUNCNAME' ,
512+ 'LINENO' ,
513+ 'PWD' ,
514+ 'OLDPWD' ,
515+ 'HOME' ,
516+ 'PATH' ,
517+ 'IFS' ,
518+ 'SHELLOPTS' ,
519+ 'UID' ,
520+ 'EUID' ,
521+ 'PPID' ,
522+ 'SHLVL' ,
523+ 'OSTYPE' ,
524+ 'MACHTYPE' ,
525+ 'HOSTNAME' ,
526+ 'HOSTTYPE' ,
527+ 'TERM' ,
528+ 'COLUMNS' ,
529+ 'LINES' ,
530+ 'RANDOM' ,
531+ ] ) ;
483532
484533 const exportedVars = new Set < string > ( ) ;
485534 for ( const match of shellScript . matchAll ( exportPattern ) ) {
@@ -488,26 +537,45 @@ function collectInternalVariableExports(shellScript: string): string {
488537 }
489538 }
490539
491- const foundVars : string [ ] = [ ] ;
492- const seenVars = new Set < string > ( ) ;
540+ const assignedVars = new Set < string > ( ) ;
493541 for ( const line of shellScript . split ( '\n' ) ) {
494- const match = line . match ( assignmentPattern ) ;
495- if ( ! match || ! match [ 1 ] ) {
542+ const trimmed = line . trim ( ) ;
543+ if ( ! trimmed || trimmed . startsWith ( '#' ) ) {
496544 continue ;
497545 }
498- const varName = match [ 1 ] ;
499- if ( ! exportedVars . has ( varName ) && ! seenVars . has ( varName ) ) {
500- seenVars . add ( varName ) ;
501- foundVars . push ( varName ) ;
546+ const match = trimmed . match ( assignmentPattern ) ;
547+ if ( match ?. [ 1 ] ) {
548+ assignedVars . add ( match [ 1 ] ) ;
502549 }
503550 }
504551
505- if ( foundVars . length === 0 ) {
506- return '' ;
552+ const warningVars = new Set < string > ( ) ;
553+ for ( const line of shellScript . split ( '\n' ) ) {
554+ const trimmed = line . trim ( ) ;
555+ if ( ! trimmed || trimmed . startsWith ( '#' ) ) {
556+ continue ;
557+ }
558+ const matches = trimmed . matchAll ( referencePattern ) ;
559+ for ( const match of matches ) {
560+ const varName = match [ 1 ] ;
561+ if ( ! varName ) {
562+ continue ;
563+ }
564+ if (
565+ declaredVariables . has ( varName ) ||
566+ assignedVars . has ( varName ) ||
567+ exportedVars . has ( varName ) ||
568+ knownShellVars . has ( varName )
569+ ) {
570+ continue ;
571+ }
572+ warningVars . add ( varName ) ;
573+ }
507574 }
508575
509- const exportLines = foundVars . map ( varName => `export ${ varName } ` ) ;
510- return `# Internal Variable Exports\n${ exportLines . join ( '\n' ) } ` ;
576+ return [ ...warningVars ] . sort ( ) . map ( varName => {
577+ return `Potentially unbound variable '${ varName } ' referenced in generated script` ;
578+ } ) ;
511579}
512580
513581/**
0 commit comments