@@ -33,25 +33,36 @@ import type {
3333 Bot ,
3434 BotsState ,
3535 BotTags ,
36+ Result ,
3637 StoredAux ,
3738 StoredAuxVersion1 ,
3839} from '@casual-simulation/aux-common' ;
3940import {
41+ calculateStringTagValue ,
4042 createBot ,
4143 DATE_TAG_PREFIX ,
4244 DNA_TAG_PREFIX ,
45+ failure ,
4346 getBotsStateFromStoredAux ,
4447 getSessionKeyExpiration ,
4548 getUploadState ,
4649 hasValue ,
4750 isExpired ,
51+ isFailure ,
52+ isFormula ,
53+ isModule ,
54+ isScript ,
4855 LIBRARY_SCRIPT_PREFIX ,
4956 merge ,
5057 NUMBER_TAG_PREFIX ,
58+ parseFormula ,
59+ parseModule ,
60+ parseScript ,
5161 parseSessionKey ,
5262 parseVersionNumber ,
5363 ROTATION_TAG_PREFIX ,
5464 STRING_TAG_PREFIX ,
65+ success ,
5566 tryParseJson ,
5667 VECTOR_TAG_PREFIX ,
5768 willExpire ,
@@ -86,6 +97,8 @@ import {
8697import standardMimeTypes from 'mime/types/standard.js' ;
8798import otherMimeTypes from 'mime/types/other.js' ;
8899import { parseEntitlements } from 'cli-utils' ;
100+ import { Transpiler } from '@casual-simulation/aux-runtime' ;
101+ import { groupBy } from 'es-toolkit' ;
89102
90103const mime = new Mime ( standardMimeTypes , otherMimeTypes , {
91104 'application/json' : [ 'aux' , 'json' ] ,
@@ -358,6 +371,159 @@ program
358371 }
359372 } ) ;
360373
374+ program
375+ . command ( 'check-aux' )
376+ . description ( 'Check if an AUX file is valid and can be loaded.' )
377+ . argument ( '[input]' , 'The AUX file to check.' )
378+ . option (
379+ '--skip-scripts' ,
380+ 'Whether to skip checking script tags. By default, script tags are checked and any errors in them will be reported.'
381+ )
382+ . action ( async ( input , options ) => {
383+ // Implement the action for checking the AUX file here.
384+
385+ const skipScripts = options . skipScripts ?? false ;
386+ const inputPath = path . resolve ( input ) ;
387+ const auxJson = tryParseJson ( await readFile ( inputPath , 'utf-8' ) ) ;
388+ if ( auxJson . success === false ) {
389+ console . error ( `Could not parse aux file: ${ auxJson . error } ` ) ;
390+ process . exit ( 1 ) ;
391+ }
392+
393+ const aux = STORED_AUX_SCHEMA . safeParse ( auxJson . value ) ;
394+
395+ if ( aux . success === false ) {
396+ console . error (
397+ `Aux file is not a valid stored aux: ${ aux . error . toString ( ) } `
398+ ) ;
399+ process . exit ( 1 ) ;
400+ }
401+
402+ const botsState = getBotsStateFromStoredAux (
403+ aux . data as StoredAuxVersion1
404+ ) ;
405+
406+ interface ValueError {
407+ bot : Bot ;
408+ tag : string ;
409+ space : string ;
410+ errorCode : string ;
411+ errorMessage : string ;
412+ }
413+
414+ const transpiler = new Transpiler ( ) ;
415+
416+ function checkCode (
417+ bot : Bot ,
418+ tag : string ,
419+ space : string ,
420+ code : string
421+ ) : Result < void , ValueError > {
422+ try {
423+ transpiler . transpile ( code ) ;
424+ } catch ( err ) {
425+ return failure ( {
426+ bot,
427+ tag,
428+ space,
429+ errorCode : 'invalid_script' ,
430+ errorMessage : err . toString ( ) ,
431+ } ) ;
432+ }
433+
434+ return success ( ) ;
435+ }
436+
437+ function checkValue (
438+ bot : Bot ,
439+ tag : string ,
440+ space : string ,
441+ value : any
442+ ) : Result < void , ValueError > {
443+ if ( isFormula ( value ) ) {
444+ const formula = parseFormula ( value ) ;
445+ const parsed = tryParseJson ( formula ) ;
446+ if ( parsed . success === false ) {
447+ return failure ( {
448+ bot,
449+ tag,
450+ space,
451+ errorCode : 'invalid_formula' ,
452+ errorMessage : parsed . error as string ,
453+ } ) ;
454+ }
455+ } else if ( ! skipScripts ) {
456+ if ( isScript ( value ) ) {
457+ const code = parseScript ( value ) ;
458+ return checkCode ( bot , tag , space , code ) ;
459+ } else if ( isModule ( value ) ) {
460+ const code = parseModule ( value ) ;
461+ return checkCode ( bot , tag , space , code ) ;
462+ }
463+ }
464+
465+ return success ( ) ;
466+ }
467+
468+ const errors : ValueError [ ] = [ ] ;
469+
470+ for ( let botId in botsState ) {
471+ const bot = botsState [ botId ] ;
472+ for ( let tag in bot . tags ) {
473+ const value = bot . tags [ tag ] ;
474+ const result = checkValue ( bot , tag , null , value ) ;
475+ if ( isFailure ( result ) ) {
476+ errors . push ( result . error ) ;
477+ }
478+ }
479+
480+ for ( let space in bot . masks ) {
481+ const masks = bot . masks [ space ] ;
482+ for ( let tag in masks ) {
483+ const value = masks [ tag ] ;
484+ const result = checkValue ( bot , tag , space , value ) ;
485+ if ( isFailure ( result ) ) {
486+ errors . push ( result . error ) ;
487+ }
488+ }
489+ }
490+ }
491+
492+ if ( errors . length > 0 ) {
493+ console . error ( `Found ${ errors . length } errors in aux file:` ) ;
494+ const byBot = groupBy ( errors , ( error ) => error . bot . id ) ;
495+
496+ for ( let botId in byBot ) {
497+ const botErrors = byBot [ botId ] ;
498+ const bot = botErrors [ 0 ] . bot ;
499+ const system = calculateStringTagValue (
500+ null ,
501+ bot ,
502+ 'system' ,
503+ null
504+ ) ;
505+ console . error (
506+ `Bot: ${ bot . id } ${ system ? `, System: ${ system } ` : '' } `
507+ ) ;
508+ for ( let error of botErrors ) {
509+ console . error (
510+ ` Tag: ${ error . tag } ${
511+ error . space ? `, Space: ${ error . space } ` : ''
512+ } , Error: (${ error . errorCode } ) ${ error . errorMessage } `
513+ ) ;
514+ }
515+ }
516+
517+ process . exit ( 2 ) ;
518+ } else {
519+ console . log (
520+ `Aux file is valid! No errors found in ${
521+ Object . keys ( botsState ) . length
522+ } bots.`
523+ ) ;
524+ }
525+ } ) ;
526+
361527program
362528 . command ( 'unpack-aux' )
363529 . argument ( '[input]' , 'The aux file/directory to convert to a file system.' )
0 commit comments