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