2022-08-17 22:03:21 +00:00
#!/usr/bin/env node
2020-02-28 14:17:22 +00:00
/ * C h e c k s f o r a n y o b v i o u s p r o b l e m s i n a p p s . j s o n
* /
var fs = require ( "fs" ) ;
2022-11-21 12:16:27 +00:00
var heatshrink = require ( "../webtools/heatshrink" ) ;
2020-02-28 14:17:22 +00:00
var acorn ;
try {
acorn = require ( "acorn" ) ;
} catch ( e ) {
console . log ( "=====================================================" ) ;
console . log ( " ACORN NOT FOUND" ) ;
console . log ( " ---------------" ) ;
console . log ( "" ) ;
console . log ( " This means we won't sanity-check uploaded JSON" ) ;
console . log ( "=====================================================" ) ;
}
var BASEDIR = _ _dirname + "/../" ;
2022-06-28 10:51:25 +00:00
var APPSDIR _RELATIVE = "apps/" ;
var APPSDIR = BASEDIR + APPSDIR _RELATIVE ;
var warningCount = 0 ;
var errorCount = 0 ;
function ERROR ( msg , opt ) {
// file=app.js,line=1,col=5,endColumn=7
opt = opt || { } ;
console . log ( ` ::error ${ Object . keys ( opt ) . length ? " " : "" } ${ Object . keys ( opt ) . map ( k => k + "=" + opt [ k ] ) . join ( "," ) } :: ${ msg } ` ) ;
errorCount ++ ;
2020-02-28 14:17:22 +00:00
}
2022-06-28 10:51:25 +00:00
function WARN ( msg , opt ) {
// file=app.js,line=1,col=5,endColumn=7
opt = opt || { } ;
2022-09-05 12:38:34 +00:00
if ( KNOWN _WARNINGS . includes ( msg ) ) {
console . log ( ` Known warning : ${ msg } ` ) ;
} else {
console . log ( ` ::warning ${ Object . keys ( opt ) . length ? " " : "" } ${ Object . keys ( opt ) . map ( k => k + "=" + opt [ k ] ) . join ( "," ) } :: ${ msg } ` ) ;
}
2022-06-28 10:51:25 +00:00
warningCount ++ ;
2020-02-28 14:17:22 +00:00
}
2022-01-07 00:14:18 +00:00
var apps = [ ] ;
var dirs = fs . readdirSync ( APPSDIR , { withFileTypes : true } ) ;
dirs . forEach ( dir => {
var appsFile ;
2022-01-19 15:56:33 +00:00
if ( dir . name . startsWith ( "_example" ) || ! dir . isDirectory ( ) )
2022-01-07 00:14:18 +00:00
return ;
try {
appsFile = fs . readFileSync ( APPSDIR + dir . name + "/metadata.json" ) . toString ( ) ;
} catch ( e ) {
2022-01-19 15:56:33 +00:00
ERROR ( dir . name + "/metadata.json does not exist" ) ;
2022-01-07 00:14:18 +00:00
return ;
2020-05-04 08:28:27 +00:00
}
2022-01-07 00:14:18 +00:00
try {
apps . push ( JSON . parse ( appsFile ) ) ;
} catch ( e ) {
console . log ( e ) ;
var m = e . toString ( ) . match ( /in JSON at position (\d+)/ ) ;
2022-06-28 10:51:25 +00:00
var messageInfo = {
file : "apps/" + dir . name + "/metadata.json" ,
} ;
2022-01-07 00:14:18 +00:00
if ( m ) {
var char = parseInt ( m [ 1 ] ) ;
2022-06-28 10:51:25 +00:00
messageInfo . line = appsFile . substr ( 0 , char ) . split ( "\n" ) . length ;
2022-01-07 00:14:18 +00:00
console . log ( "===============================================" ) ;
2022-06-28 10:51:25 +00:00
console . log ( "LINE " + messageInfo . line ) ;
2022-01-07 00:14:18 +00:00
console . log ( "===============================================" ) ;
console . log ( appsFile . substr ( char - 10 , 20 ) ) ;
console . log ( "===============================================" ) ;
}
console . log ( m ) ;
2022-06-28 10:51:25 +00:00
ERROR ( messageInfo . file + " not valid JSON" , messageInfo ) ;
2022-01-07 00:14:18 +00:00
}
} ) ;
2020-02-28 14:17:22 +00:00
2020-04-11 23:11:47 +00:00
const APP _KEYS = [
2021-10-28 11:14:02 +00:00
'id' , 'name' , 'shortName' , 'version' , 'icon' , 'screenshots' , 'description' , 'tags' , 'type' ,
2021-10-20 14:11:04 +00:00
'sortorder' , 'readme' , 'custom' , 'customConnect' , 'interface' , 'storage' , 'data' ,
2021-10-28 11:14:02 +00:00
'supports' , 'allow_emulator' ,
2022-11-16 15:17:28 +00:00
'dependencies' , 'provides_modules'
2020-04-11 23:11:47 +00:00
] ;
2022-10-12 08:06:25 +00:00
const STORAGE _KEYS = [ 'name' , 'url' , 'content' , 'evaluate' , 'noOverwite' , 'supports' , 'noOverwrite' ] ;
2021-04-09 08:58:38 +00:00
const DATA _KEYS = [ 'name' , 'wildcard' , 'storageFile' , 'url' , 'content' , 'evaluate' ] ;
2022-01-12 11:05:27 +00:00
const SUPPORTS _DEVICES = [ "BANGLEJS" , "BANGLEJS2" ] ; // device IDs allowed for 'supports'
2022-11-21 16:37:04 +00:00
const METADATA _TYPES = [ "app" , "clock" , "widget" , "bootloader" , "RAM" , "launch" , "scheduler" , "notify" , "locale" , "settings" , "waypoints" , "textinput" , "module" , "clkinfo" ] ; // values allowed for "type" field
2020-04-16 15:06:25 +00:00
const FORBIDDEN _FILE _NAME _CHARS = /[,;]/ ; // used as separators in appid.info
2020-05-04 08:32:50 +00:00
const VALID _DUPLICATES = [ '.tfmodel' , '.tfnames' ] ;
2021-12-17 14:16:35 +00:00
const GRANDFATHERED _ICONS = [ "s7clk" , "snek" , "astral" , "alpinenav" , "slomoclock" , "arrow" , "pebble" , "rebble" ] ;
2022-07-26 13:54:34 +00:00
const INTERNAL _FILES _IN _APP _TYPE = { // list of app types and files they SHOULD provide...
'textinput' : [ 'textinput' ] ,
2022-07-26 15:14:04 +00:00
'waypoints' : [ 'waypoints' ] ,
2022-07-26 13:54:34 +00:00
// notify?
} ;
2022-09-05 12:38:34 +00:00
/* These are warnings we know about but don't want in our output */
var KNOWN _WARNINGS = [
"App gpsrec data file wildcard .gpsrc? does not include app ID"
] ;
2020-04-11 23:11:47 +00:00
2020-04-16 15:06:25 +00:00
function globToRegex ( pattern ) {
const ESCAPE = '.*+-?^${}()|[]\\' ;
const regex = pattern . replace ( /./g , c => {
switch ( c ) {
case '?' : return '.' ;
case '*' : return '.*' ;
default : return ESCAPE . includes ( c ) ? ( '\\' + c ) : c ;
}
} ) ;
return new RegExp ( '^' + regex + '$' ) ;
}
const isGlob = f => / [ ? * ] / . test ( f )
// All storage+data files in all apps: {app:<appid>,[file:<storage.name> | data:<data.name|data.wildcard>]}
let allFiles = [ ] ;
2022-01-04 09:37:59 +00:00
let existingApps = [ ] ;
2020-04-11 23:11:47 +00:00
apps . forEach ( ( app , appIdx ) => {
2020-02-28 14:17:22 +00:00
if ( ! app . id ) ERROR ( ` App ${ appIdx } has no id ` ) ;
2022-06-28 10:51:25 +00:00
var appDirRelative = APPSDIR _RELATIVE + app . id + "/" ;
var appDir = APPSDIR + app . id + "/" ;
var metadataFile = appDirRelative + "metadata.json" ;
if ( existingApps . includes ( app . id ) ) ERROR ( ` Duplicate app ' ${ app . id } ' ` , { file : metadataFile } ) ;
2022-01-04 09:37:59 +00:00
existingApps . push ( app . id ) ;
2020-02-28 17:01:29 +00:00
//console.log(`Checking ${app.id}...`);
2022-06-28 10:51:25 +00:00
2020-02-28 14:17:22 +00:00
if ( ! fs . existsSync ( APPSDIR + app . id ) ) ERROR ( ` App ${ app . id } has no directory ` ) ;
2022-06-28 10:51:25 +00:00
if ( ! app . name ) ERROR ( ` App ${ app . id } has no name ` , { file : metadataFile } ) ;
2020-02-28 14:17:22 +00:00
var isApp = ! app . type || app . type == "app" ;
2022-06-28 10:51:25 +00:00
if ( app . name . length > 20 && ! app . shortName && isApp ) ERROR ( ` App ${ app . id } has a long name, but no shortName ` , { file : metadataFile } ) ;
2022-04-29 08:29:02 +00:00
if ( app . type && ! METADATA _TYPES . includes ( app . type ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } 'type' is one one of ` + METADATA _TYPES , { file : metadataFile } ) ;
if ( ! Array . isArray ( app . supports ) ) ERROR ( ` App ${ app . id } has no 'supports' field or it's not an array ` , { file : metadataFile } ) ;
2021-10-20 14:11:04 +00:00
else {
app . supports . forEach ( dev => {
2022-01-12 11:05:27 +00:00
if ( ! SUPPORTS _DEVICES . includes ( dev ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } has unknown device in 'supports' field - ${ dev } ` , { file : metadataFile } ) ;
2021-10-20 14:11:04 +00:00
} ) ;
}
2022-06-28 10:51:25 +00:00
if ( ! app . version ) ERROR ( ` App ${ app . id } has no version ` , { file : metadataFile } ) ;
2020-03-05 13:15:03 +00:00
else {
if ( ! fs . existsSync ( appDir + "ChangeLog" ) ) {
2022-07-06 08:30:44 +00:00
var invalidChangeLog = fs . readdirSync ( appDir ) . find ( f => f . toLowerCase ( ) . startsWith ( "changelog" ) && f != "ChangeLog" ) ;
if ( invalidChangeLog )
ERROR ( ` App ${ app . id } has wrongly named ChangeLog ( ${ invalidChangeLog } ) ` , { file : appDirRelative + invalidChangeLog } ) ;
else if ( app . version != "0.01" )
2022-06-28 10:51:25 +00:00
WARN ( ` App ${ app . id } has no ChangeLog ` , { file : metadataFile } ) ;
2020-03-05 13:15:03 +00:00
} else {
2020-03-30 15:23:45 +00:00
var changeLog = fs . readFileSync ( appDir + "ChangeLog" ) . toString ( ) ;
var versions = changeLog . match ( /\d+\.\d+:/g ) ;
2022-06-28 10:51:25 +00:00
if ( ! versions ) ERROR ( ` No versions found in ${ app . id } ChangeLog ( ${ appDir } ChangeLog) ` , { file : metadataFile } ) ;
2020-03-05 13:15:03 +00:00
var lastChangeLog = versions . pop ( ) . slice ( 0 , - 1 ) ;
if ( lastChangeLog != app . version )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } app version ( ${ app . version } ) and ChangeLog ( ${ lastChangeLog } ) don't agree ` , { file : appDirRelative + "ChangeLog" , line : changeLog . split ( "\n" ) . length - 1 } ) ;
2020-03-05 13:15:03 +00:00
}
}
2022-06-28 10:51:25 +00:00
if ( ! app . description ) ERROR ( ` App ${ app . id } has no description ` , { file : metadataFile } ) ;
if ( ! app . icon ) ERROR ( ` App ${ app . id } has no icon ` , { file : metadataFile } ) ;
if ( ! fs . existsSync ( appDir + app . icon ) ) ERROR ( ` App ${ app . id } icon doesn't exist ` , { file : metadataFile } ) ;
2021-10-28 11:14:02 +00:00
if ( app . screenshots ) {
2022-06-28 10:51:25 +00:00
if ( ! Array . isArray ( app . screenshots ) ) ERROR ( ` App ${ app . id } screenshots is not an array ` , { file : metadataFile } ) ;
2021-10-28 11:14:02 +00:00
app . screenshots . forEach ( screenshot => {
if ( ! fs . existsSync ( appDir + screenshot . url ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } screenshot file ${ screenshot . url } not found ` , { file : metadataFile } ) ;
2021-10-28 11:14:02 +00:00
} ) ;
}
2022-06-28 10:51:25 +00:00
if ( app . readme && ! fs . existsSync ( appDir + app . readme ) ) ERROR ( ` App ${ app . id } README file doesn't exist ` , { file : metadataFile } ) ;
if ( app . custom && ! fs . existsSync ( appDir + app . custom ) ) ERROR ( ` App ${ app . id } custom HTML doesn't exist ` , { file : metadataFile } ) ;
if ( app . customConnect && ! app . custom ) ERROR ( ` App ${ app . id } has customConnect but no customn HTML ` , { file : metadataFile } ) ;
if ( app . interface && ! fs . existsSync ( appDir + app . interface ) ) ERROR ( ` App ${ app . id } interface HTML doesn't exist ` , { file : metadataFile } ) ;
2020-06-04 14:48:27 +00:00
if ( app . dependencies ) {
if ( ( "object" == typeof app . dependencies ) && ! Array . isArray ( app . dependencies ) ) {
Object . keys ( app . dependencies ) . forEach ( dependency => {
2022-11-16 15:17:28 +00:00
if ( ! [ "type" , "app" , "module" ] . includes ( app . dependencies [ dependency ] ) )
ERROR ( ` App ${ app . id } 'dependencies' must all be tagged 'type/app/module' right now ` , { file : metadataFile } ) ;
2022-04-29 08:29:02 +00:00
if ( app . dependencies [ dependency ] == "type" && ! METADATA _TYPES . includes ( dependency ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } 'type' dependency must be one of ` + METADATA _TYPES , { file : metadataFile } ) ;
2020-06-04 14:48:27 +00:00
} ) ;
} else
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } 'dependencies' must be an object ` , { file : metadataFile } ) ;
2020-06-04 14:48:27 +00:00
}
2022-11-16 15:17:28 +00:00
2020-02-28 14:17:22 +00:00
var fileNames = [ ] ;
app . storage . forEach ( ( file ) => {
2022-06-28 10:51:25 +00:00
if ( ! file . name ) ERROR ( ` App ${ app . id } has a file with no name ` , { file : metadataFile } ) ;
if ( isGlob ( file . name ) ) ERROR ( ` App ${ app . id } storage file ${ file . name } contains wildcards ` , { file : metadataFile } ) ;
2020-04-16 15:06:25 +00:00
let char = file . name . match ( FORBIDDEN _FILE _NAME _CHARS )
2022-06-28 10:51:25 +00:00
if ( char ) ERROR ( ` App ${ app . id } storage file ${ file . name } contains invalid character " ${ char [ 0 ] } " ` , { file : metadataFile } )
2021-10-28 13:28:30 +00:00
if ( fileNames . includes ( file . name ) && ! file . supports ) // assume that there aren't duplicates if 'supports' is set
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } file ${ file . name } is a duplicate ` , { file : metadataFile } ) ;
2022-01-12 11:05:27 +00:00
if ( file . supports && ! Array . isArray ( file . supports ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } file ${ file . name } supports field must be an array ` , { file : metadataFile } ) ;
2022-01-12 11:05:27 +00:00
if ( file . supports )
file . supports . forEach ( dev => {
if ( ! SUPPORTS _DEVICES . includes ( dev ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } file ${ file . name } has unknown device in 'supports' field - ${ dev } ` , { file : metadataFile } ) ;
2022-01-12 11:05:27 +00:00
} ) ;
2021-10-28 13:28:30 +00:00
fileNames . push ( file . name ) ;
2022-07-26 13:54:34 +00:00
var fileInternal = false ;
if ( app . type && INTERNAL _FILES _IN _APP _TYPE [ app . type ] ) {
if ( INTERNAL _FILES _IN _APP _TYPE [ app . type ] . includes ( file . name ) )
fileInternal = true ;
}
allFiles . push ( { app : app . id , file : file . name , internal : fileInternal } ) ;
2022-06-28 10:51:25 +00:00
if ( file . url ) if ( ! fs . existsSync ( appDir + file . url ) ) ERROR ( ` App ${ app . id } file ${ file . url } doesn't exist ` , { file : metadataFile } ) ;
if ( ! file . url && ! file . content && ! app . custom ) ERROR ( ` App ${ app . id } file ${ file . name } has no contents ` , { file : metadataFile } ) ;
2020-02-28 17:01:29 +00:00
var fileContents = "" ;
if ( file . content ) fileContents = file . content ;
if ( file . url ) fileContents = fs . readFileSync ( appDir + file . url ) . toString ( ) ;
2022-06-28 10:51:25 +00:00
if ( file . supports && ! Array . isArray ( file . supports ) ) ERROR ( ` App ${ app . id } file ${ file . name } supports field is not an array ` , { file : metadataFile } ) ;
2020-02-28 14:17:22 +00:00
if ( file . evaluate ) {
try {
acorn . parse ( "(" + fileContents + ")" ) ;
} catch ( e ) {
console . log ( "=====================================================" ) ;
console . log ( " PARSE OF " + appDir + file . url + " failed." ) ;
console . log ( "" ) ;
console . log ( e ) ;
console . log ( "=====================================================" ) ;
console . log ( fileContents ) ;
console . log ( "=====================================================" ) ;
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } 's ${ file . name } has evaluate:true but is not valid JS expression ` , { file : appDirRelative + file . url } ) ;
2020-02-28 14:17:22 +00:00
}
}
2020-02-28 17:01:29 +00:00
if ( file . name . endsWith ( ".js" ) ) {
// TODO: actual lint?
try {
acorn . parse ( fileContents ) ;
} catch ( e ) {
console . log ( "=====================================================" ) ;
console . log ( " PARSE OF " + appDir + file . url + " failed." ) ;
console . log ( "" ) ;
console . log ( e ) ;
console . log ( "=====================================================" ) ;
console . log ( fileContents ) ;
console . log ( "=====================================================" ) ;
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } 's ${ file . name } is a JS file but isn't valid JS ` , { file : appDirRelative + file . url } ) ;
2020-02-28 17:01:29 +00:00
}
2022-09-05 10:03:41 +00:00
// clock app checks
if ( app . type == "clock" ) {
var a = fileContents . indexOf ( "Bangle.loadWidgets()" ) ;
2022-11-16 15:17:28 +00:00
var b = fileContents . indexOf ( "Bangle.setUI(" ) ;
2022-09-05 10:03:41 +00:00
if ( a >= 0 && b >= 0 && a < b )
2022-09-05 12:38:34 +00:00
WARN ( ` Clock ${ app . id } file calls loadWidgets before setUI (clock widget/etc won't be aware a clock app is running) ` , { file : appDirRelative + file . url , line : fileContents . substr ( 0 , a ) . split ( "\n" ) . length } ) ;
2022-09-05 10:03:41 +00:00
}
2020-02-28 17:01:29 +00:00
}
2020-04-11 23:11:47 +00:00
for ( const key in file ) {
2022-06-28 10:51:25 +00:00
if ( ! STORAGE _KEYS . includes ( key ) ) ERROR ( ` App ${ app . id } file ${ file . name } has unknown key ${ key } ` , { file : appDirRelative + file . url } ) ;
2020-04-11 23:11:47 +00:00
}
2021-12-17 02:52:48 +00:00
// warn if JS icon is the wrong size
if ( file . name == app . id + ".img" ) {
let icon ;
2022-04-19 09:03:46 +00:00
let match = fileContents . match ( /^\s*E\.toArrayBuffer\(atob\(\"([^"]*)\"\)\)\s*$/ ) ;
if ( match == null ) match = fileContents . match ( /^\s*atob\(\"([^"]*)\"\)\s*$/ ) ;
2021-12-17 02:52:48 +00:00
if ( match ) icon = Buffer . from ( match [ 1 ] , 'base64' ) ;
else {
2022-04-19 09:03:46 +00:00
match = fileContents . match ( /^\s*require\(\"heatshrink\"\)\.decompress\(\s*atob\(\s*\"([^"]*)\"\s*\)\s*\)\s*$/ ) ;
2021-12-17 02:52:48 +00:00
if ( match ) icon = heatshrink . decompress ( Buffer . from ( match [ 1 ] , 'base64' ) ) ;
2022-06-28 10:51:25 +00:00
else ERROR ( ` JS icon ${ file . name } does not match the pattern 'require("heatshrink").decompress(atob("..."))' ` , { file : appDirRelative + file . url } ) ;
2021-12-17 02:52:48 +00:00
}
if ( match ) {
2021-12-17 14:16:35 +00:00
if ( icon [ 0 ] > 48 || icon [ 0 ] < 24 || icon [ 1 ] > 48 || icon [ 1 ] < 24 ) {
2022-06-28 10:51:25 +00:00
if ( GRANDFATHERED _ICONS . includes ( app . id ) ) WARN ( ` JS icon ${ file . name } should be 48x48px (or slightly under) but is instead ${ icon [ 0 ] } x ${ icon [ 1 ] } px ` , { file : appDirRelative + file . url } ) ;
else ERROR ( ` JS icon ${ file . name } should be 48x48px (or slightly under) but is instead ${ icon [ 0 ] } x ${ icon [ 1 ] } px ` , { file : appDirRelative + file . url } ) ;
2021-12-17 03:11:29 +00:00
}
2021-12-17 02:52:48 +00:00
}
}
2020-02-28 14:17:22 +00:00
} ) ;
2020-04-11 22:26:08 +00:00
let dataNames = [ ] ;
( app . data || [ ] ) . forEach ( ( data ) => {
2022-06-28 10:51:25 +00:00
if ( ! data . name && ! data . wildcard ) ERROR ( ` App ${ app . id } has a data file with no name ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
if ( dataNames . includes ( data . name || data . wildcard ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file ${ data . name || data . wildcard } is a duplicate ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
dataNames . push ( data . name || data . wildcard )
2020-04-16 15:06:25 +00:00
allFiles . push ( { app : app . id , data : ( data . name || data . wildcard ) } ) ;
2020-04-11 22:26:08 +00:00
if ( 'name' in data && 'wildcard' in data )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file ${ data . name } has both name and wildcard ` , { file : metadataFile } ) ;
2020-04-16 15:06:25 +00:00
if ( isGlob ( data . name ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file name ${ data . name } contains wildcards ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
if ( data . wildcard ) {
2020-04-16 15:06:25 +00:00
if ( ! isGlob ( data . wildcard ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file wildcard ${ data . wildcard } does not actually contains wildcard ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
if ( data . wildcard . replace ( /\?|\*/g , '' ) === '' )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file wildcard ${ data . wildcard } does not contain regular characters ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
else if ( data . wildcard . replace ( /\?|\*/g , '' ) . length < 3 )
2022-06-28 10:51:25 +00:00
WARN ( ` App ${ app . id } data file wildcard ${ data . wildcard } is very broad ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
else if ( ! data . wildcard . includes ( app . id ) )
2022-06-28 10:51:25 +00:00
WARN ( ` App ${ app . id } data file wildcard ${ data . wildcard } does not include app ID ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
}
2020-04-16 15:06:25 +00:00
let char = ( data . name || data . wildcard ) . match ( FORBIDDEN _FILE _NAME _CHARS )
2022-06-28 10:51:25 +00:00
if ( char ) ERROR ( ` App ${ app . id } data file ${ data . name || data . wildcard } contains invalid character " ${ char [ 0 ] } " ` , { file : metadataFile } )
2020-04-11 22:26:08 +00:00
if ( 'storageFile' in data && typeof data . storageFile !== 'boolean' )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file ${ data . name || data . wildcard } has non-boolean value for "storageFile" ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
for ( const key in data ) {
2020-04-15 19:30:44 +00:00
if ( ! DATA _KEYS . includes ( key ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ app . id } data file ${ data . name || data . wildcard } has unknown property " ${ key } " ` , { file : metadataFile } ) ;
2020-04-11 22:26:08 +00:00
}
} ) ;
2020-04-16 15:06:25 +00:00
// prefer "appid.json" over "appid.settings.json" (TODO: change to ERROR once all apps comply?)
2021-12-09 17:16:55 +00:00
/ * i f ( d a t a N a m e s . i n c l u d e s ( a p p . i d + " . s e t t i n g s . j s o n " ) & & ! d a t a N a m e s . i n c l u d e s ( a p p . i d + " . j s o n " ) )
2020-04-16 15:06:25 +00:00
WARN ( ` App ${ app . id } uses data file ${ app . id + '.settings.json' } instead of ${ app . id + '.json' } ` )
2021-11-20 16:52:44 +00:00
else if ( dataNames . includes ( app . id + ".settings.json" ) )
2021-12-09 17:16:55 +00:00
WARN ( ` App ${ app . id } uses data file ${ app . id + '.settings.json' } ` ) * /
2020-04-16 15:06:25 +00:00
// settings files should be listed under data, not storage (TODO: change to ERROR once all apps comply?)
if ( fileNames . includes ( app . id + ".settings.json" ) )
2022-06-28 10:51:25 +00:00
WARN ( ` App ${ app . id } uses storage file ${ app . id + '.settings.json' } instead of data file ` , { file : metadataFile } )
2020-04-16 15:06:25 +00:00
if ( fileNames . includes ( app . id + ".json" ) )
2022-06-28 10:51:25 +00:00
WARN ( ` App ${ app . id } uses storage file ${ app . id + '.json' } instead of data file ` , { file : metadataFile } )
2020-04-16 15:06:25 +00:00
// warn if storage file matches data file of same app
dataNames . forEach ( dataName => {
const glob = globToRegex ( dataName )
fileNames . forEach ( fileName => {
if ( glob . test ( fileName ) ) {
2022-06-28 10:51:25 +00:00
if ( isGlob ( dataName ) ) WARN ( ` App ${ app . id } storage file ${ fileName } matches data wildcard ${ dataName } ` , { file : metadataFile } )
else WARN ( ` App ${ app . id } storage file ${ fileName } is also listed in data ` , { file : metadataFile } )
2020-04-16 15:06:25 +00:00
}
} )
} )
2020-02-28 14:17:22 +00:00
//console.log(fileNames);
2022-06-28 10:51:25 +00:00
if ( isApp && ! fileNames . includes ( app . id + ".app.js" ) ) ERROR ( ` App ${ app . id } has no entrypoint ` , { file : metadataFile } ) ;
if ( isApp && ! fileNames . includes ( app . id + ".img" ) ) ERROR ( ` App ${ app . id } has no JS icon ` , { file : metadataFile } ) ;
if ( app . type == "widget" && ! fileNames . includes ( app . id + ".wid.js" ) ) ERROR ( ` Widget ${ app . id } has no entrypoint ` , { file : metadataFile } ) ;
2020-04-11 23:11:47 +00:00
for ( const key in app ) {
2022-06-28 10:51:25 +00:00
if ( ! APP _KEYS . includes ( key ) ) ERROR ( ` App ${ app . id } has unknown key ${ key } ` , { file : metadataFile } ) ;
2020-04-11 23:11:47 +00:00
}
2022-07-26 13:54:34 +00:00
if ( app . type && INTERNAL _FILES _IN _APP _TYPE [ app . type ] ) {
INTERNAL _FILES _IN _APP _TYPE [ app . type ] . forEach ( fileName => {
if ( ! fileNames . includes ( fileName ) )
ERROR ( ` App ${ app . id } should include file named ${ fileName } but it doesn't ` , { file : metadataFile } ) ;
} ) ;
}
2022-11-16 15:17:28 +00:00
if ( app . type == "module" && ! app . provides _modules ) {
ERROR ( ` App ${ app . id } has type:module but it doesn't have a provides_modules field ` , { file : metadataFile } ) ;
}
if ( app . provides _modules ) {
app . provides _modules . forEach ( modulename => {
if ( ! app . storage . find ( s => s . name == modulename ) )
ERROR ( ` App ${ app . id } has provides_modules ${ modulename } but it doesn't provide that filename ` , { file : metadataFile } ) ;
} ) ;
}
2020-02-28 14:17:22 +00:00
} ) ;
2022-06-28 10:51:25 +00:00
2020-04-16 15:06:25 +00:00
// Do not allow files from different apps to collide
let fileA
2022-06-28 10:51:25 +00:00
2020-04-16 15:06:25 +00:00
while ( fileA = allFiles . pop ( ) ) {
2020-05-04 08:32:50 +00:00
if ( VALID _DUPLICATES . includes ( fileA . file ) )
2022-06-28 10:51:25 +00:00
break ;
2020-04-16 15:06:25 +00:00
const nameA = ( fileA . file || fileA . data ) ,
globA = globToRegex ( nameA ) ,
typeA = fileA . file ? 'storage' : 'data'
allFiles . forEach ( fileB => {
const nameB = ( fileB . file || fileB . data ) ,
globB = globToRegex ( nameB ) ,
typeB = fileB . file ? 'storage' : 'data'
if ( globA . test ( nameB ) || globB . test ( nameA ) ) {
if ( isGlob ( nameA ) || isGlob ( nameB ) )
2022-06-28 10:51:25 +00:00
ERROR ( ` App ${ fileB . app } ${ typeB } file ${ nameB } matches app ${ fileA . app } ${ typeB } file ${ nameA } ` ) ;
2022-07-26 13:54:34 +00:00
else if ( fileA . app != fileB . app && ( ! fileA . internal ) && ( ! fileB . internal ) )
2022-06-28 10:51:25 +00:00
WARN ( ` App ${ fileB . app } ${ typeB } file ${ nameB } is also listed as ${ typeA } file for app ${ fileA . app } ` ) ;
2020-04-16 15:06:25 +00:00
}
} )
}
2022-06-28 10:51:25 +00:00
console . log ( "==================================" ) ;
console . log ( ` ${ errorCount } errors, ${ warningCount } warnings ` ) ;
console . log ( "==================================" ) ;
if ( errorCount ) {
process . exit ( 1 ) ;
}