diff --git a/build/build.properties b/build/build.properties index bad12bd71..c7b837428 100644 --- a/build/build.properties +++ b/build/build.properties @@ -11,12 +11,12 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib cfml.version=5.2.9.31 -cfml.loader.version=2.2.9 +cfml.loader.version=2.2.11 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.adoptVesionr=openjdk8 -jre.version=jdk8u192-b12 +jre.version=jdk8u202-b08 launch4j.version=3.12 runwar.version=3.8.1-SNAPSHOT jline.version=3.8.2 diff --git a/build/build.xml b/build/build.xml index 256e0d7c5..69dfcd086 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,8 +16,8 @@ External Dependencies: - - + + @@ -199,6 +199,14 @@ External Dependencies: + + + diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index 5eab2b4cb..8195a520a 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -37,6 +37,7 @@ component accessors="true" singleton { variables.configService = wirebox.getInstance( "ConfigService" ); variables.SystemSettings = wirebox.getInstance( "SystemSettings" ); variables.job = wirebox.getInstance( "interactiveJob" ); + variables.thisThread = createObject( 'java', 'java.lang.Thread' ).currentThread(); variables.exitCode = 0; return this; @@ -294,8 +295,8 @@ component accessors="true" singleton { * if the user has hit Ctrl-C. This method will throw an UserInterruptException * which you should not catch. It will unroll the stack all the way back to the shell */ - function checkInterrupted() { - shell.checkInterrupted(); + function checkInterrupted( thisThread=variables.thisThread ) { + shell.checkInterrupted( argumentCollection=arguments ); } diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index d705ccd0a..6aa7eb168 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -602,8 +602,10 @@ component accessors="true" singleton { * if the user has hit Ctrl-C. This method will throw an UserInterruptException * which you should not catch. It will unroll the stack all the way back to the shell */ - function checkInterrupted() { - var thisThread = createObject( 'java', 'java.lang.Thread' ).currentThread(); + function checkInterrupted( thisThread ) { + if( isNull( arguments.thisThread ) ) { + thisThread = createObject( 'java', 'java.lang.Thread' ).currentThread(); + } // Has the user tried to interrupt this thread? if( thisThread.isInterrupted() ) { diff --git a/src/cfml/system/endpoints/ForgeBox.cfc b/src/cfml/system/endpoints/ForgeBox.cfc index b6f2a043a..f89f1ce27 100644 --- a/src/cfml/system/endpoints/ForgeBox.cfc +++ b/src/cfml/system/endpoints/ForgeBox.cfc @@ -225,7 +225,7 @@ component accessors="true" implements="IEndpointInteractive" { try { forgebox.getStorageLocation( props.slug, props.version, props.APIToken ); if ( ! arguments.force ) { - consoleLogger.warn( "A zip for this version has already been uploaded. If you want to override the uploaded zip, run this command with the `force` flag. We will continue to update your package metadata." ); + consoleLogger.error( "A zip for this version has already been uploaded. If you want to override the uploaded zip, run this command with the `force` flag. We will continue to update your package metadata." ); upload = false; } } diff --git a/src/cfml/system/modules/propertyFile/ModuleConfig.cfc b/src/cfml/system/modules/propertyFile/ModuleConfig.cfc index 6f40fe6f6..f95e7055a 100644 --- a/src/cfml/system/modules/propertyFile/ModuleConfig.cfc +++ b/src/cfml/system/modules/propertyFile/ModuleConfig.cfc @@ -5,4 +5,4 @@ component { function configure(){ } -} +} \ No newline at end of file diff --git a/src/cfml/system/modules/propertyFile/box.json b/src/cfml/system/modules/propertyFile/box.json index f75167acf..7c2184fa7 100644 --- a/src/cfml/system/modules/propertyFile/box.json +++ b/src/cfml/system/modules/propertyFile/box.json @@ -1,8 +1,8 @@ { "name":"PropertyFile Util", - "version":"1.1.0", + "version":"1.2.0", "author":"Brad Wood", - "location":"bdw429s/PropertyFile#v1.1.0", + "location":"bdw429s/PropertyFile#v1.2.0", "homepage":"https://github.com/bdw429s/PropertyFile/", "documentation":"https://github.com/bdw429s/PropertyFile/blob/master/readme.md", "repository":{ diff --git a/src/cfml/system/modules/propertyFile/models/PropertyFile.cfc b/src/cfml/system/modules/propertyFile/models/PropertyFile.cfc index ab0d44c5a..9e7f17e5d 100644 --- a/src/cfml/system/modules/propertyFile/models/PropertyFile.cfc +++ b/src/cfml/system/modules/propertyFile/models/PropertyFile.cfc @@ -2,12 +2,13 @@ * I am a new Model Object */ component accessors="true"{ - + // Properties property name='javaPropertyFile'; + // A fully qualified path to a property file property name='path'; property name='syncedNames'; - + /** * Constructor @@ -17,46 +18,46 @@ component accessors="true"{ setJavaPropertyFile( createObject( 'java', 'java.util.Properties' ).init() ); return this; } - + /** - * load + * @load A fully qualified path to a property file */ function load( required string path){ setPath( arguments.path ); - var fis = CreateObject( 'java', 'java.io.FileInputStream' ).init( expandPath( path ) ); + var fis = CreateObject( 'java', 'java.io.FileInputStream' ).init( path ); var BOMfis = CreateObject( 'java', 'org.apache.commons.io.input.BOMInputStream' ).init( fis ); var propertyFile = getJavaPropertyFile(); propertyFile.load( BOMfis ); BOMfis.close(); - - + + var props = propertyFile.propertyNames(); var syncedNames = getSyncedNames(); while( props.hasMoreElements() ) { var prop = props.nextElement(); this[ prop ] = get( prop ); - syncedNames.append( prop ); + syncedNames.append( prop ); } setSyncedNames( syncedNames ); - + return this; } /** - * store + * @load A fully qualified path to a property file. File will be created if it doesn't exist. */ function store( string path=variables.path ){ syncProperties(); - + if( !fileExists( arguments.path ) ) { directoryCreate( getDirectoryFromPath( arguments.path ), true, true ); fileWrite( arguments.path, '' ); } - - var fos = CreateObject( 'java', 'java.io.FileOutputStream' ).init( expandPath( arguments.path ) ); + + var fos = CreateObject( 'java', 'java.io.FileOutputStream' ).init( arguments.path ); getJavaPropertyFile().store( fos, '' ); fos.close(); - + return this; } @@ -65,7 +66,7 @@ component accessors="true"{ */ function get( required string name, string defaultValue ){ if( structKeyExists( arguments, 'defaultValue' ) ) { - return getJavaPropertyFile().getProperty( name, defaultValue ); + return getJavaPropertyFile().getProperty( name, defaultValue ); } else if( exists( name ) ) { return getJavaPropertyFile().getProperty( name ); } else { @@ -78,14 +79,14 @@ component accessors="true"{ */ function set( required string name, required string value ){ getJavaPropertyFile().setProperty( name, value ); - + var syncedNames = getSyncedNames(); this[ name ] = value; if( !arrayContains( syncedNames, name ) ){ syncedNames.append( name ); } setSyncedNames( syncedNames ); - + return this; } @@ -95,7 +96,7 @@ component accessors="true"{ function remove( required string name ){ if( exists( name ) ) { getJavaPropertyFile().remove( name ); - + var syncedNames = getSyncedNames(); if( arrayFind( syncedNames, name ) ){ syncedNames.deleteAt( arrayFind( syncedNames, name ) ); @@ -122,7 +123,7 @@ component accessors="true"{ structAppend( result, getJavaPropertyFile() ); return result; } - + /** * Keeps public properties in sync with Java object */ @@ -130,7 +131,7 @@ component accessors="true"{ var syncedNames = getSyncedNames(); var ignore = listToArray( 'init,load,store,get,set,exists,remove,exists,getAsStruct,$mixed' ); var propertyFile = getJavaPropertyFile(); - + // This CFC's public properties for( var prop in this ) { // Set any new/updated properties in, excluding actual methods and non-simple values @@ -138,7 +139,7 @@ component accessors="true"{ set( prop, this[ prop ] ); } } - + // All the properties in the Java object var props = propertyFile.propertyNames(); while( props.hasMoreElements() ) { @@ -148,7 +149,7 @@ component accessors="true"{ remove( prop ); } } - + } -} +} \ No newline at end of file diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc index 4fe59651c..2a95978a8 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc @@ -83,12 +83,18 @@ component aliases='coldbox create controller' { // Are we creating views? if( arguments.views ) { - var viewPath = arguments.viewsDirectory & '/' & arguments.name & '/' & thisAction & '.cfm'; + + var camelCaseHandlerName = arguments.name.left( 1 ).lCase(); + if( arguments.name.len() > 1 ) { + camelCaseHandlerName &= arguments.name.right( -1 ); + } + + var viewPath = resolvePath( arguments.viewsDirectory & '/' & camelCaseHandlerName & '/' & thisAction & '.cfm' ); // Create dir if it doesn't exist directorycreate( getDirectoryFromPath( viewPath ), true, true ); // Create View Stub fileWrite( viewPath, '#cr#

#arguments.name#.#thisAction#

#cr#
' ); - print.greenLine( 'Created ' & arguments.viewsDirectory & '/' & arguments.name & '/' & thisAction & '.cfm' ); + print.greenLine( 'Created ' & viewPath ); } // Are we creating tests cases on actions @@ -109,7 +115,7 @@ component aliases='coldbox create controller' { handlerTestContent = replaceNoCase( handlerTestContent, '|TestCases|', '', 'all' ); } - var handlerPath = '#arguments.directory#/#arguments.name#.cfc'; + var handlerPath = resolvePath( '#arguments.directory#/#arguments.name#.cfc' ); // Create dir if it doesn't exist directorycreate( getDirectoryFromPath( handlerPath ), true, true ); @@ -124,7 +130,7 @@ component aliases='coldbox create controller' { print.greenLine( 'Created #handlerPath#' ); if( arguments.integrationTests ) { - var testPath = '#arguments.testsDirectory#/#arguments.name#Test.cfc'; + var testPath = resolvePath( '#arguments.testsDirectory#/#arguments.name#Test.cfc' ); // Create dir if it doesn't exist directorycreate( getDirectoryFromPath( testPath ), true, true ); // Create the tests diff --git a/src/cfml/system/modules_app/package-commands/commands/package/init.cfc b/src/cfml/system/modules_app/package-commands/commands/package/init.cfc index 8ae96b2fe..cc4a95167 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/init.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/init.cfc @@ -110,7 +110,7 @@ component aliases="init" { // Ignore List if( arguments.ignoreList ){ - arguments[ "ignore" ] = serializeJSON( [ '**/.*', 'test', 'tests' ] ); + arguments[ "ignore" ] = serializeJSON( [ '**/.*', '/test/', '/tests/' ] ); } // Cleanup the argument so it does not get written. structDelete( arguments, "ignoreList" ); diff --git a/src/cfml/system/modules_app/system-commands/commands/repl.cfc b/src/cfml/system/modules_app/system-commands/commands/repl.cfc index 46834a4e1..7416a8bc9 100644 --- a/src/cfml/system/modules_app/system-commands/commands/repl.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/repl.cfc @@ -80,6 +80,9 @@ component { break; } } + + // Evaluate any ${} placeholders + command = systemSettings.expandSystemSettings( command ) // add command to our parser REPLParser.addCommandLine( command ); diff --git a/src/cfml/system/modules_app/system-commands/commands/run.cfc b/src/cfml/system/modules_app/system-commands/commands/run.cfc index b600eee55..b3406b712 100644 --- a/src/cfml/system/modules_app/system-commands/commands/run.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/run.cfc @@ -62,6 +62,10 @@ component{ commandArray = [ nativeShell, '-i', '-c', arguments.command & ' 2>&1 && ( exit $? > /dev/null )' ]; } + if( configService.getSetting( 'debugNativeExecution', false ) ) { + print.line( commandArray.tolist( ' ' ) ).toConsole(); + } + var exitCode = 1; // grab the current working directory var CWDFile = createObject( 'java', 'java.io.File' ).init( resolvePath( '' ) ); @@ -111,9 +115,13 @@ component{ // I convert the byte array in the piped input stream to a character array var inputStreamReader = createObject( 'java', 'java.io.InputStreamReader' ).init( inputStream ); + var interruptCount = 0; // This will block/loop until the input stream closes, which means this loops until the process ends. while( ( var char = inputStreamReader.read() ) != -1 ) { - checkInterrupted(); + if( ++interruptCount > 1000 ) { + checkInterrupted(); + interruptCount=0; + } // if running non-interactive, gather the output of the command processOutputStringBuilder.append( javaCast( 'char', char ) ); } diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc index 936355545..a2c29c379 100644 --- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc +++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc @@ -77,7 +77,7 @@ component singleton accessors=true { try { // Run the task - taskCFC[ target ]( argumentCollection = taskArgs ); + local.returnedExitCode = taskCFC[ target ]( argumentCollection = taskArgs ); } catch( any e ) { // If this task didn't already set a failing exit code... @@ -100,7 +100,11 @@ component singleton accessors=true { } finally { // Set task exit code into the shell - shell.setExitCode( taskCFC.getExitCode() ); + if( !isNull( local.returnedExitCode ) && isSimpleValue( local.returnedExitCode ) ) { + shell.setExitCode( val( local.returnedExitCode ) ); + } else { + shell.setExitCode( taskCFC.getExitCode() ); + } } // If the previous Task failed diff --git a/src/cfml/system/services/ArtifactService.cfc b/src/cfml/system/services/ArtifactService.cfc index 7c7733f05..4ce5c2f9c 100644 --- a/src/cfml/system/services/ArtifactService.cfc +++ b/src/cfml/system/services/ArtifactService.cfc @@ -21,7 +21,6 @@ component accessors="true" singleton { property name='packageService' inject='PackageService'; property name='logger' inject='logbox:logger:{this}'; property name="semanticVersion" inject="provider:semanticVersion@semver"; - // COMMANDBOX-479 property name="configService" inject="ConfigService"; @@ -31,7 +30,6 @@ component accessors="true" singleton { function onDIComplete() { // Create the artifacts directory if it doesn't exist - // COMMANDBOX-479 if( !directoryExists( getArtifactsDirectory() ) ) { directoryCreate( getArtifactsDirectory() ); } @@ -44,8 +42,9 @@ component accessors="true" singleton { * @returns A struct of arrays where the struct key is the package package and the array contains the versions of that package in the cache. */ struct function listArtifacts( packageName='' ) { - var result = {}; - // COMMANDBOX-479 + // Ordered struct + var result = [:]; + var dirList = directoryList( path=getArtifactsDirectory(), recurse=false, listInfo='query', sort='name asc' ); for( var dir in dirList ) { @@ -71,7 +70,7 @@ component accessors="true" singleton { * Removes all artifacts from the cache and returns the number of wiped out directories */ numeric function cleanArtifacts() { - // COMMANDBOX-479 + var qryDir = directoryList( path=getArtifactsDirectory(), recurse=false, listInfo='query' ); var numRemoved = 0; @@ -115,7 +114,7 @@ component accessors="true" singleton { */ function getPackagePath( required packageName, version="" ){ // This will likely change, so I'm only going to put the code here. - // COMMANDBOX-479 + var path = getArtifactsDirectory() & '/' & arguments.packageName; // do we have a version? if( arguments.version.len() ){ @@ -267,7 +266,7 @@ component accessors="true" singleton { } } - // COMMANDBOX-479 + string function getArtifactsDirectory() { return configService.getSetting( 'artifactsDirectory', variables.artifactDir ); } diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index 09c5eeb6f..9b170b80c 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -914,6 +914,12 @@ component accessors="true" singleton { excludeFromHelp = commandMD.excludeFromHelp ?: false, commandMD = commandMD }; + + // Fix for CFCs with no hint, they inherit this from the Lucee base compnent. + if( commandData.hint == 'This is the Base Component' ) { + commandData.hint = ''; + } + // check functions if( structKeyExists( commandMD, 'functions' ) ){ // Capture the command's parameters diff --git a/src/cfml/system/services/ConfigService.cfc b/src/cfml/system/services/ConfigService.cfc index 2525ae456..92a1e3d4e 100644 --- a/src/cfml/system/services/ConfigService.cfc +++ b/src/cfml/system/services/ConfigService.cfc @@ -71,7 +71,8 @@ component accessors="true" singleton { 'JSON.ANSIColors.number', 'JSON.ANSIColors.string', // General - 'verboseErrors' + 'verboseErrors', + 'debugNativeExecution' ]); setConfigFilePath( '/commandbox-home/CommandBox.json' ); diff --git a/src/cfml/system/services/EndpointService.cfc b/src/cfml/system/services/EndpointService.cfc index ea8902df6..c4b0a07c1 100644 --- a/src/cfml/system/services/EndpointService.cfc +++ b/src/cfml/system/services/EndpointService.cfc @@ -52,7 +52,14 @@ component accessors="true" singleton { var endpointPath = listChangeDelims( arguments.rootDirectory, '/\', '.' ) & '.' & endpointName; var oEndPoint = wirebox.getInstance( endpointPath ); - registerEndpoint( oEndPoint ); + if( endPointName == 'forgebox' ) { + var customForgeBoxAPIURL = configService.getSetting( 'endpoints.forgebox.apiURL', '' ); + if( customForgeBoxAPIURL.len() ) { + oEndPoint.getForgeBox().setEndpointURL( customForgeBoxAPIURL.reReplaceNoCase( '/api/.*', '' ) ); + oEndPoint.getForgeBox().setAPIURL( customForgeBoxAPIURL ); + } + } + registerEndpoint( oEndPoint ); } } @@ -81,7 +88,7 @@ component accessors="true" singleton { oEndPoint.setNamePrefixes( endpointName.replaceNoCase( 'forgebox-', '' ) ); // Set the API URL for this endpoint's forgebox Util - oEndPoint.getForgeBox().setEndpointURL( endpointData.APIURL ); + oEndPoint.getForgeBox().setEndpointURL( endpointData.APIURL.reReplaceNoCase( '/api/.*', '' ) ); oEndPoint.getForgeBox().setAPIURL( endpointData.APIURL ); // Register it, baby! diff --git a/src/cfml/system/services/ModuleService.cfc b/src/cfml/system/services/ModuleService.cfc index a8b18db66..90ca1971e 100644 --- a/src/cfml/system/services/ModuleService.cfc +++ b/src/cfml/system/services/ModuleService.cfc @@ -165,7 +165,7 @@ if( len( arguments.invocationPath ) ){ // Check if passed module name is already registered if( structKeyExists( instance.moduleRegistry, arguments.moduleName ) AND !arguments.force ){ - instance.logger.warn( "The module #arguments.moduleName# has already been registered, so skipping registration" ); + instance.logger.debug( "The module #arguments.moduleName# has already been registered, so skipping registration" ); return false; } // register new incoming location @@ -468,8 +468,13 @@ interceptorProperties=mConfig.interceptors[ y ].properties, interceptorName=mConfig.interceptors[ y ].name); // Loop over module interceptors to autowire them - wirebox.autowire( target=interceptorService.getInterceptor( mConfig.interceptors[ y ].name, true ), - targetID=mConfig.interceptors[ y ].class ); + try { + wirebox.autowire( target=interceptorService.getInterceptor( mConfig.interceptors[ y ].name, true ), + targetID=mConfig.interceptors[ y ].class ); + } catch( EventPoolManager.ObjectNotFound var e ){ + // This error simply means our interceptor had no states + // And an interceptor with no states basically ceases to exist as it has no purpose in life + } } // Register module routing entry point pre-pended to routes diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index ea99abd0f..7f9c61f19 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -855,6 +855,18 @@ component accessors="true" singleton { var displayEngineName = 'WAR'; } + // Doing this check here instead of the ServerEngineService so it can apply to existing installs + if( CFEngineName == 'adobe' ) { + // Work arounnd sketchy resoution of non-existant paths in Undertow + // https://issues.jboss.org/browse/UNDERTOW-1413 + var flexLogFile = serverInfo.serverHomeDirectory & "/WEB-INF/cfform/logs/flex.log"; + if ( !fileExists( flexLogFile ) ) { + // if this doesn't already exist, it ends up getting created in a WEB-INF folder in the web root. Eww.... + directoryCreate( getDirectoryFromPath( flexLogFile ), true, true ); + fileWrite( flexLogFile, '' ); + } + } + // logdir is set above and is different for WARs and CF engines serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; serverInfo.accessLogPath = serverInfo.logDir & '/access.txt'; @@ -1326,13 +1338,19 @@ component accessors="true" singleton { variables.waitingOnConsoleStart = true; while( true ) { - // Detect user pressing Ctrl-C - // Any other characters captured will be ignored - var line = shell.getReader().readLine(); - if( line == 'q' ) { - break; + // For dumb terminals, just sit and wait to be interrupted + // Trying to read from a dumb terminal will throw "The handle is invalid" errors + if( shell.getReader().getTerminal().getClass().getName() contains 'dumb' ) { + sleep( 500 ); } else { - consoleLogger.error( 'To exit press Ctrl-C or "q" followed the enter key.' ); + // Detect user pressing Ctrl-C + // Any other characters captured will be ignored + var line = shell.getReader().readLine(); + if( line == 'q' ) { + break; + } else { + consoleLogger.error( 'To exit press Ctrl-C or "q" followed the enter key.' ); + } } } @@ -1642,7 +1660,7 @@ component accessors="true" singleton { throw( "The host name [#arguments.host#] can't be found. Do you need to add a host file entry?", 'serverException', e.message & ' ' & e.detail ); } catch( java.net.BindException var e ) { // Same as above-- the IP address/host isn't bound to any local adapters. Probably a host file entry went missing. - throw( "The IP address that [#arguments.host#] resovles to can't be bound. If you ping it, does it point to a local network adapter?", 'serverException', e.message & ' ' & e.detail ); + throw( "The IP address that [#arguments.host#] resolves to can't be bound. If you ping it, does it point to a local network adapter?", 'serverException', e.message & ' ' & e.detail ); } return portNumber; @@ -1871,7 +1889,7 @@ component accessors="true" singleton { arguments.webroot = fileSystemUtil.resolvePath( arguments.webroot ); var servers = getServers(); for( var thisServer in servers ){ - if( fileSystemUtil.resolvePath( servers[ thisServer ].webroot ) == arguments.webroot ){ + if( fileSystemUtil.resolvePath( path=servers[ thisServer ].webroot, forceDirectory=true ) == arguments.webroot ){ return servers[ thisServer ]; } } diff --git a/src/cfml/system/util/FileSystem.cfc b/src/cfml/system/util/FileSystem.cfc index 61c4ec33a..521d8ab3c 100644 --- a/src/cfml/system/util/FileSystem.cfc +++ b/src/cfml/system/util/FileSystem.cfc @@ -45,8 +45,9 @@ component accessors="true" singleton { * Resolve the incoming path from the file system * @path.hint The directory to resolve * @basePath.hint An expanded base path to resolve the path against. Defaults to CWD. + * @forceDirectory is for optimization. If you know the path is a directory for sure, pass true and we'll skip the directoryExists() check for performance */ - function resolvePath( required string path, basePath=shell.pwd() ) { + function resolvePath( required string path, basePath=shell.pwd(), boolean forceDirectory=false ) { // The Java class will strip trailing slashses, but these are meaningful in globbing patterns var trailingSlash = ( path.len() > 1 && ( path.endsWith( '/' ) || path.endsWith( '\' ) ) ); @@ -83,10 +84,14 @@ component accessors="true" singleton { } // Add back trailing slash if we had it - var finalPath = oPath.toString() & ( trailingSlash ? server.separator.file : '' ); + var finalPath = oPath.toString() & ( ( trailingSlash || forceDirectory ) ? server.separator.file : '' ); // This will standardize the name and calculate stuff like ../../ - finalPath = getCanonicalPath( finalPath ) + if( forceDirectory ) { + finalPath = calculateCanonicalPath( finalPath ); + } else { + finalPath = getCanonicalPath( finalPath ); + } // have to add back the period after canonicalizing since Java removes it! return finalPath & ( trailingPeriod && !finalPath.endsWith( '.' ) ? '.' : '' ); @@ -212,15 +217,21 @@ component accessors="true" singleton { var runtime = createObject( "java", "java.lang.Runtime" ).getRuntime(); if( isWindows() and target.isFile() ){ runtime.exec( [ "rundll32", "url.dll,FileProtocolHandler", target.getCanonicalPath() ] ); - } - else if( isWindows() and target.isDirectory() ){ + } else if( isWindows() and target.isDirectory() ){ var processBuilder = createObject( "java", "java.lang.ProcessBuilder" ) .init( [ "explorer.exe", target.getCanonicalPath() ] ) .start(); - } - // Linux based or mac - else { + } else if( isMac() ) { + // Mac runtime.exec( [ "/usr/bin/open", target.getCanonicalPath() ] ); + } else { + // Default to Linux + // If there is xdg-open around, we'll try to use it - otherwise we'll use see. + if( runtime.exec( "which xdg-open" ).waitFor() == 0 ) { + runtime.exec( [ "/usr/bin/xdg-open", target.getCanonicalPath() ] ); + } else { + runtime.exec( [ "/usr/bin/see", target.getCanonicalPath() ] ); + } } return true; } @@ -235,12 +246,18 @@ component accessors="true" singleton { if( !findNoCase( "http", arguments.URI ) ){ arguments.URI = "http://#arguments.uri#"; } - - // open using awt class, if it fails, we are in headless mode. - if( desktop.isDesktopSupported() ){ - desktop.getDesktop().browse( createObject( "java", "java.net.URI" ).init( arguments.URI ) ); - return true; - } + + // Some openJDK distros error out above if installed headlessly. + try { + // open using awt class, if it fails, we are in headless mode. + if( desktop.isDesktopSupported() ){ + desktop.getDesktop().browse( createObject( "java", "java.net.URI" ).init( arguments.URI ) ); + return true; + } + } catch( any var e ) { + // Bird strike! Log it. + logger.error( '#e.message# #e.detail#' ); + } // if we get here, then we don't support desktop awt class, most likely in headless mode. var runtime = createObject( "java", "java.lang.Runtime" ).getRuntime(); diff --git a/src/cfml/system/util/ForgeBox.cfc b/src/cfml/system/util/ForgeBox.cfc index d53247871..bc35e3447 100644 --- a/src/cfml/system/util/ForgeBox.cfc +++ b/src/cfml/system/util/ForgeBox.cfc @@ -410,7 +410,7 @@ or just add DEBUG to the root logger // If there's only one suggestion and it doesn't have an @ in it, add another suggestion with the @ at the end. // This is to prevent the tab completion from adding a space after the suggestion since it thinks it's the only possible option // Hitting tab will still populate the line, but won't add the space which makes it easier if the user intends to continue for a specific version. - if( opts.len() == 1 && !( opts[1] contains '@' ) ) { + if( opts.len() == 1 && ( !( opts[1] contains '@' ) || opts[1].listRest( '@' ).reFindNoCase( '^[a-z]' ) ) ) { opts.append( opts[1] & '@' ); } @@ -419,7 +419,7 @@ or just add DEBUG to the root logger function getStorageLocation( required string slug, required string version, required string APIToken ) { var results = makeRequest( - resource = "storage/#slug#/#version#", + resource = "storage/#urlEncode( slug )#/#urlEncode( version )#", method = "get", headers = { 'x-api-token' : arguments.APIToken @@ -551,7 +551,7 @@ or just add DEBUG to the root logger if( errorDetail != statusMessage ) { errorDetail &= chr( 10 ) & statusMessage; } - errorDetail = ucase( arguments.method ) & ' ' &thisURL & chr( 10 ) & errorDetail; + errorDetail = ucase( arguments.method ) & ' ' & thisURL & ' ' & errorDetail; CommandBoxlogger.error( 'Something other than JSON returned. #errorDetail#', 'Actual HTTP Response: ' & results.rawResponse ); throw( 'Uh-oh, ForgeBox returned something other than JSON. Run "system-log | open" to see the full response.', 'forgebox', errorDetail ); } diff --git a/src/cfml/system/util/ProgressableDownloader.cfc b/src/cfml/system/util/ProgressableDownloader.cfc index 627d3a5b0..8fbee801a 100644 --- a/src/cfml/system/util/ProgressableDownloader.cfc +++ b/src/cfml/system/util/ProgressableDownloader.cfc @@ -36,6 +36,14 @@ component singleton { var info = resolveConnection( arguments.downloadURL, arguments.redirectUDF ); var connection = info.connection; var netURL = info.netURL; + + // Initialize status + var status = { + percent = 0, + speedKBps = 0, + totalSizeKB = -1, + completeSizeKB = 0 + }; try { @@ -84,7 +92,7 @@ component singleton { } // Build status data to pass to closure - var status = { + status = { percent = currentPercentage, speedKBps = kiloBytesPerSecond, totalSizeKB = ( lenghtOfFile == -1 ? -1 : lenghtOfFile/1000 ), diff --git a/src/cfml/system/util/TaskDSL.cfc b/src/cfml/system/util/TaskDSL.cfc index ea7b7c6b3..897e9273e 100644 --- a/src/cfml/system/util/TaskDSL.cfc +++ b/src/cfml/system/util/TaskDSL.cfc @@ -20,6 +20,7 @@ component accessors=true { property name='flags'; property name='workingDirectory'; property name='rawParams'; + property name='exitCode'; // DI @@ -45,6 +46,7 @@ component accessors=true { setFlags( [] ); setWorkingDirectory( '' ); setRawParams( false ); + setExitCode( 0 ); return this; } @@ -161,6 +163,8 @@ component accessors=true { var result = shell.callCommand( getTokens(), true ); } finally { + setExitCode( shell.getExitCode() ); + var postCommandCWD = shell.getPWD(); // Only change back if the executed command didn't change the CWD diff --git a/src/cfml/system/util/jline/CommandHighlighter.cfc b/src/cfml/system/util/jline/CommandHighlighter.cfc index 07f1c05d5..cd8a5cd2d 100644 --- a/src/cfml/system/util/jline/CommandHighlighter.cfc +++ b/src/cfml/system/util/jline/CommandHighlighter.cfc @@ -26,8 +26,12 @@ component { function highlight( reader, buffer ) { - // Call CommandBox parser to parse the line. - var commandChain = CommandService.resolveCommand( buffer ); + try { + // Call CommandBox parser to parse the line. + var commandChain = CommandService.resolveCommand( buffer ); + } catch( any var e ) { + return createObject("java","org.jline.utils.AttributedString").fromAnsi( buffer ); + } // For each command in the chain for( var command in commandChain ) { diff --git a/src/cfml/system/util/jline/REPLHighlighter.cfc b/src/cfml/system/util/jline/REPLHighlighter.cfc index 2323a8d67..d4334e956 100644 --- a/src/cfml/system/util/jline/REPLHighlighter.cfc +++ b/src/cfml/system/util/jline/REPLHighlighter.cfc @@ -13,22 +13,6 @@ component { property name='shell' inject='provider:shell'; function init() { - variables.functionList = getFunctionList() - .keyArray() - // Add in member function versions of functions - .reduce( function( orig, i ) { - orig.append( i ); - if( reFind( 'array|struct|query|image|spreadsheet|XML', i ) ) { - orig.append( i.reReplaceNoCase( '(array|struct|query|image|spreadsheet|XML)(.+)', '\2' ) ); - } - return orig; - }, [] ) - // Sort function names longest to shortest - .sort( function(a,b){ - if( a.len() > b.len() ) return -1; - if( a.len() < b.len() ) return 1; - return 0; - } ); variables.reservedWords = [ 'if', @@ -49,8 +33,10 @@ component { 'false', 'return', 'in', - 'function' - ]; + 'function', + 'any' + ].toList( '|' ); + variables.sets = { ')' : '(', '}' : '{', @@ -64,17 +50,12 @@ component { function highlight( reader, buffer ) { // Highlight CF function names - for( var func in functionList ) { - // Find function names that are at the line start or prepended with a space, curly, or period and ending with an opening paren - buffer = reReplaceNoCase( buffer, '(^|[ \.\{\}])(#func#)(\()', '\1' & print.boldCyan( '\2' ) & '\3', 'all' ); - } + // Find text that is at the line start or prepended with a space, curly, or period and ending with an opening paren + buffer = reReplaceNoCase( buffer, '(^|[ \.\{\}\(\)])([^ \.\{\}\(\)]*)(\()', '\1' & print.boldCyan( '\2' ) & '\3', 'all' ); // highight reserved words - for( var reservedWord in reservedWords ) { - // Find keywords, bookended by a space, curly, paren, or semicolon. Or, of course, the start/end of the line - buffer = reReplaceNoCase( buffer, '(^|[ \{\}\(])(#reservedWord#)($|[ ;\(\)\{\}])', '\1' & print.boldCyan( '\2' ) & '\3', 'all' ); - } - + buffer = reReplaceNoCase( buffer, '(^|[ \{\}\(])(#reservedWords#)($|[ ;\(\)\{\}])', '\1' & print.boldCyan( '\2' ) & '\3', 'all' ); + // If the last character was an ending } or ) or ] or " or ' then highlight it and the matching start character // This logic is pretty basic and doesn't account for escaped stuff. If you want, please send a pull to improve it :) if( sets.keyExists( buffer.right( 1 ) ) ) { diff --git a/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc b/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc index 234570e64..88a144477 100644 --- a/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc +++ b/src/cfml/system/wirebox/system/logging/appenders/FileAppender.cfc @@ -119,8 +119,6 @@ component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ var message = loge.getMessage(); var entry = ""; - // Ensure Log File - initLogLocation(); // Message Layout if( hasCustomLayout() ){ @@ -238,6 +236,10 @@ component accessors="true" extends="wirebox.system.logging.AbstractAppender"{ var flushInterval = 1000; // 1 second var sleepInterval = 50; var count = 0; + + // Ensure Log File + initLogLocation(); + var oFile = fileOpen( variables.logFullPath, "append", this.getProperty( "fileEncoding" ) ); var hasMessages = false;