11import  type  PolykeyClient  from  'polykey/PolykeyClient.js' ; 
2- import  type  {  ParsedSecretPathValue  }  from  '../types.js' ; 
2+ import  type  {  JSONSchema ,   ParsedSecretPathValue  }  from  '../types.js' ; 
33import  path  from  'node:path' ; 
44import  os  from  'node:os' ; 
5+ import  $RefParser  from  '@apidevtools/json-schema-ref-parser' ; 
6+ import  {  Ajv2019  as  Ajv  }  from  'ajv/dist/2019.js' ; 
57import  {  InvalidArgumentError  }  from  'commander' ; 
68import  *  as  utils  from  'polykey/utils/index.js' ; 
79import  CommandPolykey  from  '../CommandPolykey.js' ; 
@@ -26,6 +28,7 @@ class CommandEnv extends CommandPolykey {
2628    this . addOption ( binOptions . envDuplicate ) ; 
2729    this . addOption ( binOptions . envExport ) ; 
2830    this . addOption ( binOptions . preserveNewline ) ; 
31+     this . addOption ( binOptions . egressSchema ) ; 
2932    this . argument ( 
3033      '<args...>' , 
3134      'command and arguments formatted as <envPaths...> [-- cmd [cmdArgs...]]' , 
@@ -53,6 +56,7 @@ class CommandEnv extends CommandPolykey {
5356        if  ( secretPath  ==  null )  preservedSecrets . add ( vaultName ) ; 
5457        else  preservedSecrets . add ( `${ vaultName }  :${ secretPath }  ` ) ; 
5558      } 
59+ 
5660      // There are a few stages here 
5761      // 1. parse the desired secrets 
5862      // 2. obtain the desired secrets 
@@ -226,7 +230,50 @@ class CommandEnv extends CommandPolykey {
226230            } ; 
227231          } 
228232          await  writeP ; 
229-           return  [ envp ,  envpPath ] ; 
233+ 
234+           // Apply validation using the schema 
235+           // TODO: filter before pulling instead of after 
236+           const  filteredEnvp : Record < string ,  string >  =  { } ; 
237+           if  ( options . egressSchema  !=  null )  { 
238+             // Resolve references and bundle schema 
239+             const  schema : JSONSchema  =  await  $RefParser . bundle ( 
240+               options . egressSchema , 
241+             ) ; 
242+ 
243+             // Validate the incoming secrets against the schema 
244+             const  ajv  =  new  Ajv ( { 
245+               coerceTypes : true , 
246+               useDefaults : false , 
247+               allErrors : true , 
248+             } ) ; 
249+             const  validate  =  ajv . compile ( schema ) ; 
250+             validate ( envp ) ; 
251+ 
252+             // Extract relevant keys, discarding the rest 
253+             const  {  requiredKeys,  allKeys,  defaults }  = 
254+               binUtils . loadSchema ( schema ) ; 
255+ 
256+             for  ( const  key  of  allKeys )  { 
257+               let  value  =  envp [ key ] ; 
258+               if  ( value  ==  null  &&  defaults [ key ]  !=  null )  { 
259+                 value  =  defaults [ key ] ; 
260+               } 
261+               if  ( 
262+                 requiredKeys . includes ( key )  && 
263+                 ( value  ==  null  ||  value  ===  '' ) 
264+               )  { 
265+                 throw  new  Error ( 'TMP missing required variable' ) ; 
266+               } 
267+               if  ( value  !=  null )  { 
268+                 filteredEnvp [ key ]  =  value . toString ( ) ; 
269+               } 
270+             } 
271+           } 
272+ 
273+           return  [ 
274+             utils . isEmptyObject ( filteredEnvp )  ? envp  : filteredEnvp , 
275+             envpPath , 
276+           ] ; 
230277        } ,  meta ) ; 
231278        // End connection early to avoid errors on server 
232279        await  pkClient . stop ( ) ; 
0 commit comments