From adae4355c57f9aca51cfc75c38a256be49d130fb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 6 Dec 2022 22:26:14 -0600 Subject: [PATCH 01/48] 5.8 bump --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index d39038e5..21960c80 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,7 @@ External Dependencies: - + From 487673d55ef40d0177dc752635530193e2d7066c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 7 Dec 2022 14:53:00 -0600 Subject: [PATCH 02/48] COMMANDBOX-1345 --- build/build.properties | 2 +- src/cfml/system/services/ServerService.cfc | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/build/build.properties b/build/build.properties index ec424298..8c906f1a 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.17+8 launch4j.version=3.14 -runwar.version=4.7.16 +runwar.version=4.7.17-SNAPSHOT jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 4751bdcb..c8568b93 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -224,7 +224,8 @@ component accessors="true" singleton { 'subjectDNs' : d.web.security.clientCert.subjectDNs ?: '', 'issuerDNs' : d.web.security.clientCert.issuerDNs ?: '' } - } + }, + 'mimeTypes' : duplicate( d.web.mimeTypes ?: {} ) }, 'app' : { 'logDir' : d.app.logDir ?: '', @@ -961,6 +962,9 @@ component accessors="true" singleton { return fileSystemUtil.resolvePath( p, defaultServerConfigFileDirectory ); } ) ); + // Combine global and server-specific mime types + serverInfo.mimeTypes = defaults.web.mimeTypes.append( serverJSON.web.mimeTypes ?: {}, true ); + // Global errorPages are always added on top of server.json (but don't overwrite the full struct) // Aliases aren't accepted via command params serverInfo.errorPages = defaults.web.errorPages; @@ -1447,6 +1451,13 @@ component accessors="true" singleton { CLIAliases = CLIAliases.listAppend( thisAlias & '=' & serverInfo.aliases[ thisAlias ] ); } + // Turn struct of mimeTypes into a comma-delimited list + // "log;text/plain,foo;content/type" + var mimeTypesList = ''; + for( var thisMime in serverInfo.mimeTypes ) { + mimeTypesList = mimeTypesList.listAppend( thisMime & ';' & serverInfo.mimeTypes[ thisMime ] ); + } + // Turn struct of errorPages into a comma-delimited list. // --error-pages="404=/path/to/404.html,500=/path/to/500.html,1=/path/to/default.html" var errorPages = ''; @@ -1610,6 +1621,9 @@ component accessors="true" singleton { if( len( CLIAliases ) ) { args.append( '--dirs' ).append( CLIAliases ); } + if( len( mimeTypesList ) ) { + args.append( '--mime-types' ).append( mimeTypesList ); + } if( serverInfo.fileCacheEnable ) { args.append( '--cache-servlet-paths' ).append( true ); args.append( '--file-cache-total-size-mb' ).append( val( serverInfo.fileCacheTotalSizeMB ) ); @@ -2958,7 +2972,8 @@ component accessors="true" singleton { 'HSTSEnable' : false, 'HSTSMaxAge' : 0, 'HSTSIncludeSubDomains' : false, - 'AJPSecret' : '' + 'AJPSecret' : '', + 'mimeTypes' : {} }; } From 69b99bb3903004acc57dca7ba1b0bdbb1af72662 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 7 Dec 2022 17:29:41 -0600 Subject: [PATCH 03/48] COMMANDBOX-1537 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 8c906f1a..6aa266b7 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.17+8 launch4j.version=3.14 -runwar.version=4.7.17-SNAPSHOT +runwar.version=4.7.18-SNAPSHOT jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r From 6fb6800709d6c13c1db8f96249ea1227dacb22fa Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 13 Dec 2022 12:58:07 -0600 Subject: [PATCH 04/48] Add thin and light checksums --- build/build.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/build.xml b/build/build.xml index 21960c80..f751e5cd 100644 --- a/build/build.xml +++ b/build/build.xml @@ -703,6 +703,14 @@ External Dependencies: + + + + + + + + From 242bdfab9e7719f08c111c1bf6abdd6d9f1a4c49 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 15 Dec 2022 13:21:00 -0600 Subject: [PATCH 05/48] COMMANDBOX-1538 --- .../interceptors/ServerSystemSettingExpansions.cfc | 6 +++++- .../modules_app/system-commands/commands/sql.cfc | 14 ++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/modules_app/server-commands/interceptors/ServerSystemSettingExpansions.cfc b/src/cfml/system/modules_app/server-commands/interceptors/ServerSystemSettingExpansions.cfc index 3511b919..3a669ecb 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/ServerSystemSettingExpansions.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/ServerSystemSettingExpansions.cfc @@ -45,11 +45,15 @@ component { } else if( interceptData.setting.lcase().startsWith( 'serverinfo.' ) ) { var settingName = interceptData.setting.replaceNoCase( 'serverinfo.', '', 'one' ); + var interceptData_serverInfo_name = systemSettings.getSystemSetting( 'interceptData.SERVERINFO.name', '' ); // Lookup by name if( listLen( settingName, '@' ) > 1 ) { var serverInfo = serverService.getServerInfoByName( listLast( settingName, '@' ) ); settingName = listFirst( settingName, '@' ); + // If we're running inside of a server-related package script, use that server + } else if( interceptData_serverInfo_name != '' ) { + var serverInfo = serverService.resolveServerDetails( { name=interceptData_serverInfo_name } ).serverInfo; // Lookup by current working directory } else { var serverInfo = serverService.getServerInfoByWebroot( shell.pwd() ); @@ -59,7 +63,7 @@ component { if( !serverInfo.count() && fileExists( serverJSONPath ) ) { var serverInfo = serverService.getServerInfoByServerConfigFile( serverJSONPath ); } - + interceptData.setting = JSONService.show( serverInfo, settingName, interceptData.defaultValue ); if( !isSimpleValue( interceptData.setting ) ) { diff --git a/src/cfml/system/modules_app/system-commands/commands/sql.cfc b/src/cfml/system/modules_app/system-commands/commands/sql.cfc index f8a9b555..8b3776b7 100644 --- a/src/cfml/system/modules_app/system-commands/commands/sql.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/sql.cfc @@ -1,3 +1,4 @@ + /** * SQL query command for filtering table like data. This command will automatically * format any table like data into a query object that can be queried against @@ -73,11 +74,16 @@ component { string headerNames='' ) { - // Treat input as a potential file path + // Strip any incoming ANSI formatting arguments.data = print.unAnsi( arguments.data ); - //deserialize data if in a JSON format - if(isJSON(arguments.data)) arguments.data = deserializeJSON( arguments.data, false ); + // deserialize data if in a JSON format + // For very large JSON inputs, it's faster just to try than to call isJSON() which pre-parses the entire input again + try { + arguments.data = deserializeJSON( arguments.data, false ); + } catch( any e ) { + error( 'Input cannot be parsed as JSON. #e.message#', e.detail ); + } //format inputs into valid sql parts var dataQuery = isQuery( arguments.data ) ? arguments.data : convert.toQuery(arguments.data, arguments.headerNames); @@ -112,7 +118,7 @@ component { if( limit==0 ) { dataQueryResults = []; } else { - dataQueryResults = dataQueryResults.slice(offset, limit); + dataQueryResults = dataQueryResults.slice(offset, limit); } }; From eb47eabcab7ec87721d62f12c01f3d9d513b4f58 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 15 Dec 2022 14:20:58 -0600 Subject: [PATCH 06/48] COMMANDBOX-1539 --- src/cfml/system/config/server.schema.json | 5 +++++ .../package-commands/interceptors/PackageScripts.cfc | 1 + .../server-commands/interceptors/ServerScripts.cfc | 1 + src/cfml/system/services/InterceptorService.cfc | 2 +- src/cfml/system/services/ServerService.cfc | 4 ++++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/config/server.schema.json b/src/cfml/system/config/server.schema.json index eb6251ac..440b311b 100644 --- a/src/cfml/system/config/server.schema.json +++ b/src/cfml/system/config/server.schema.json @@ -886,6 +886,11 @@ "description": "Runs when engine is being installed during server startup", "type": "string" }, + "onServerInitialInstall": { + "title": "On Server Initial Install Script", + "description": "Runs when engine is being installed the first time during server startup", + "type": "string" + }, "onServerStop": { "title": "On Server Stop Script", "description": "Runs before a server stop", diff --git a/src/cfml/system/modules_app/package-commands/interceptors/PackageScripts.cfc b/src/cfml/system/modules_app/package-commands/interceptors/PackageScripts.cfc index 304bf51c..810baaa0 100644 --- a/src/cfml/system/modules_app/package-commands/interceptors/PackageScripts.cfc +++ b/src/cfml/system/modules_app/package-commands/interceptors/PackageScripts.cfc @@ -42,6 +42,7 @@ component { function preServerStart() { processScripts( 'preServerStart', shell.pwd(), interceptData ); } function onServerInstall() { processScripts( 'onServerInstall', interceptData.serverinfo.webroot, interceptData ); } + function onServerInitialInstall() { processScripts( 'onServerInitialInstall', interceptData.serverinfo.webroot, interceptData ); } function onServerStart() { processScripts( 'onServerStart', interceptData.serverinfo.webroot, interceptData ); } function onServerStop() { processScripts( 'onServerStop', interceptData.serverinfo.webroot, interceptData ); } function preServerForget() { processScripts( 'preServerForget', interceptData.serverinfo.webroot, interceptData ); } diff --git a/src/cfml/system/modules_app/server-commands/interceptors/ServerScripts.cfc b/src/cfml/system/modules_app/server-commands/interceptors/ServerScripts.cfc index 09cb818e..c73868d5 100644 --- a/src/cfml/system/modules_app/server-commands/interceptors/ServerScripts.cfc +++ b/src/cfml/system/modules_app/server-commands/interceptors/ServerScripts.cfc @@ -18,6 +18,7 @@ component { function preServerStart() { processScripts( 'preServerStart', shell.pwd(), interceptData ); } function onServerInstall() { processScripts( 'onServerInstall', interceptData.serverinfo.webroot, interceptData ); } + function onServerInitialInstall() { processScripts( 'onServerInitialInstall', interceptData.serverinfo.webroot, interceptData ); } function onServerStart() { processScripts( 'onServerStart', interceptData.serverinfo.webroot, interceptData ); } function onServerStop() { processScripts( 'onServerStop', interceptData.serverinfo.webroot, interceptData ); } function preServerForget() { processScripts( 'preServerForget', interceptData.serverinfo.webroot, interceptData ); } diff --git a/src/cfml/system/services/InterceptorService.cfc b/src/cfml/system/services/InterceptorService.cfc index b1e539ab..d3f62014 100644 --- a/src/cfml/system/services/InterceptorService.cfc +++ b/src/cfml/system/services/InterceptorService.cfc @@ -33,7 +33,7 @@ component accessors=true singleton { // Module lifecycle 'preModuleLoad','postModuleLoad','preModuleUnLoad','postModuleUnload', // Server lifecycle - 'preServerStart','onServerStart','onServerInstall','onServerProcessLaunch','onServerStop','preServerForget','postServerForget', + 'preServerStart','onServerStart','onServerInstall','onServerInitialInstall','onServerProcessLaunch','onServerStop','preServerForget','postServerForget', // Error handling 'onException', // Package lifecycle diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index c8568b93..ac11e30a 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1258,6 +1258,9 @@ component accessors="true" singleton { setServerInfo( serverInfo ); // This interception point can be used for additional configuration of the engine before it actually starts. interceptorService.announceInterception( 'onServerInstall', { serverInfo=serverInfo, installDetails=installDetails, serverJSON=serverJSON, defaults=defaults, serverProps=serverProps, serverDetails=serverDetails } ); + if( installDetails.initialInstall ) { + interceptorService.announceInterception( 'onServerInitialInstall', { serverInfo=serverInfo, installDetails=installDetails, serverJSON=serverJSON, defaults=defaults, serverProps=serverProps, serverDetails=serverDetails } ); + } // If Lucee server, set the java agent if( serverInfo.cfengine contains "lucee" ) { @@ -3030,6 +3033,7 @@ component accessors="true" singleton { 'scripts' : { 'preServerStart' : '', 'onServerInstall' : '', + 'onServerInitialInstall' : '', 'onServerStart' : '', 'onServerStop' : '', 'preServerForget' : '', From 15fb8b4729344a9255585c4ed6bb8869a43337b2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 23 Dec 2022 17:25:59 -0600 Subject: [PATCH 07/48] COMMANDBOX-1541 --- build/build.properties | 2 +- src/java/cliloader/LoaderCLIMain.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 6aa266b7..5d4ece57 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,7 +12,7 @@ java.debug=true dependencies.dir=${basedir}/lib cfml.version=5.3.10.97 cfml.extensions=8D7FB0DF-08BB-1589-FE3975678F07DB17 -cfml.loader.version=2.8.1 +cfml.loader.version=2.8.2 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} # Don't bump this version. Need to remove this dependency from cfmlprojects.org diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index ab8824c0..f1fcdfb5 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -303,6 +303,7 @@ && new File( cliArguments.get( 0 ) ).isFile() ) { // A couple tweaks to make Felix faster System.setProperty( "felix.cache.locking", "false" ); System.setProperty( "felix.storage.clean", "none" ); + System.setProperty( "felix.log.level", System.getProperty( "felix.log.level", System.getenv().getOrDefault("FELIX_LOG_LEVEL", "0" ) ) ); // Load up JSR-223! ScriptEngineManager engineManager = new ScriptEngineManager( cl ); From d2dc50171280ae45fff7b8314c5d583215d6d7ae Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 23 Dec 2022 17:26:50 -0600 Subject: [PATCH 08/48] COMMANDBOX-1542 --- src/cfml/system/services/ServerService.cfc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index ac11e30a..857260a5 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -2139,6 +2139,9 @@ component accessors="true" singleton { function fixBinaryPath(command, fullPath){ if(!isNull(fullPath) or !isEmpty(fullPath)){ + if( fullPath contains ' ' ) { + fullPath = '"' & fullPath & '"'; + } if( command.left( 4 ) == 'box ' ){ command = command.replacenoCase( 'box ', fullPath & ' ', 'one' ); } else if( command.left( 8 ) == 'box.exe ' ){ From e5c533f804ae8d32a78c13dae160accd45ff0500 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 27 Dec 2022 15:50:18 -0600 Subject: [PATCH 09/48] COMMANDBOX-1543 --- src/cfml/system/services/ServerService.cfc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 857260a5..121a0a83 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -147,6 +147,7 @@ component accessors="true" singleton { 'host' : d.web.host ?: '127.0.0.1', 'directoryBrowsing' : d.web.directoryBrowsing ?: '', 'webroot' : d.web.webroot ?: '', + 'caseSensitivePaths' : d.web.caseSensitivePaths ?: '', // Duplicate so onServerStart interceptors don't actually change config settings via reference. 'aliases' : duplicate( d.web.aliases ?: {} ), // Duplicate so onServerStart interceptors don't actually change config settings via reference. @@ -887,6 +888,8 @@ component accessors="true" singleton { serverInfo.welcomeFiles = serverProps.welcomeFiles ?: serverJSON.web.welcomeFiles ?: defaults.web.welcomeFiles; serverInfo.maxRequests = serverJSON.web.maxRequests ?: defaults.web.maxRequests; + serverInfo.caseSensitivePaths= serverJSON.web.caseSensitivePaths ?: defaults.web.caseSensitivePaths; + serverInfo.trayEnable = serverProps.trayEnable ?: serverJSON.trayEnable ?: defaults.trayEnable; serverInfo.dockEnable = serverJSON.dockEnable ?: defaults.dockEnable; serverInfo.defaultBaseURL = serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#'; @@ -1621,6 +1624,9 @@ component accessors="true" singleton { if( len( serverInfo.maxRequests ) ) { args.append( '--worker-threads' ).append( serverInfo.maxRequests ); } + if( len( serverInfo.caseSensitivePaths ) ) { + args.append( '--case-sensitive-web-server' ).append( serverInfo.caseSensitivePaths ); + } if( len( CLIAliases ) ) { args.append( '--dirs' ).append( CLIAliases ); } @@ -2964,6 +2970,7 @@ component accessors="true" singleton { 'customServerFolder' : '', 'welcomeFiles' : '', 'maxRequests' : '', + 'caseSensitivePaths' : '', 'exitCode' : 0, 'rules' : [], 'rulesFile' : '', From f84722189736a76515a940081cda5bb5f60aa9e7 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 29 Dec 2022 14:18:50 -0600 Subject: [PATCH 10/48] COMMANDBOX-1546 COMMANDBOX-1545 COMMANDBOX-1357 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 5d4ece57..851f6d2d 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.17+8 launch4j.version=3.14 -runwar.version=4.7.18-SNAPSHOT +runwar.version=4.7.19-SNAPSHOT jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r From 7e3bb850d69f436710d8119ed2f55eafa8033ed1 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 29 Dec 2022 14:33:19 -0600 Subject: [PATCH 11/48] COMMANDBOX-1540 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 851f6d2d..1310687d 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.17+8 launch4j.version=3.14 -runwar.version=4.7.19-SNAPSHOT +runwar.version=4.7.20-SNAPSHOT jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r From b4c2dcfe52172000131d43e61e4c577804b97480 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 29 Dec 2022 15:11:40 -0600 Subject: [PATCH 12/48] COMMANDBOX-1544 --- src/cfml/system/services/ServerService.cfc | 44 +++++++++++++--------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 121a0a83..3a7a48e8 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -325,7 +325,7 @@ component accessors="true" singleton { var foundServer = getServerInfoByName( serverDetails.defaultName ); - if( structCount( foundServer ) && normalizeWebroot( foundServer.webroot ) != normalizeWebroot( serverDetails.defaultwebroot ) ) { + if( !isSingleServerMode() && structCount( foundServer ) && normalizeWebroot( foundServer.webroot ) != normalizeWebroot( serverDetails.defaultwebroot ) ) { throw( message='You''ve asked to start a server named [#serverDetails.defaultName#] with a webroot of [#serverDetails.defaultwebroot#], but a server of this name already exists with a different webroot of [#foundServer.webroot#]', detail='Server name and webroot must be unique. Please forget the old server first. Use "server list" to see all defined servers.', @@ -2214,7 +2214,7 @@ component accessors="true" singleton { ) { // If CommandBox is in single server mode, just force the first (and only) server to be the one we find - if( ConfigService.getSetting( 'server.singleServerMode', false ) && getServers().count() ){ + if( isSingleServerMode() && getServers().count() ){ // CFConfig calls this method sometimes with a path to a JSON file and needs to get no server back if( serverProps.keyExists( 'name' ) && lcase( serverProps.name ).endsWith( '.json' ) ) { @@ -2227,16 +2227,6 @@ component accessors="true" singleton { serverIsNew : true }; } - - var serverInfo = getFirstServer(); - return { - defaultName : serverInfo.name, - defaultwebroot : serverInfo.webroot, - defaultServerConfigFile : serverInfo.serverConfigFile, - serverJSON : readServerJSON( serverInfo.serverConfigFile ), - serverInfo : serverInfo, - serverIsNew : false - }; } var job = wirebox.getInstance( 'interactiveJob' ); @@ -2305,6 +2295,20 @@ component accessors="true" singleton { serverConfigFile = serverProps.serverConfigFile ?: '' // Since this takes precedence, I only want to use it if it was actually specified ); + // If CommandBox is in single server mode, set our current values in so the "single server" always represents the last name and web root that was used. + if( isSingleServerMode() && getServers().count() ){ + if( len( defaultName ) ) { + serverInfo.name = defaultName; + } else { + serverInfo.name = replace( listLast( defaultwebroot, "\/" ), ':', ''); + } + serverInfo.webroot = defaultwebroot; + if( !isNull( serverProps.serverConfigFile ) ) { + serverInfo.serverConfigFile = serverProps.serverConfigFile; + } + setServerInfo( serverInfo ); + } + // If we found a server, set our name. if( len( serverInfo.name ?: '' ) ) { defaultName = serverInfo.name; @@ -2493,7 +2497,7 @@ component accessors="true" singleton { * @serverInfo The server information */ function getCustomServerFolder( required struct serverInfo ){ - if( configService.getSetting( 'server.singleServerMode', false ) ){ + if( isSingleServerMode() ){ return variables.customServerDirectory & 'serverHome'; } else { return variables.customServerDirectory & arguments.serverinfo.id & "-" & arguments.serverInfo.name; @@ -2647,7 +2651,7 @@ component accessors="true" singleton { function calculateServerID( webroot, name ) { - if( ConfigService.getSetting( 'server.singleServerMode', false ) ){ + if( isSingleServerMode() ){ return 'serverHome'; } var normalizedWebroot = normalizeWebroot( webroot ); @@ -2739,7 +2743,7 @@ component accessors="true" singleton { */ struct function getServerInfoByDiscovery( required directory="", required name="", serverConfigFile="" ){ - if( ConfigService.getSetting( 'server.singleServerMode', false ) && getServers().count() ){ + if( isSingleServerMode() && getServers().count() ){ return getFirstServer(); } @@ -2770,7 +2774,7 @@ component accessors="true" singleton { */ struct function getServerInfoByName( required name ){ - if( ConfigService.getSetting( 'server.singleServerMode', false ) && getServers().count() ){ + if( isSingleServerMode() && getServers().count() ){ return getFirstServer(); } @@ -2790,7 +2794,7 @@ component accessors="true" singleton { */ struct function getServerInfoByServerConfigFile( required serverConfigFile ){ - if( ConfigService.getSetting( 'server.singleServerMode', false ) && getServers().count() ){ + if( isSingleServerMode() && getServers().count() ){ return getFirstServer(); } @@ -2821,7 +2825,7 @@ component accessors="true" singleton { */ struct function getServerInfoByWebroot( required webroot ){ - if( ConfigService.getSetting( 'server.singleServerMode', false ) && getServers().count() ){ + if( isSingleServerMode() && getServers().count() ){ return getFirstServer(); } @@ -3206,6 +3210,10 @@ component accessors="true" singleton { } } + function isSingleServerMode() { + return configService.getSetting( 'server.singleServerMode', false ); + } + } From 3727ad2bca6fe9040b505817c8fcacd07da0b2dd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 12 Jan 2023 18:59:59 -0600 Subject: [PATCH 13/48] COMMANDBOX-1547 --- .../commands/coldbox/watch-reinit.cfc | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc index 7e37974f..5c1975aa 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc @@ -36,15 +36,18 @@ component { * @paths Command delimited list of file globbing paths to watch relative to the working directory, defaults to **.cfc * @delay How may milliseconds to wait before polling for changes, defaults to 500 ms * @password Reinit password + * @directory Working directory to start watcher in **/ function run( string paths, number delay, - string password = "1" + string password = "1", + string directory=getCWD() ){ // Get watch options from package descriptor var boxOptions = packageService.readPackageDescriptor( getCWD() ); var initPassword = arguments.password; + directory = resolvePath( directory ); var getOptionsWatchers = function(){ // Return to List @@ -59,6 +62,8 @@ component { // Determine watching patterns, either from arguments or boxoptions or defaults var globbingPaths = arguments.paths ?: getOptionsWatchers() ?: variables.PATHS; + var globArray = globbingPaths.listToArray(); + // handle non numeric config and put a floor of 150ms var delayMs = max( val( arguments.delay ?: boxOptions.reinitWatchDelay ?: variables.WATCH_DELAY ), 150 ); var statusColors = { @@ -91,16 +96,18 @@ component { print .greenLine( "---------------------------------------------------" ) .greenLine( "Watching the following files for a framework reinit" ) - .greenLine( "---------------------------------------------------" ) - .greenLine( " " & globbingPaths ) + .greenLine( "---------------------------------------------------" ); + globArray.each( (p) => print.greenLine( " " & p ) ); + print + .greenLine( " in directory: #directory#" ) .greenLine( " Press Ctrl-C to exit " ) .greenLine( "---------------------------------------------------" ) .toConsole(); // Start watcher watch() - .paths( globbingPaths.listToArray() ) - .inDirectory( getCWD() ) + .paths( globArray ) + .inDirectory( directory ) .withDelay( delayMs ) .onChange( function( changeData ){ // output file changes From 56c735a995c1a32f29994209385778faaaf80aa8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 12 Jan 2023 20:37:42 -0600 Subject: [PATCH 14/48] COMMANDBOX-1547 --- src/cfml/system/config/box.json.txt | 5 ++++- .../commands/coldbox/watch-reinit.cfc | 20 +++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/cfml/system/config/box.json.txt b/src/cfml/system/config/box.json.txt index f8f24a81..9ce9168e 100644 --- a/src/cfml/system/config/box.json.txt +++ b/src/cfml/system/config/box.json.txt @@ -75,5 +75,8 @@ "watchDelay":500, "watchPaths":"**.cfc", "options":{} - } + }, + "reinitWatchDirectory" : "", + "reinitWatchDelay" : 500, + "reinitWatchPaths" : "", } diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc index 5c1975aa..c8abf1a4 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/watch-reinit.cfc @@ -17,7 +17,8 @@ * * {code} * package set reinitWatchDelay=1000 - * package set reinitWatchPaths= "config/**.cfc,handlers/**.cfc,models/**.cfc,ModuleConfig.cfc" + * package set reinitWatchPaths="config/**.cfc,handlers/**.cfc,models/**.cfc,ModuleConfig.cfc" + * package set reinitWatchDirectory="../" * {code} * * This command will run in the foreground until you stop it. When you are ready to shut down the watcher, press Ctrl+C. @@ -42,12 +43,11 @@ component { string paths, number delay, string password = "1", - string directory=getCWD() + string directory ){ // Get watch options from package descriptor var boxOptions = packageService.readPackageDescriptor( getCWD() ); var initPassword = arguments.password; - directory = resolvePath( directory ); var getOptionsWatchers = function(){ // Return to List @@ -63,9 +63,11 @@ component { // Determine watching patterns, either from arguments or boxoptions or defaults var globbingPaths = arguments.paths ?: getOptionsWatchers() ?: variables.PATHS; var globArray = globbingPaths.listToArray(); + var theDirectory = arguments.directory ?: boxOptions.reinitWatchDirectory ?: getCWD(); + theDirectory = resolvePath( theDirectory ); - // handle non numeric config and put a floor of 150ms - var delayMs = max( val( arguments.delay ?: boxOptions.reinitWatchDelay ?: variables.WATCH_DELAY ), 150 ); + // handle non numeric config + var delayMs = max( val( arguments.delay ?: boxOptions.reinitWatchDelay ?: variables.WATCH_DELAY ), variables.WATCH_DELAY ); var statusColors = { "added" : "green", "removed" : "red", @@ -96,10 +98,12 @@ component { print .greenLine( "---------------------------------------------------" ) .greenLine( "Watching the following files for a framework reinit" ) - .greenLine( "---------------------------------------------------" ); + .greenLine( "---------------------------------------------------" ) + .line(); globArray.each( (p) => print.greenLine( " " & p ) ); print - .greenLine( " in directory: #directory#" ) + .line() + .greenLine( " in directory: #theDirectory#" ) .greenLine( " Press Ctrl-C to exit " ) .greenLine( "---------------------------------------------------" ) .toConsole(); @@ -107,7 +111,7 @@ component { // Start watcher watch() .paths( globArray ) - .inDirectory( directory ) + .inDirectory( theDirectory ) .withDelay( delayMs ) .onChange( function( changeData ){ // output file changes From 836cb9d40161201830417f07c7b7f17e4d4778f1 Mon Sep 17 00:00:00 2001 From: Mark Bockenstedt Date: Wed, 18 Jan 2023 15:15:57 -0600 Subject: [PATCH 15/48] FIx typo --- src/cfml/system/Quotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/Quotes.txt b/src/cfml/system/Quotes.txt index 83374c34..673946d3 100644 --- a/src/cfml/system/Quotes.txt +++ b/src/cfml/system/Quotes.txt @@ -13,7 +13,7 @@ Forget a server as you stop it with "stop --forget" Stop all your servers at once with "stop --all" View the servers for a given folder with "server list --local" This CLI viewed best on Netscape navigator 2.0 -On WIndows? Use ConEMU, it supports 256 glorious colors! +On Windows? Use ConEMU, it supports 256 glorious colors! The code name for CommandBox before it launched was "Project Gideon" CommandBox is a registered trademark of Ortus Solutions CommandBox is professional supported open source software From ddbecdae58c2fb0ac2f51cc20b07353815b8427c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 20 Jan 2023 19:46:13 +0100 Subject: [PATCH 16/48] finalized assert function for all commands and tasks (#324) * finalized assert function for all commands and tasks * updates according to pr --- src/cfml/system/BaseCommand.cfc | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index 2fc576e0..4b632470 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -221,9 +221,9 @@ component accessors="true" singleton { function error( required message, detail='', clearPrintBuffer=false, exitCode=1 ) { wirebox.getInstance( "ConsolePainter" ).stop( message ); - + if( !getSystemSetting( 'box_currentCommandPiped', false ) ) { - print.line().toConsole(); + print.line().toConsole(); } setExitCode( arguments.exitCode ); @@ -234,6 +234,25 @@ component accessors="true" singleton { throw( message=arguments.message, detail=arguments.detail, type="commandException", errorcode=arguments.exitCode ); } + /** + * This method mimics a Java/Groovy assert() function, where it evaluates the target to a boolean value or an executable closure and it must be true + * to pass and return a true to you, or throw an `AssertException` + * + * @target The tareget to evaluate for being true, it can also be a closure that will be evaluated at runtime + * @message The message to send in the exception + * + * @throws AssertException if the target is a false or null value + * @return True, if the target is a non-null value. If false, then it will throw the `AssertError` exception + */ + boolean function assert( target, message="" ){ + // param against nulls + arguments.target = arguments.target ?: false; + // evaluate it + var results = isClosure( arguments.target ) || isCustomFunction( arguments.target ) ? arguments.target( this ) : arguments.target; + // deal it : callstack two is from where the `assert` was called. + return results ? true : error( "Assertion failed from #callStackGet()[ 2 ].toString()#", arguments.target ); + } + /** * Tells you if the error() method has been called on this command. **/ From b4839f0b668685cd54307ce5ed05983484826160 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 24 Jan 2023 14:24:22 -0600 Subject: [PATCH 17/48] Small fix for Luis' pull --- src/cfml/system/BaseCommand.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index 4b632470..c5a8ed4c 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -250,7 +250,7 @@ component accessors="true" singleton { // evaluate it var results = isClosure( arguments.target ) || isCustomFunction( arguments.target ) ? arguments.target( this ) : arguments.target; // deal it : callstack two is from where the `assert` was called. - return results ? true : error( "Assertion failed from #callStackGet()[ 2 ].toString()#", arguments.target ); + return results ? true : error( "Assertion failed from #callStackGet()[ 2 ].toString()#", arguments.message ); } /** From 09ebefc545e89e18df09f46673485c88c907bc01 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 24 Jan 2023 14:24:40 -0600 Subject: [PATCH 18/48] COMMANDBOX-1549 COMMANDBOX-1550 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 1310687d..ab3fd7b5 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.17+8 launch4j.version=3.14 -runwar.version=4.7.20-SNAPSHOT +runwar.version=4.8.2-SNAPSHOT jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r From 5223b5b0fc72c083e22e313df2ae5965cb7c506b Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 24 Jan 2023 14:29:55 -0600 Subject: [PATCH 19/48] COMMANDBOX-1551 --- src/cfml/system/util/CFMLExecutor.cfc | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/util/CFMLExecutor.cfc b/src/cfml/system/util/CFMLExecutor.cfc index 643ef359..f57727f2 100644 --- a/src/cfml/system/util/CFMLExecutor.cfc +++ b/src/cfml/system/util/CFMLExecutor.cfc @@ -30,11 +30,14 @@ component { savecontent variable="local.out"{ include "#arguments.template#"; } + if( !isNull( variables.__result2 ) ) { + return variables.__result2; + } return local.out; } /** - * Execute a snipped of code in the context of a directory + * Execute a snippet of code in the context of a directory * @code.hint CFML code to run * @script.hint is the CFML code script or tags * @directory.hint Absolute path to a directory context to run in @@ -47,7 +50,7 @@ component { var tmpFileAbsolute = arguments.directory & "/" & tmpFile; // generate cfml command to write to file - var CFMLFileContents = ( arguments.script ? "" & arguments.code & "" : arguments.code ); + var CFMLFileContents = ( arguments.script ? "variables.__result2 = " & arguments.code & "" : arguments.code ); // write out our cfml command fileWrite( tmpFileAbsolute, CFMLFileContents ); @@ -55,7 +58,14 @@ component { try { return runFile( tmpFileAbsolute, arguments.vars ); } catch( any e ){ - rethrow; + + // generate cfml command to write to file + local.CFMLFileContents = ( arguments.script ? "" & arguments.code & "" : arguments.code ); + + // write out our cfml command + fileWrite( tmpFileAbsolute, CFMLFileContents ); + + return runFile( tmpFileAbsolute, arguments.vars ); } finally { // cleanup if( fileExists( tmpFileAbsolute ) ){ From afd9c1ffe37df34d0e6dcbc7cb1b1e7fc33a4824 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 24 Jan 2023 15:15:41 -0600 Subject: [PATCH 20/48] COMMANDBOX-1552 --- src/java/cliloader/LoaderCLIMain.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java/cliloader/LoaderCLIMain.java b/src/java/cliloader/LoaderCLIMain.java index f1fcdfb5..44ec862f 100644 --- a/src/java/cliloader/LoaderCLIMain.java +++ b/src/java/cliloader/LoaderCLIMain.java @@ -302,8 +302,8 @@ && new File( cliArguments.get( 0 ) ).isFile() ) { System.setProperty( "lucee.base.dir", getLuceeCLIConfigServerDir().getAbsolutePath() ); // A couple tweaks to make Felix faster System.setProperty( "felix.cache.locking", "false" ); - System.setProperty( "felix.storage.clean", "none" ); - System.setProperty( "felix.log.level", System.getProperty( "felix.log.level", System.getenv().getOrDefault("FELIX_LOG_LEVEL", "0" ) ) ); + System.setProperty( "org.osgi.framework.storage.clean", "none" ); + // System.setProperty( "felix.log.level", System.getProperty( "felix.log.level", System.getenv().getOrDefault("FELIX_LOG_LEVEL", "0" ) ) ); // Load up JSR-223! ScriptEngineManager engineManager = new ScriptEngineManager( cl ); From 6d04cb5ef0b82e19295b9ecc79370cfc7115f6af Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 24 Jan 2023 15:16:00 -0600 Subject: [PATCH 21/48] COMMANDBOX-1552 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index ab3fd7b5..c38fa1d6 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,7 +12,7 @@ java.debug=true dependencies.dir=${basedir}/lib cfml.version=5.3.10.97 cfml.extensions=8D7FB0DF-08BB-1589-FE3975678F07DB17 -cfml.loader.version=2.8.2 +cfml.loader.version=2.8.3 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} # Don't bump this version. Need to remove this dependency from cfmlprojects.org From 053d3b1ca681cbcf58112dc964d25ce27eb41557 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 24 Jan 2023 15:34:44 -0600 Subject: [PATCH 22/48] COMMANDBOX-1553 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index c38fa1d6..f36fc96d 100644 --- a/build/build.properties +++ b/build/build.properties @@ -17,7 +17,7 @@ cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} # Don't bump this version. Need to remove this dependency from cfmlprojects.org lucee.config.version=5.2.4.37 -jre.version=jdk-11.0.17+8 +jre.version=jdk-11.0.18+10 launch4j.version=3.14 runwar.version=4.8.2-SNAPSHOT jline.version=3.21.0 From 0c8df19f0b28b575a7eb624f119decae9b6b9028 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 27 Jan 2023 19:56:54 -0600 Subject: [PATCH 23/48] COMMANDBOX-1554 --- src/cfml/system/config/server.schema.json | 6 ++++++ .../server-commands/commands/server/open.cfc | 9 +++++++++ src/cfml/system/services/ServerService.cfc | 11 +++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/config/server.schema.json b/src/cfml/system/config/server.schema.json index 440b311b..bf9fb692 100644 --- a/src/cfml/system/config/server.schema.json +++ b/src/cfml/system/config/server.schema.json @@ -81,6 +81,12 @@ "type": "string", "default": "" }, + "preferredBrowser": { + "title": "Preferred Browser", + "description": "Preferred Browser to use for open commands, including 'server open' and the tray menus", + "type": "string", + "default": "" + }, "openBrowser": { "title": "Open Browser", "description": "Controls whether browser opens by default when starting server", diff --git a/src/cfml/system/modules_app/server-commands/commands/server/open.cfc b/src/cfml/system/modules_app/server-commands/commands/server/open.cfc index 8fd6e1c4..5e3ada9a 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/open.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/open.cfc @@ -75,6 +75,15 @@ component { if( serverDetails.serverIsNew ){ print.boldRedLine( "No servers found." ); } else { + var serverJSON = serverService.readServerJSON( serverDetails.defaultServerConfigFile ) + // If no explicit browser was provided to this command, but the server.json has one, use that. + if( !len( arguments.browser ) && len( serverJSON.preferredBrowser ?: '' ) ) { + arguments.browser = serverJSON.preferredBrowser; + } + if( !len( arguments.browser ) && len( serverInfo.preferredBrowser ) ) { + arguments.browser = serverInfo.preferredBrowser; + } + if ( arguments.webRoot ) { if ( fileSystemUtil.openNatively(serverInfo.appFileSystemPath) ) { print.line( "Web Root Opened." ); diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 3a7a48e8..d0a3f90e 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -119,6 +119,7 @@ component accessors="true" singleton { return { 'name' : d.name ?: '', + 'preferredBrowser' : d.preferredBrowser ?: '', 'openBrowser' : d.openBrowser ?: true, 'openBrowserURL' : d.openBrowserURL ?: '', 'startTimeout' : 240, @@ -389,7 +390,7 @@ component accessors="true" singleton { } else if( action == 'openinbrowser' ) { job.addLog( "Opening...#serverInfo.openbrowserURL#" ); job.error( 'Aborting...' ); - shell.callCommand( 'browse #serverInfo.openbrowserURL#', false); + shell.callCommand( 'server open', false); return; } else if( action == 'newname' ) { job.clear(); @@ -601,6 +602,7 @@ component accessors="true" singleton { serverInfo.openbrowser = serverProps.openbrowser ?: serverJSON.openbrowser ?: defaults.openbrowser; serverInfo.openbrowserURL = serverProps.openbrowserURL ?: serverJSON.openbrowserURL ?: defaults.openbrowserURL; + serverInfo.preferredBrowser = serverJSON.preferredBrowser ?: defaults.preferredBrowser; // Trace assumes debug serverInfo.debug = serverInfo.trace || serverInfo.debug; @@ -1543,7 +1545,11 @@ component accessors="true" singleton { .append( '--cookie-httponly' ).append( serverInfo.sessionCookieHTTPOnly ) .append( '--pid-file').append( serverInfo.pidfile ); - if( ConfigService.settingExists( 'preferredBrowser' ) ) { + // If server.json has a default browser, use it + if( len( serverInfo.preferredBrowser ) ) { + args.append( '--preferred-browser' ).append( serverInfo.preferredBrowser ); + // Otherwise, use the global config setting, if it exists + } else if( ConfigService.settingExists( 'preferredBrowser' ) ) { args.append( '--preferred-browser' ).append( ConfigService.getSetting( 'preferredBrowser' ) ); } @@ -2970,6 +2976,7 @@ component accessors="true" singleton { 'dateLastStarted' : '', 'openBrowser' : true, 'openBrowserURL' : '', + 'preferredBrowser' : '', 'profile' : '', 'customServerFolder' : '', 'welcomeFiles' : '', From 9601731e727a9085dd8243a547ab9b70aa4be2de Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 27 Jan 2023 23:23:18 -0600 Subject: [PATCH 24/48] COMMANDBOX-1434 --- src/cfml/system/box.json | 42 +-- src/cfml/system/modules/globber/box.json | 2 +- src/cfml/system/modules/jsondiff/LICENSE | 21 ++ .../system/modules/jsondiff/ModuleConfig.cfc | 16 + src/cfml/system/modules/jsondiff/box.json | 46 +++ .../modules/jsondiff/models/jsondiff.cfc | 288 ++++++++++++++++++ .../commands/config/sync/diff.cfc | 95 ++++++ .../commands/config/sync/pull.cfc | 105 +++++++ .../commands/config/sync/push.cfc | 106 +++++++ src/cfml/system/util/ForgeBox.cfc | 49 ++- 10 files changed, 746 insertions(+), 24 deletions(-) create mode 100644 src/cfml/system/modules/jsondiff/LICENSE create mode 100644 src/cfml/system/modules/jsondiff/ModuleConfig.cfc create mode 100644 src/cfml/system/modules/jsondiff/box.json create mode 100644 src/cfml/system/modules/jsondiff/models/jsondiff.cfc create mode 100644 src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc create mode 100644 src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc create mode 100644 src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc diff --git a/src/cfml/system/box.json b/src/cfml/system/box.json index 0b7168c2..0d8a7acf 100644 --- a/src/cfml/system/box.json +++ b/src/cfml/system/box.json @@ -1,22 +1,24 @@ { - "name": "CommandBox System Core", - "version": "@build.version@+@build.number@", - "author": "Brad Wood", - "shortDescription": "This tracks the CommandBox core dependencies", - "dependencies": { - "string-similarity": "^1.0.0", - "semver": "^1.2.3", - "globber": "^3.0.4", - "JSONPrettyPrint": "^1.0.0", - "propertyFile": "^1.0.9", - "JMESPath": "^2.4.0" - }, - "devDependencies": {}, - "installPaths": { - "string-similarity": "modules\\string-similarity", - "semver": "modules/semver/", - "globber": "modules/globber/", - "JSONPrettyPrint": "modules\\JSONPrettyPrint", - "propertyFile": "modules\\propertyFile" - } + "name":"CommandBox System Core", + "version":"@build.version@+@build.number@", + "author":"Brad Wood", + "shortDescription":"This tracks the CommandBox core dependencies", + "dependencies":{ + "string-similarity":"^1.0.0", + "semver":"^1.2.3", + "globber":"^3.0.4", + "JSONPrettyPrint":"^1.0.0", + "propertyFile":"^1.0.9", + "JMESPath":"^2.4.0", + "jsondiff":"^1.1.3" + }, + "devDependencies":{}, + "installPaths":{ + "string-similarity":"modules\\string-similarity", + "semver":"modules/semver/", + "globber":"modules/globber/", + "JSONPrettyPrint":"modules\\JSONPrettyPrint", + "propertyFile":"modules\\propertyFile", + "jsondiff":"modules/jsondiff/" + } } \ No newline at end of file diff --git a/src/cfml/system/modules/globber/box.json b/src/cfml/system/modules/globber/box.json index 19826fda..93652410 100644 --- a/src/cfml/system/modules/globber/box.json +++ b/src/cfml/system/modules/globber/box.json @@ -1,6 +1,6 @@ { "name":"Globber", - "version":"3.1.4", + "version":"3.1.5", "author":"Brad Wood", "homepage":"https://github.com/Ortus-Solutions/globber/", "documentation":"https://github.com/Ortus-Solutions/globber/", diff --git a/src/cfml/system/modules/jsondiff/LICENSE b/src/cfml/system/modules/jsondiff/LICENSE new file mode 100644 index 00000000..51fdf697 --- /dev/null +++ b/src/cfml/system/modules/jsondiff/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Scott Steinbeck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/cfml/system/modules/jsondiff/ModuleConfig.cfc b/src/cfml/system/modules/jsondiff/ModuleConfig.cfc new file mode 100644 index 00000000..e141cf03 --- /dev/null +++ b/src/cfml/system/modules/jsondiff/ModuleConfig.cfc @@ -0,0 +1,16 @@ +component { + + this.name = 'JSON-Diff'; + this.title = 'JSON-Diff'; + this.author = 'Scott Steinbeck'; + this.webURL = 'https://github.com/scottsteinbeck/json-diff'; + this.description = 'An ColdFusion utility for checking if 2 JSON objects have differences'; + this.version = '1.0.4'; + this.autoMapModels = false; + this.dependencies = []; + + function configure() { + binder.map('jsondiff').to('#moduleMapping#.models.jsondiff'); + } + +} \ No newline at end of file diff --git a/src/cfml/system/modules/jsondiff/box.json b/src/cfml/system/modules/jsondiff/box.json new file mode 100644 index 00000000..a3b9ac3a --- /dev/null +++ b/src/cfml/system/modules/jsondiff/box.json @@ -0,0 +1,46 @@ +{ + "name":"JSON-Diff", + "slug":"jsondiff", + "version":"1.1.3", + "License":[ + { + "type":"MIT", + "URL":"https://github.com/scottsteinbeck/json-diff/blob/master/LICENSE" + } + ], + "author":"Scott Steinbeck ", + "Documentation":"https://github.com/scottsteinbeck/json-diff", + "Repository":{ + "type":"git", + "URL":"https://github.com/scottsteinbeck/json-diff" + }, + "scripts":{ + "postVersion":"publish", + "onRelease":"!git push --follow-tags" + }, + "Bugs":"https://github.com/scottsteinbeck/json-diff/issues", + "shortDescription":"An ColdFusion utility for checking if 2 JSON objects have differences", + "keywords":[ + "json diff", + "json-diff", + "json differences" + ], + "private":"false", + "engines":[ + { + "type":"lucee", + "version":">=4.5.x" + } + ], + "Contributors":[ + "Scott Steinbeck " + ], + "DevDependencies":{ + "testbox":"^4.2.1+400" + }, + "installPaths":{ + "testbox":"testbox/" + }, + "type":"modules", + "dependencies":{} +} \ No newline at end of file diff --git a/src/cfml/system/modules/jsondiff/models/jsondiff.cfc b/src/cfml/system/modules/jsondiff/models/jsondiff.cfc new file mode 100644 index 00000000..c284fdbd --- /dev/null +++ b/src/cfml/system/modules/jsondiff/models/jsondiff.cfc @@ -0,0 +1,288 @@ +component singleton { + variables.uniqueKeyName = '________key'; + function numericCheck(value) { + if ( + getMetadata(value).getName() == 'java.lang.Double' || + getMetadata(value).getName() == 'java.lang.Integer' + ) + return true; + return false + } + + function isSame(first, second) { + if (isNull(first) && isNull(second)) return true; + if (isNull(first) || isNull(second)) return false; + if (isSimpleValue(first) && isSimpleValue(second)) { + if (numericCheck(first) && numericCheck(second) && precisionEvaluate(first - second) != 0) { + return false; + } else if (first != second) { + return false; + } + return true; + } + + // We know that first and second have the same type so we can just check the + // first type from now on.1 + if (isArray(first) && isArray(second)) { + // Short circuit if they're not the same length; + if (first.len() != second.len()) { + return false; + } + for (var i = 1; i <= first.len(); i++) { + if (isSame(first[i], second[i]) == false) { + return false; + } + } + return true; + } + + if (isStruct(first) && isStruct(second)) { + // echo('we are here') + // An object is equal if it has the same key/value pairs. + var keysSeen = {}; + for (var key in first) { + // echo('first -> ' & key & '
'); + if (structKeyExists(first, key) && structKeyExists(second, key)) { + if (isSame(first[key], second[key]) == false) { + return false; + } + keysSeen[key] = true; + } + } + // Now check that there aren't any keys in second that weren't + // in first. + for (var key2 in second) { + // echo('second -> ' & key2 & '
'); + if (!structKeyExists(second, key2) || !structKeyExists(keysSeen, key2)) { + return false; + } + } + return true; + } + return false; + } + + function groupData(required array data, required array uniqueKeys) { + return data.reduce((acc, x) => { + var uniqueKey = uniqueKeys.reduce((accKey, key) => { + accKey.append(x[key]); + return accKey; + }, []); + uniqueKey = serializeJSON(uniqueKey); + x[variables.uniqueKeyName] = uniqueKey; + acc[uniqueKey] = x; + return acc + }, {}) + } + + + function diffByKey( + array first = [], + array second = [], + required any uniqueKeys, + array ignoreKeys = [] + ) { + + if (!isArray(uniqueKeys)) { + uniqueKeys = [uniqueKeys]; + } + var data1 = groupData(first, uniqueKeys); + var data2 = groupData(second, uniqueKeys); + var diffData = diff(data1, data2, ignoreKeys); + var groupedDiff = diffData.reduce((acc, x) => { + if (x.type == 'add') { + key = x.new[variables.uniqueKeyName]; + x.new.delete(variables.uniqueKeyName) + acc[x.type].append({'key': deserializeKey(key), 'data': x.new}); + } else if (x.type == 'remove') { + key = x.old[variables.uniqueKeyName]; + x.old.delete(variables.uniqueKeyName) + acc[x.type].append({'key': deserializeKey(key), 'data': x.old}); + } else if (x.type == 'change') { + if (!acc[x.type].keyExists(x.path[1])) acc[x.type][x.path[1]] = []; + var pathRest = arraySlice(x.path, 2); + acc[x.type][x.path[1]].append({ + 'key': pathRest[1], + 'path': pathRest, + 'new': x.new, + 'old': x.old + }); + } + return acc + }, {'add': [], 'remove': [], 'change': {}}); + groupedDiff['update'] = groupedDiff.change.reduce((acc, key, value) => { + data1[key].delete(variables.uniqueKeyName); + data2[key].delete(variables.uniqueKeyName); + acc.push({ + 'key': deserializeKey(key), + 'orig': data1[key], + 'data': data2[key], + 'changes': value + }) + return acc; + }, []); + groupedDiff.delete('change'); + first.map((row) => { row.delete(variables.uniqueKeyName)}) + second.map((row) => { row.delete(variables.uniqueKeyName)}) + return groupedDiff; + } + + function deserializeKey(serializedKey){ + var valueArr = deserializeJSON(serializedKey); + if(valueArr.len() == 1) return valueArr[1]; + return valueArr; + } + + // Now check that there aren't any keys in second that weren't + function diff(any first = '', any second = '', array ignoreKeys = []) { + var diffs = []; + if ( + (isSimpleValue(first) && !isSimpleValue(second)) + || (!isSimpleValue(first) && isSimpleValue(second)) + ) { + diffs.append({ + 'path': [], + 'type': 'CHANGE', + 'old': first, + 'new': second + }); + } else if (isSimpleValue(first) && isSimpleValue(second)) { + if ( + numericCheck(first) + && numericCheck(second) + ) { + if (precisionEvaluate(first - second) != 0) { + diffs.append({ + 'path': [], + 'type': 'CHANGE', + 'old': first, + 'new': second + }); + } + } else if (first != second) { + diffs.append({ + 'path': [], + 'type': 'CHANGE', + 'old': first, + 'new': second + }); + } + } else if (isArray(first) && isArray(second)) { + for (var i = 1; i <= first.len(); i++) { + var path = i; + + if (second.len() < i) { + diffs.append({ + 'path': [path], + 'type': 'REMOVE', + 'old': first[i], + 'new': '' + }); + } else if (isSimpleValue(first[i]) && isSimpleValue(second[i])) { + if ( + numericCheck(first[i]) + && numericCheck(second[i]) + ) { + if (precisionEvaluate(first[i] - second[i]) != 0) { + diffs.append({ + 'path': [path], + 'type': 'CHANGE', + 'old': first[i], + 'new': second[i] + }); + } + } else if (first[i] != second[i]) { + diffs.append({ + 'path': [path], + 'type': 'CHANGE', + 'old': first[i], + 'new': second[i] + }); + } + } else { + var nestedDiffs = diff(first[i], second[i], ignoreKeys); + nestedDiffs = nestedDiffs.each((difference) => { + difference.path.prepend(path); + diffs.append(difference); + }); + } + } + for (var t = first.len() + 1; t <= second.len(); t++) { + var path = t; + diffs.append({ + 'type': 'ADD', + 'path': [path], + 'old': '', + 'new': second[path] + }); + } + } else if (isStruct(first) && isStruct(second)) { + var keysSeen = {}; + for (var key in first) { + var path = key; + if (ignoreKeys.find(key) > 0) { + continue; + } + if (!first.keyExists(key)) first[key] = ''; + if (!second.keyExists(key)) { + diffs.append({ + 'path': [path], + 'type': 'REMOVE', + 'old': first[key], + 'new': '' + }); + } else if (isSimpleValue(first[key]) && isSimpleValue(second[key])) { + if ( + numericCheck(first[key]) + && numericCheck(second[key]) + ) { + if (precisionEvaluate(first[key] - second[key]) != 0) { + diffs.append({ + 'key': path, + 'path': [path], + 'type': 'CHANGE', + 'old': first[key], + 'new': second[key] + }); + } + } else if (first[key] != second[key]) { + diffs.append({ + 'key': path, + 'path': [path], + 'type': 'CHANGE', + 'old': first[key], + 'new': second[key] + }); + } + } else { + if (structKeyExists(first, key) && structKeyExists(second, key)) { + var nestedDiffs = diff(first[key], second[key], ignoreKeys); + nestedDiffs = nestedDiffs.each((difference) => { + difference.path.prepend(path); + diffs.append(difference); + }) + } + } + keysSeen[key] = true; + } + // Now check that there aren't any keys in second that weren't + // in first. + for (var key2 in second) { + if (ignoreKeys.find(key2) > 0) { + continue; + }; + if (structKeyExists(second, key2) && !structKeyExists(keysSeen, key2)) { + diffs.append({ + 'type': 'ADD', + 'path': [key2], + 'old': '', + 'new': second[key2] + }); + } + } + } + + return diffs; + } + +} diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc new file mode 100644 index 00000000..c259ad13 --- /dev/null +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc @@ -0,0 +1,95 @@ +/** + * Compare local config settings with remote settings for your user + * . + * {code:bash} + * config sync diff + * {code} + * . + **/ +component { + + property name="ConfigService" inject="ConfigService"; + property name="JSONService" inject="JSONService"; + property name="endpointService" inject="endpointService"; + property name="jsondiff" inject="jsondiff"; + + /** + * @endpointName Name of custom forgebox endpoint to use + * @endpointName.optionsUDF endpointNameComplete + * @overwrite Overwrite local settings entirely with remote settings + **/ + function run( string endpointName, boolean overwrite=false ) { + try { + + endpointName = endpointName ?: configService.getSetting( 'endpoints.defaultForgeBoxEndpoint', 'forgebox' ); + + try { + var oEndpoint = endpointService.getEndpoint( endpointName ); + } catch( EndpointNotFound var e ) { + error( e.message, e.detail ?: '' ); + } + + var forgebox = oEndpoint.getForgebox(); + var APIToken = oEndpoint.getAPIToken(); + + if( !len( APIToken ) ) { + if( endpointName == 'forgebox' ) { + error( 'You don''t have a Forgebox API token set.', 'Use "forgebox login" to authenticate as a user.' ); + } else { + error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); + } + } + var userData = forgebox.whoami( APIToken ); + var remoteConfig = forgebox.getConfig( userData.username, APIToken ); + var configSettings = duplicate( configService.getconfigSettings( noOverrides=true ) ); + + var diffDetails = jsondiff.diff(remoteConfig, configSettings ) + .reduce( ( diffDetails, item )=>{ + diffDetails[ item.type ].append( item ); + return diffDetails; + }, { add : [], remove : [], change : [] } ); + + if( diffDetails.remove.len() ) { + print.line().boldBlueLine( 'Remote-only settings:' ); + diffDetails.remove.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.old ) ) ) + } + + if( diffDetails.add.len() ) { + print.line().boldGreenLine( 'Local-only settings:' ); + diffDetails.add.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.new ) ) ) + } + + if( diffDetails.change.len() ) { + print.line().boldMagentaLine( 'Changed settings:' ); + diffDetails.change.each( ( item )=>{ + print.indentedLine( buildPath( item.path ) & ' = ' ) + .indentedIndentedBlue( 'Remote Value: ' ).line( serializeJSON( item.old ) ) + .indentedIndentedGreen( 'Local Value: ' ).line(serializeJSON( item.new ) ); + } ); + } + + if( !diffDetails.add.len() && !diffDetails.remove.len() && !diffDetails.change.len() ) { + print.boldGreenLine( "All config settings are identical between remote and local" ); + } + + } catch( forgebox var e ) { + // This can include "expected" errors such as "Email already in use" + error( e.message, e.detail ); + } + + } + + function buildPath( tokens ) { + return tokens.reduce( ( path, item )=>{ + if( isNumeric( item ) ) { + return path & "[#item#]"; + } + return path.listAppend( item, '.' ); + }, '' ); + } + + function endpointNameComplete() { + return getInstance( 'endpointService' ).forgeboxEndpointNameComplete(); + } + +} diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc new file mode 100644 index 00000000..4c262c26 --- /dev/null +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc @@ -0,0 +1,105 @@ +/** + * Sync remote config settings with locaL settings for your user + * Settings will be merged together + * . + * {code:bash} + * config sync pull + * {code} + * . + * To completely replace local settings wtih remote settings, use the --overwrite flag + * . + * {code:bash} + * config sync pull --overwrite + * {code} + * + **/ +component { + + property name="ConfigService" inject="ConfigService"; + property name="JSONService" inject="JSONService"; + property name="endpointService" inject="endpointService"; + property name="jsondiff" inject="jsondiff"; + + /** + * @endpointName Name of custom forgebox endpoint to use + * @endpointName.optionsUDF endpointNameComplete + * @overwrite Overwrite local settings entirely with remote settings + **/ + function run( string endpointName, boolean overwrite=false ) { + try { + + endpointName = endpointName ?: configService.getSetting( 'endpoints.defaultForgeBoxEndpoint', 'forgebox' ); + + try { + var oEndpoint = endpointService.getEndpoint( endpointName ); + } catch( EndpointNotFound var e ) { + error( e.message, e.detail ?: '' ); + } + + var forgebox = oEndpoint.getForgebox(); + var APIToken = oEndpoint.getAPIToken(); + + if( !len( APIToken ) ) { + if( endpointName == 'forgebox' ) { + error( 'You don''t have a Forgebox API token set.', 'Use "forgebox login" to authenticate as a user.' ); + } else { + error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); + } + } + var userData = forgebox.whoami( APIToken ); + var remoteConfig = forgebox.getConfig( userData.username, APIToken ); + var configSettings = duplicate( configService.getconfigSettings( noOverrides=true ) ); + + var diffDetails = jsondiff.diff(remoteConfig, configSettings ) + .reduce( ( diffDetails, item )=>{ + diffDetails[ item.type ].append( item ); + return diffDetails; + }, { add : [], remove : [], change : [] } ); + + if( diffDetails.remove.len() ) { + print.line().boldGreenLine( 'New incoming settings:' ); + diffDetails.remove.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.old ) ) ) + } + + if( diffDetails.add.len() && overwrite ) { + print.line().boldRedLine( 'Removed local settings:' ); + diffDetails.add.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.new ) ) ) + } + + if( diffDetails.change.len() ) { + print.line().boldYellowLine( 'Changed settings:' ); + diffDetails.change.each( ( item )=>{ + print.indentedLine( buildPath( item.path ) & ' = ' ) + .indentedIndentedRed( 'Old Value: ' ).line(serializeJSON( item.new ) ) + .indentedIndentedGreen( 'New Value: ' ).line( serializeJSON( item.old ) ); + } ); + } + + if( !overwrite ) { + remoteConfig = JSONService.mergeData( configSettings, remoteConfig ); + } + + configService.setConfigSettings( remoteConfig ); + print.line().greenLine( "ClI Settings imported" ); + + } catch( forgebox var e ) { + // This can include "expected" errors such as "Email already in use" + error( e.message, e.detail ); + } + + } + + function buildPath( tokens ) { + return tokens.reduce( ( path, item )=>{ + if( isNumeric( item ) ) { + return path & "[#item#]"; + } + return path.listAppend( item, '.' ); + }, '' ); + } + + function endpointNameComplete() { + return getInstance( 'endpointService' ).forgeboxEndpointNameComplete(); + } + +} diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc new file mode 100644 index 00000000..c44a1387 --- /dev/null +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc @@ -0,0 +1,106 @@ +/** + * Sync local config settings with remote settings for your user + * Settings will be merged together + * . + * {code:bash} + * config sync push + * {code} + * . + * To completely replace remote settings wtih local settings, use the --overwrite flag + * . + * {code:bash} + * config sync push --overwrite + * {code} + * + **/ +component { + + property name="ConfigService" inject="ConfigService"; + property name="JSONService" inject="JSONService"; + property name="endpointService" inject="endpointService"; + property name="jsondiff" inject="jsondiff"; + + /** + * @endpointName Name of custom forgebox endpoint to use + * @endpointName.optionsUDF endpointNameComplete + * @overwrite Overwrite local settings entirely with remote settings + **/ + function run( string endpointName, boolean overwrite=false ) { + try { + + endpointName = endpointName ?: configService.getSetting( 'endpoints.defaultForgeBoxEndpoint', 'forgebox' ); + + try { + var oEndpoint = endpointService.getEndpoint( endpointName ); + } catch( EndpointNotFound var e ) { + error( e.message, e.detail ?: '' ); + } + + var forgebox = oEndpoint.getForgebox(); + var APIToken = oEndpoint.getAPIToken(); + + if( !len( APIToken ) ) { + if( endpointName == 'forgebox' ) { + error( 'You don''t have a Forgebox API token set.', 'Use "forgebox login" to authenticate as a user.' ); + } else { + error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); + } + } + var userData = forgebox.whoami( APIToken ); + var configSettings = duplicate( ConfigService.getconfigSettings( noOverrides=true ) ); + var remoteConfig = forgebox.getConfig( userData.username, APIToken ); + + + + var diffDetails = jsondiff.diff(remoteConfig, configSettings ) + .reduce( ( diffDetails, item )=>{ + diffDetails[ item.type ].append( item ); + return diffDetails; + }, { add : [], remove : [], change : [] } ); + + if( diffDetails.remove.len() && overwrite ) { + print.line().boldRedLine( 'Removed remote settings:' ); + diffDetails.remove.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.old ) ) ) + } + + if( diffDetails.add.len() ) { + print.line().boldGreenLine( 'New remote settings:' ); + diffDetails.add.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.new ) ) ) + } + + if( diffDetails.change.len() ) { + print.line().boldYellowLine( 'Changed settings:' ); + diffDetails.change.each( ( item )=>{ + print.indentedLine( buildPath( item.path ) & ' = ' ) + .indentedIndentedRed( 'Old Value: ' ).line(serializeJSON( item.old ) ) + .indentedIndentedGreen( 'New Value: ' ).line( serializeJSON( item.new ) ); + } ); + } + + if( !overwrite ) { + configSettings = JSONService.mergeData( remoteConfig, configSettings ); + } + print.line().greenLine( forgebox.setConfig( configSettings, userData.username, APIToken ) ); + + } catch( forgebox var e ) { + // This can include "expected" errors such as "Email already in use" + error( e.message, e.detail ); + } + + } + + function buildPath( tokens ) { + return tokens.reduce( ( path, item )=>{ + if( isNumeric( item ) ) { + return path & "[#item#]"; + } + return path.listAppend( item, '.' ); + }, '' ); + } + + + function endpointNameComplete() { + return getInstance( 'endpointService' ).forgeboxEndpointNameComplete(); + } + +} diff --git a/src/cfml/system/util/ForgeBox.cfc b/src/cfml/system/util/ForgeBox.cfc index 7c998cf5..ddcd0483 100644 --- a/src/cfml/system/util/ForgeBox.cfc +++ b/src/cfml/system/util/ForgeBox.cfc @@ -223,6 +223,50 @@ or just add DEBUG to the root logger return results.response.data; } + /** + * set user config + */ + function setConfig( required struct config, required string username, required string APIToken ) { + + var results = makeRequest( + resource="users/#username#/clisettings", + method='post', + formFields={ + 'CLISettings' : serializeJSON( config ) + }, + headers = { + 'x-api-token' : arguments.APIToken, + "Content-Type" = "application/x-www-form-urlencoded" + } ); + + // error + if( results.response.error ){ + throw( arrayToList( results.response.messages ), 'forgebox' ); + } + + return arrayToList( results.response.messages ); + } + + /** + * set user config + */ + function getConfig( required string username, required string APIToken ) { + + var results = makeRequest( + resource="users/#username#/clisettings", + method='get', + headers = { + 'x-api-token' : arguments.APIToken + } ); + + // error + if( results.response.error ){ + throw( arrayToList( results.response.messages ), 'forgebox' ); + } + + return deserializeJSON( results.response.data ); + } + /** * Authenticates a user in ForgeBox */ @@ -429,9 +473,9 @@ or just add DEBUG to the root logger if( configService.getSetting( 'offlineMode', false ) ) { - throw( 'Can''t access #getEndpointName()# resource [#resource#], CommandBox is in offline mode. Go online with [config set offlineMode=false].', 'forgebox' ); + throw( 'Can''t access #getEndpointName()# resource [#resource#], CommandBox is in offline mode. Go online with [config set offlineMode=false].', 'forgebox' ); } - + var results = {error=false,response={},message="",responseheader={},rawResponse=""}; var HTTPResults = ""; var param = ""; @@ -480,7 +524,6 @@ or just add DEBUG to the root logger } // structDelete( arguments.headers, "Content-Type" ); - From 5e91fbbe648064f29947aa103436b314e85a17dd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 27 Jan 2023 23:25:37 -0600 Subject: [PATCH 25/48] COMMANDBOX-1555 --- .../commands/forgebox/whoami.cfc | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/whoami.cfc b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/whoami.cfc index c959077d..c0c9a69e 100644 --- a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/whoami.cfc +++ b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/whoami.cfc @@ -13,8 +13,9 @@ component { /** * @endpointName Name of custom forgebox endpoint to use * @endpointName.optionsUDF endpointNameComplete + * @json Set true for JSON format of user data **/ - function run( string endpointName ){ + function run( string endpointName, boolean json=false ){ try { endpointName = endpointName ?: configService.getSetting( 'endpoints.defaultForgeBoxEndpoint', 'forgebox' ); @@ -32,13 +33,21 @@ component { if( endpointName == 'forgebox' ) { error( 'You don''t have a Forgebox API token set.', 'Use "forgebox login" to authenticate as a user.' ); } else { - error( 'You don''t have a Forgebox API token set.', 'Use "forgebox login endpointName=#endpointName#" to authenticate as a user.' ); + error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); + } + } + var userData = forgebox.whoami( APIToken ); + if( json ) { + print.line( userData ); + } else { + print.boldLine( '#userData.fname# #userData.lname# (#userData.username#)' ) + .line( userData.email ); + if( !isNull( userData.subscription.plan ) ) { + print.line() + .line( '#userData.subscription.subscriptionType.UcFirst()# Plan: #userData.subscription.plan.name#' ) + .indentedLine( userData.subscription.plan.features ); } } - userData = forgebox.whoami( APIToken ); - - print.boldLine( '#userData.fname# #userData.lname# (#userData.username#)' ) - .line( userData.email ); } catch( forgebox var e ) { // This can include "expected" errors such as "Email already in use" From 597aeb55ff42b91f896c744b7b50e61b966b4aa5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 31 Jan 2023 13:30:05 -0600 Subject: [PATCH 26/48] COMMANDBOX-1557 --- .../package-commands/commands/package/install.cfc | 2 ++ src/cfml/system/util/ForgeBox.cfc | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/package-commands/commands/package/install.cfc b/src/cfml/system/modules_app/package-commands/commands/package/install.cfc index 40fcd2ec..f82a87cf 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/install.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/install.cfc @@ -101,6 +101,8 @@ component aliases="install" { /** * @ID.hint "endpoint:package" to install. Default endpoint is "forgebox". If no ID is passed, all dependencies in box.json will be installed. * @ID.optionsUDF IDComplete + * @ID.optionsFileComplete true + * @ID.optionsDirectoryComplete true * @directory.hint The directory to install in and creates the directory if it does not exist. This will override the packages box.json install dir if provided. * @save.hint Save the installed package as a dependency in box.json (if it exists), defaults to true * @saveDev.hint Save the installed package as a dev dependency in box.json (if it exists) diff --git a/src/cfml/system/util/ForgeBox.cfc b/src/cfml/system/util/ForgeBox.cfc index ddcd0483..bcb30db8 100644 --- a/src/cfml/system/util/ForgeBox.cfc +++ b/src/cfml/system/util/ForgeBox.cfc @@ -407,13 +407,14 @@ or just add DEBUG to the root logger string typeSlug = '', string APIToken='' ) { - var thisResource = "slugs/#arguments.searchTerm#"; + var thisResource = "slugs"; var results = makeRequest( resource=thisResource, method='get', parameters={ - typeSlug : arguments.typeSlug + typeSlug : arguments.typeSlug, + searchTerm : arguments.searchTerm }, headers = { 'x-api-token' : arguments.APIToken From 79162443ef5f19633eefda646bc51e800cb465aa Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 7 Feb 2023 17:28:26 -0600 Subject: [PATCH 27/48] Finalize assert() function for REPL usage --- src/cfml/system/util/CFMLExecutor.cfc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/cfml/system/util/CFMLExecutor.cfc b/src/cfml/system/util/CFMLExecutor.cfc index f57727f2..4d5c5462 100644 --- a/src/cfml/system/util/CFMLExecutor.cfc +++ b/src/cfml/system/util/CFMLExecutor.cfc @@ -108,4 +108,25 @@ component { } ); } + /** + * This method mimics a Java/Groovy assert() function, where it evaluates the target to a boolean value or an executable closure and it must be true + * to pass and return a true to you, or throw an `AssertException` + * + * @target The tareget to evaluate for being true, it can also be a closure that will be evaluated at runtime + * @message The message to send in the exception + * + * @throws AssertException if the target is a false or null value + * @return True, if the target is a non-null value. If false, then it will throw the `AssertError` exception + */ + boolean function assert( target, message="" ){ + // param against nulls + arguments.target = arguments.target ?: false; + // evaluate it + var results = isClosure( arguments.target ) || isCustomFunction( arguments.target ) ? arguments.target( variables ) : arguments.target; + // deal it : callstack two is from where the `assert` was called. + + + return results ? true : throw( message="Assertion failed", detail=arguments.message, type="commandException" ); + } + } From 1ed8a7114eb6f584f8e0b5ccf033f086d0d5028d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 13 Feb 2023 12:04:44 -0600 Subject: [PATCH 28/48] COMMANDBOX-1558 --- src/cfml/system/BaseCommand.cfc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index c5a8ed4c..d13b325e 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -367,5 +367,22 @@ component accessors="true" singleton { return getCurrentThread().getName(); } + /** + * Install an extension into the Lucee server instance inside the CLI. + * If the extension is already installed, nothing will happen + * + * @extensionID The ID of the extenstion to install into the CLI + * @extensionVersion The version of the extension to install into the CLI + * @LuceeContextType Either "server" or "web" + * @LuceeContextPassword Use this if you've changed the default Lucee context password in the CLI + */ + function installExtension( + required string extensionID, + string extensionVersion='latest', + string luceeContextType='server', + string luceeContextPassword='commandbox' + ){ + new Administrator( luceeContextType, luceeContextPassword ).updateExtension( extensionID, extensionVersion ); + } } From 94f176b113f3530286476340874bb49775484e99 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 13 Feb 2023 12:07:35 -0600 Subject: [PATCH 29/48] COMMANDBOX-1559 --- src/cfml/system/services/ServerService.cfc | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index d0a3f90e..cd5f33f8 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -736,19 +736,6 @@ component accessors="true" singleton { job[ 'add#( serverInfo.fileCacheEnable ? 'Success' : '' )#Log' ]( 'File Caching #( serverInfo.fileCacheEnable ? 'en' : 'dis' )#abled' ); job.complete( serverInfo.verbose ); - // Double check that the port in the user params or server.json isn't in use - if( !isPortAvailable( serverInfo.host, serverInfo.port ) ) { - job.addErrorLog( "" ); - var badPortlocation = 'config'; - if( serverProps.keyExists( 'port' ) ) { - badPortlocation = 'start params'; - } else if ( len( defaults.web.http.port ?: '' ) ) { - badPortlocation = 'server.json'; - } else { - badPortlocation = 'config server defaults'; - } - throw( message="You asked for port [#( serverProps.port ?: serverJSON.web.http.port ?: defaults.web.http.port ?: '?' )#] in your #badPortlocation# but it's already in use.", detail="Please choose another or use netstat to find out what process is using the port already.", type="commandException" ); - } serverInfo.stopsocket = serverProps.stopsocket ?: serverJSON.stopsocket ?: getRandomPort( serverInfo.host ); @@ -782,6 +769,21 @@ component accessors="true" singleton { serverInfo.AJPPort = serverProps.AJPPort ?: serverJSON.web.AJP.port ?: defaults.web.AJP.port; serverInfo.AJPSecret = serverJSON.web.AJP.secret ?: defaults.web.AJP.secret; + + // Double check that the port in the user params or server.json isn't in use + if( serverInfo.HTTPEnable && !isPortAvailable( serverInfo.host, serverInfo.port ) ) { + job.addErrorLog( "" ); + var badPortlocation = 'config'; + if( serverProps.keyExists( 'port' ) ) { + badPortlocation = 'start params'; + } else if ( len( defaults.web.http.port ?: '' ) ) { + badPortlocation = 'server.json'; + } else { + badPortlocation = 'config server defaults'; + } + throw( message="You asked for port [#serverInfo.port#] in your #badPortlocation# but it's already in use.", detail="Please choose another or use netstat to find out what process is using the port already.", type="commandException" ); + } + // relative certFile in server.json is resolved relative to the server.json if( isDefined( 'serverJSON.web.SSL.certFile' ) ) { serverJSON.web.SSL.certFile = fileSystemUtil.resolvePath( serverJSON.web.SSL.certFile, defaultServerConfigFileDirectory ); } // relative certFile in config setting server defaults is resolved relative to the web root From cc872388ea7f88b43dccaea5a46aad365af58389 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 13 Feb 2023 12:13:00 -0600 Subject: [PATCH 30/48] COMMANDBOX-1560 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index f36fc96d..83d06350 100644 --- a/build/build.properties +++ b/build/build.properties @@ -10,7 +10,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib -cfml.version=5.3.10.97 +cfml.version=5.3.10.120 cfml.extensions=8D7FB0DF-08BB-1589-FE3975678F07DB17 cfml.loader.version=2.8.3 cfml.cli.version=${cfml.loader.version}.${cfml.version} From ea85d0169012376b3621e7d510f137d75f4a1389 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 23 Feb 2023 18:34:47 -0600 Subject: [PATCH 31/48] COMMANDBOX-1561 --- .../system-commands/commands/upgrade.cfc | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/upgrade.cfc b/src/cfml/system/modules_app/system-commands/commands/upgrade.cfc index 5e37b62b..59860018 100644 --- a/src/cfml/system/modules_app/system-commands/commands/upgrade.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/upgrade.cfc @@ -68,7 +68,14 @@ component { proxyPort="#ConfigService.getSetting( 'proxy.port', 80 )#" proxyUser="#ConfigService.getSetting( 'proxy.user', '' )#" proxyPassword="#ConfigService.getSetting( 'proxy.password', '' )#" - result="local.boxRepoResult"; + result="local.boxRepoResult" { + cfhttpparam(name="CLIID", type="url", value="#GetLuceeId().server.id#"); + cfhttpparam(name="CLIVersion", type="url", value="#shell.getVersion()#"); + cfhttpparam(name="os", type="url", value="#server.system.properties['os.name']#"); + cfhttpparam(name="jre", type="url", value="#server.java.version#"); + cfhttpparam(name="APIToken", type="url", value="#ConfigService.getSetting( 'endpoints.forgebox.APIToken', '' )#"); + } + http url="#loaderRepoURL#" @@ -78,7 +85,14 @@ component { proxyPort="#ConfigService.getSetting( 'proxy.port', 80 )#" proxyUser="#ConfigService.getSetting( 'proxy.user', '' )#" proxyPassword="#ConfigService.getSetting( 'proxy.password', '' )#" - result="local.loaderRepoResult"; + result="local.loaderRepoResult"{ + cfhttpparam(name="CLIID", type="url", value="#GetLuceeId().server.id#"); + cfhttpparam(name="CLIVersion", type="url", value="#shell.getVersion()#"); + cfhttpparam(name="os", type="url", value="#server.system.properties['os.name']#"); + cfhttpparam(name="jre", type="url", value="#server.java.version#"); + cfhttpparam(name="APIToken", type="url", value="#ConfigService.getSetting( 'endpoints.forgebox.APIToken', '' )#"); + } + // Do some error checking if( !local.boxRepoResult.statusCode contains "200" || !fileExists( '#temp#/box-repo.json' ) || From 7d45ea40c3ba964497aa026ddfd56fc85ad65153 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 27 Feb 2023 16:13:28 -0600 Subject: [PATCH 32/48] COMMANDBOX-1562 --- .../commands/forgebox/version-debug.cfc | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc diff --git a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc new file mode 100644 index 00000000..1d4bd3dd --- /dev/null +++ b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc @@ -0,0 +1,216 @@ +/** + * Debug what semantic version range will match on ForgeBox. Helpful to test before actually installing + * Provide a ForgeBox install ID in the form of package@version and find out what version of the package that would actually install + * . + * {code:bash} + * forgebox version-debug coldbox@5.x + * {code} + * . + **/ +component { + + // DI + property name="semanticVersion" inject="semanticVersion@semver"; + property name="endpointService" inject="endpointService"; + property name="configService" inject="configService"; + + /** + * @installID Install ID to test + * @installID.optionsUDF IDComplete + * @endpointName Name of endpoint (defaults to "forgebox") + * @showMatchesOnly True will filter out display of package versions that didnt match sem ver range + **/ + function run( required string installID, string endpointName, boolean showMatchesOnly=false ) { + endpointName = endpointName ?: configService.getSetting( 'endpoints.defaultForgeBoxEndpoint', 'forgebox' ); + + try { + var oEndpoint = endpointService.getEndpoint( endpointName ); + } catch( EndpointNotFound var e ) { + error( e.message, e.detail ?: '' ); + } + + var APIToken = oEndpoint.getAPIToken(); + var forgebox = oEndpoint.getForgeBox(); + + var slug = oEndpoint.parseSlug( installID ) + var version = oEndpoint.parseVersion( installID ) + var satisfyingVersion = '' + print.line( 'Endpoint: #endpointName#' ) + .line( 'Requested Slug: #slug#' ) + .line( 'Requested Version: #version#' ) + .line(); + try { + + print.yellowLine( "Verifying package '#slug#' in #endpointName#, please wait..." ).toConsole(); + + var entryData = forgebox.getEntry( slug, APIToken ); + + + print.yellowLine( 'Package [#slug#] has #entryData.versions.len()# versions.' ).line().toConsole(); + + if( !entryData.isActive ) { + error( 'The #endpointName# entry [#entryData.title#] is inactive.', 'endpointException' ); + } + + var versions = entryData.versions.map( (v)=>v.version ); + var matches = []; + // If this is an exact version (not a range) just do a simple lookup for it + if( semanticVersion.isExactVersion( version, true ) ) { + print.line( 'Requested version [#version#] is an exact version, so no semantic version ranges are begin used, just a direct match.' ) + for( var thisVer in versions ) { + if( semanticVersion.isEQ( version, thisVer, true ) ) { + matches.append( thisVer.version ); + print.line( 'Exact match [#thisVer#] found.' ) + satisfyingVersion = thisVer; + break; + } + } + if( !len( satisfyingVersion ) ) { + print.redLine( 'Exact version [#version#] not found for package [#slug#].' ); + } + } else { + + print.line( 'Requested version [#version#] is a semantic range, so searching against #versions.len()# versions for matches.' ) + // For version ranges, do a smart lookup + versions.sort( function( a, b ) { return semanticVersion.compare( b, a ) } ); + for( var thisVersion in versions ) { + if( semanticVersion.satisfies( thisVersion, version ) ) { + matches.append( thisVersion ); + } + } + + if( matches.len() ) { + satisfyingVersion = matches[1]; + print.line( 'Found #matches.len()# matches for our version range, so taking the latest one.' ) + //print.line( matches ); + } else if( version == 'stable' && arrayLen( versions ) ) { + print.line( "The version [stable] doesn't match any avaialble versions, which means all versions are a pre-release, so we'll just grab the latest one (same as [be])." ) + satisfyingVersion = versions[ 1 ]; + + matches = v + } else { + print.redLine( 'Version [#version#] not found for package [#slug#].' ); + } + } + + if( len( satisfyingVersion ) ) { + print.line().boldGreenline( "Version [#satisfyingVersion#] would be chosen for installation." ); + } + + } catch( forgebox var e ) { + + if( e.detail contains 'The entry slug sent is invalid or does not exist' ) { + error( "#e.message# #e.detail#" ); + } + + print.redline( "Aww man, #endpointName# ran into an issue."); + error( "#e.message# #e.detail#" ); + + } + + print.line() + .line(); + + if( showMatchesOnly ) { + versions = matches; + } + // Create table that matches screen width and outputs versions from "lowest" to "highest" down the columns from left to right + var numVersions = versions.len(); + if( numVersions ) { + var versions = versions.reverse() + var widestVersion = versions.map( (v)=>len( v ) ).max(); + var colWdith = widestVersion + 4; + var termWidth = shell.getTermWidth()-1; + var numCols = termWidth\colWdith; + var numRows = ceiling( numVersions/numCols ); + + loop from=1 to=numRows index="local.row" { + loop from=1 to=numCols index="local.col" { + var thisIndex = row+((col-1)*numRows); + var format='grey'; + if( thisIndex > numVersions ) { + var thisVersion = ''; + } else { + var thisVersion = versions[thisIndex]; + if( satisfyingVersion == thisVersion ) { + format = 'boldwhiteOnGreen'; + } else if ( matches.find( thisVersion ) ) { + format = 'boldWhite'; + } + } + print.text( padRight( thisVersion, colWdith ), format ); + } + print.line() + } + print.line() + .greyLine( 'Unmatched Version' ) + .boldWhiteLine( 'Version matching semver range' ) + .boldwhiteOnGreenLine( 'Chosen Version' ); + + } + } + + function endpointNameComplete() { + return getInstance( 'endpointService' ).forgeboxEndpointNameComplete(); + } + + + // Auto-complete list of IDs + function IDComplete( string paramSoFar ) { + // Only hit forgebox if they've typed something. + if( !len( trim( arguments.paramSoFar ) ) ) { + return []; + } + try { + + + var endpointName = configService.getSetting( 'endpoints.defaultForgeBoxEndpoint', 'forgebox' ); + + try { + var oEndpoint = endpointService.getEndpoint( endpointName ); + } catch( EndpointNotFound var e ) { + error( e.message, e.detail ?: '' ); + } + + var forgebox = oEndpoint.getForgebox(); + var APIToken = oEndpoint.getAPIToken(); + + // Get auto-complete options + return forgebox.slugSearch( searchTerm=arguments.paramSoFar, APIToken=APIToken ); + } catch( forgebox var e ) { + // Gracefully handle ForgeBox issues + print + .line() + .yellowLine( e.message & chr( 10 ) & e.detail ) + .toConsole(); + // After outputting the message above on a new line, but the user back where they started. + getShell().getReader().redrawLine(); + } + // In case of error, break glass. + return []; + } + + + /** + * Adds characters to the right of a string until the string reaches a certain length. + * If the text is already greater than or equal to the maxWidth, the text is returned unchanged. + * @text The text to pad. + * @maxWidth The number of characters to pad up to. + * @padChar The character to use to pad the text. + */ + private string function padRight( required string text, required numeric maxWidth, string padChar = " " ) { + var textLength = len( arguments.text ); + if ( textLength == arguments.maxWidth ) { + return arguments.text; + } else if( textLength > arguments.maxWidth ) { + if( arguments.maxWidth < 4 ) { + return left( text, arguments.maxWidth ); + } else { + return left( text, arguments.maxWidth-3 )&'...'; + } + } + arguments.text &= repeatString( arguments.padChar, arguments.maxWidth-textLength ); + return arguments.text; + } + +} From bab54c129e6797de08ed11534c6bfefec4b56e17 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 13:39:53 -0600 Subject: [PATCH 33/48] COMMANDBOX-1563 --- .../forgebox-commands/commands/forgebox/version-debug.cfc | 3 +-- src/cfml/system/services/ServerService.cfc | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc index 1d4bd3dd..593b31d6 100644 --- a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc +++ b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/version-debug.cfc @@ -81,8 +81,7 @@ component { if( matches.len() ) { satisfyingVersion = matches[1]; - print.line( 'Found #matches.len()# matches for our version range, so taking the latest one.' ) - //print.line( matches ); + print.line( 'Found #matches.len()# matches for our version range, so taking the latest one.' ); } else if( version == 'stable' && arrayLen( versions ) ) { print.line( "The version [stable] doesn't match any avaialble versions, which means all versions are a pre-release, so we'll just grab the latest one (same as [be])." ) satisfyingVersion = versions[ 1 ]; diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index cd5f33f8..9662e216 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1523,12 +1523,6 @@ component accessors="true" singleton { // Add java agent if( len( trim( javaAgent ) ) ) { argTokens.append( javaagent ); } - // TODOL Temp stopgap for Java regression that prevents Undertow from starting. - // https://issues.redhat.com/browse/UNDERTOW-2073 - // https://bugs.openjdk.java.net/browse/JDK-8285445 - if( !argTokens.filter( (a)=>a contains 'jdk.io.File.enableADS' ).len() ) { - argTokens.append( '-Djdk.io.File.enableADS=true' ); - } args .append( '-jar' ).append( serverInfo.runwarJarPath ) From 5246bc0a665351020e3e4b57441bd999683a1428 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 13:43:47 -0600 Subject: [PATCH 34/48] COMMANDBOX-1564 --- src/cfml/system/services/ServerService.cfc | 24 ++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 9662e216..05f0eb03 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -1523,9 +1523,26 @@ component accessors="true" singleton { // Add java agent if( len( trim( javaAgent ) ) ) { argTokens.append( javaagent ); } + // Collect recursive list of all jars in libDirs + jarArray = serverInfo.libDirs + .listToArray() + .reduce( function( jarArray, path ) { + if( fileExists( path ) && path.lcase().endsWith( '.jar' ) ) { + jarArray.append( path ); + } else if( directoryExists( path ) ) { + directoryList( path, true, 'array' ) + .filter( (p)=>p.lcase().endsWith( '.jar' ) ) + .each( function( p ) { + jarArray.append( p ); + } ); + } + return jarArray; + }, [] ); + jarArray.append( serverInfo.runwarJarPath ); args - .append( '-jar' ).append( serverInfo.runwarJarPath ) + .append( '-cp' ).append( jarArray.toList( server.system.properties[ 'path.separator' ] ) ) + .append( 'runwar.Start' ) .append( '--background=#background#' ) .append( '--host' ).append( serverInfo.host ) .append( '--stop-port' ).append( serverInfo.stopsocket ) @@ -1669,11 +1686,6 @@ component accessors="true" singleton { args.append( '--web-xml-override-force' ).append( serverInfo.webXMLOverrideForce ); } - if( len( serverInfo.libDirs ) ) { - // Have to get rid of empty list elements - args.append( '--lib-dirs' ).append( serverInfo.libDirs.listChangeDelims( ',', ',' ) ); - } - // Always send the enable flag for each protocol args .append( '--http-enable' ).append( serverInfo.HTTPEnable ) From bdc8cf6b148f6ab14ee7d118ba6af232533fadb8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 13:50:27 -0600 Subject: [PATCH 35/48] COMMANDBOX-1556 --- build/build.properties | 2 +- src/cfml/system/services/ServerService.cfc | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/build/build.properties b/build/build.properties index 83d06350..dff824b8 100644 --- a/build/build.properties +++ b/build/build.properties @@ -19,7 +19,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.18+10 launch4j.version=3.14 -runwar.version=4.8.2-SNAPSHOT +runwar.version=4.8.3-SNAPSHOT jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 05f0eb03..10e06458 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -251,7 +251,11 @@ component accessors="true" singleton { // Duplicate so onServerStart interceptors don't actually change config settings via reference. 'XNIOOptions' : duplicate( d.runwar.XNIOOptions ?: {} ), // Duplicate so onServerStart interceptors don't actually change config settings via reference. - 'undertowOptions' : duplicate( d.runwar.undertowOptions ?: {} ) + 'undertowOptions' : duplicate( d.runwar.undertowOptions ?: {} ), + 'console' : { + 'appenderLayout' : d.runwar.console.appenderLayout ?: '', + 'appenderLayoutOptions' : duplicate( d.runwar.console.appenderLayoutOptions ?: {} ) + } }, 'ModCFML' : { 'enable' : d.ModCFML.enable ?: false, @@ -1022,6 +1026,10 @@ component accessors="true" singleton { // Global defaults are always added on top of whatever is specified by the user or server.json serverInfo.runwarUndertowOptions = ( serverJSON.runwar.UndertowOptions ?: {} ).append( defaults.runwar.UndertowOptions, true ); + serverInfo.runwarAppenderLayout = serverJSON.runwar.console.appenderLayout ?: defaults.runwar.console.appenderLayout; + serverInfo.runwarAppenderLayoutOptions = serverJSON.runwar.console.appenderLayoutOptions ?: defaults.runwar.console.appenderLayoutOptions; + + // Server startup timeout serverInfo.startTimeout = serverProps.startTimeout ?: serverJSON.startTimeout ?: defaults.startTimeout; @@ -1592,6 +1600,13 @@ component accessors="true" singleton { args.append( '--undertow-options=' & serverInfo.runwarUndertowOptions.reduce( ( opts='', k, v ) => opts.listAppend( k & '=' & v ) ) ); } + if( len( serverInfo.runwarAppenderLayout ) ) { + args.append( '--console-layout' ).append( serverInfo.runwarAppenderLayout ); + } + if( serverInfo.runwarAppenderLayoutOptions.count() ) { + args.append( '--console-layout-options' ).append( serializeJSON( serverInfo.runwarAppenderLayoutOptions ) ); + } + if( serverInfo.debug ) { // Debug is getting turned on any time I include the --debug flag regardless of whether it's true or false. args.append( '--debug' ).append( serverInfo.debug ); From 0d53e4fcb75e0916478ed4da4c9d4049a2ebe58f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 17:56:43 -0600 Subject: [PATCH 36/48] COMMANDBOX-1565 --- src/cfml/system/endpoints/Git.cfc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/endpoints/Git.cfc b/src/cfml/system/endpoints/Git.cfc index 5fb94eaf..76fd04d2 100644 --- a/src/cfml/system/endpoints/Git.cfc +++ b/src/cfml/system/endpoints/Git.cfc @@ -47,7 +47,7 @@ component accessors="true" implements="IEndpoint" singleton { public string function resolvePackage( required string package, boolean verbose=false ) { if( configService.getSetting( 'offlineMode', false ) ) { - throw( 'Can''t clone [#getNamePrefixes()#:#package#], CommandBox is in offline mode. Go online with [config set offlineMode=false].', 'endpointException' ); + throw( 'Can''t clone [#getNamePrefixes()#:#package#], CommandBox is in offline mode. Go online with [config set offlineMode=false].', 'endpointException' ); } var job = wirebox.getInstance( 'interactiveJob' ); @@ -103,6 +103,9 @@ component accessors="true" implements="IEndpoint" singleton { if( branchList.filter( function( i ) { return i contains branch; } ).len() ) { if( arguments.verbose ){ job.addLog( 'Commit-ish [#branch#] appears to be a branch.' ); } branch = 'origin/' & branch; + } else if( branch == 'master' && branchList.filter( (b)=>b contains 'main' ).len() ) { + if( arguments.verbose ){ job.addLog( 'Switching to branch [main].' ); } + branch = 'origin/main'; } // Checkout branch, tag, or commit hash. From b06a72c22af239210a9bb42080f4d2ba4b703c44 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 18:00:42 -0600 Subject: [PATCH 37/48] COMMANDBOX-1566 --- build/build.properties | 1 + build/build.xml | 43 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/build/build.properties b/build/build.properties index dff824b8..63705c04 100644 --- a/build/build.properties +++ b/build/build.properties @@ -12,6 +12,7 @@ java.debug=true dependencies.dir=${basedir}/lib cfml.version=5.3.10.120 cfml.extensions=8D7FB0DF-08BB-1589-FE3975678F07DB17 +box.bunndled.modules=commandbox-update-check,commandbox-cfconfig,commandbox-dotenv cfml.loader.version=2.8.3 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} diff --git a/build/build.xml b/build/build.xml index f751e5cd..27d80799 100644 --- a/build/build.xml +++ b/build/build.xml @@ -297,8 +297,7 @@ External Dependencies: - - + @@ -337,7 +336,6 @@ External Dependencies: -
@@ -406,6 +404,45 @@ External Dependencies: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 015db5d0e49e996846949631db9e4994ac8ef408 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 18:32:39 -0600 Subject: [PATCH 38/48] COMMANDBOX-1566 --- build/build.xml | 2 +- src/cfml/system/Shell.cfc | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 27d80799..aa73a5b4 100644 --- a/build/build.xml +++ b/build/build.xml @@ -421,9 +421,9 @@ External Dependencies: - + diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index b7a258d3..d0adb402 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -171,6 +171,19 @@ component accessors="true" singleton { fileWrite( systemBoxJSON, '{ "name":"CommandBox System" }' ); } + // Merge in any auto-installed modules + var systemBoxJSONAutoInstall = expandPath( '/commandbox/box-auto-install.json' ); + if( fileExists( systemBoxJSONAutoInstall ) ) { + var boxJSON = deserializeJSON( fileRead( systemBoxJSON ) ); + var boxJSONAuto = deserializeJSON( fileRead( systemBoxJSONAutoInstall ) ); + boxJSON.dependencies = boxJSON.dependencies ?: {}; + boxJSON.installPaths = boxJSON.installPaths ?: {}; + boxJSONAuto.dependencies = boxJSONAuto.dependencies ?: {}; + boxJSONAuto.installPaths = boxJSONAuto.installPaths ?: {}; + boxJSONAuto.dependencies.each( (k,v)=> boxJSON.dependencies[k]=v ); + boxJSONAuto.installPaths.each( (k,v)=> boxJSON.installPaths[k]=v ); + fileWrite( systemBoxJSONAutoInstall, serializeJSON( boxJSON ) ); + } } From 1a2f83066cb3899c54e1b4d53052c4ed83e84e64 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 18:35:40 -0600 Subject: [PATCH 39/48] COMMANDBOX-1566 --- src/cfml/system/Shell.cfc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index d0adb402..66fd90b8 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -183,7 +183,9 @@ component accessors="true" singleton { boxJSONAuto.dependencies.each( (k,v)=> boxJSON.dependencies[k]=v ); boxJSONAuto.installPaths.each( (k,v)=> boxJSON.installPaths[k]=v ); fileWrite( systemBoxJSONAutoInstall, serializeJSON( boxJSON ) ); + fileDelete( systemBoxJSONAutoInstall ); } + } From 055253ee7e7af6d125d6025eef46b953aa942b01 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 19:10:26 -0600 Subject: [PATCH 40/48] COMMANDBOX-1566 --- src/cfml/system/Shell.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index 66fd90b8..52acff88 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -182,7 +182,7 @@ component accessors="true" singleton { boxJSONAuto.installPaths = boxJSONAuto.installPaths ?: {}; boxJSONAuto.dependencies.each( (k,v)=> boxJSON.dependencies[k]=v ); boxJSONAuto.installPaths.each( (k,v)=> boxJSON.installPaths[k]=v ); - fileWrite( systemBoxJSONAutoInstall, serializeJSON( boxJSON ) ); + fileWrite( systemBoxJSON, serializeJSON( boxJSON ) ); fileDelete( systemBoxJSONAutoInstall ); } From e8b92117fa4c5daabb7320137f5dac818f2366ed Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 22:16:21 -0600 Subject: [PATCH 41/48] COMMANDBOX-1551 --- src/cfml/system/util/CFMLExecutor.cfc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/util/CFMLExecutor.cfc b/src/cfml/system/util/CFMLExecutor.cfc index 4d5c5462..f5436065 100644 --- a/src/cfml/system/util/CFMLExecutor.cfc +++ b/src/cfml/system/util/CFMLExecutor.cfc @@ -30,10 +30,13 @@ component { savecontent variable="local.out"{ include "#arguments.template#"; } + if( len( local.out ) ) { + return local.out; + } if( !isNull( variables.__result2 ) ) { return variables.__result2; } - return local.out; + return; } /** From 1da428f0ae3a3fe0a637f547db2fc9f961a0101f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 22:23:51 -0600 Subject: [PATCH 42/48] COMMANDBOX-1393 --- src/cfml/system/services/ServerService.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 10e06458..6e21308e 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -369,7 +369,7 @@ component accessors="true" singleton { // If the server is already running, make sure the user really wants to do this. if( isServerRunning( serverInfo ) && !(serverProps.force ?: false ) && !(serverProps.dryRun ?: false ) ) { - if( !shell.isTerminalInteractive() ) { + if( !shell.isTerminalInteractive() || isSingleServerMode() ) { throw( message="Cannot start server [#serverInfo.name#] because it is already running.", detail="Run [server info --verbose] to find out why CommandBox thinks this server is running.", type="commandException" ); } From decc823faf3b007bd08d997d4142db028c6e3240 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 22:56:47 -0600 Subject: [PATCH 43/48] COMMANDBOX-1034 --- .../modules_app/system-commands/commands/more.cfc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/more.cfc b/src/cfml/system/modules_app/system-commands/commands/more.cfc index 994bd190..0b8028a1 100644 --- a/src/cfml/system/modules_app/system-commands/commands/more.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/more.cfc @@ -9,12 +9,19 @@ component excludeFromHelp=true { /** - * @input.hint The piped input to be displayed. + * @input The piped input to be displayed or a file path to output. + * @input.optionsFileComplete true **/ function run( input='' ) { + + // If the input is a small-ish string with no line breaks, test it to see if it's a file path + if( len( input ) < 1000 && !find( input, chr(10) ) && !find( input, chr(13) ) && fileExists( resolvePath( input ) ) ) { + input = fileRead( resolvePath( input ) ); + } // Get terminal height var termHeight = shell.getTermHeight()-2; // Turn output into an array, breaking on carriage returns + input = input.replace( chr(13) & chr(10), chr(10), 'all' ); var content = listToArray( arguments.input, chr(13) & chr(10), true ); var key= ''; var i = 0; @@ -27,10 +34,10 @@ component excludeFromHelp=true { // pause for user input key = shell.waitForKey(); // If space, advance one line - if( key == 32 ) { + if( key == ' ' ) { StopAtLine++; - // If Ctrl+c, abort, ESC and q - } else if( key == 3 || key == 27 || key == 113 ){ + // If ESC or q + } else if( key == 'escape' || key == 'q' ){ print.redLine( 'Cancelled...' ); return; // Everything else is one page From a4b2304fd192ee949274d48de2c8a79f0ea1a3b5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 2 Mar 2023 23:31:31 -0600 Subject: [PATCH 44/48] COMMANDBOX-1434 Updated formatting for complex settings --- .../system-commands/commands/config/sync/diff.cfc | 8 ++++---- .../system-commands/commands/config/sync/pull.cfc | 8 ++++---- .../system-commands/commands/config/sync/push.cfc | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc index c259ad13..c9c17fbb 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc @@ -51,20 +51,20 @@ component { if( diffDetails.remove.len() ) { print.line().boldBlueLine( 'Remote-only settings:' ); - diffDetails.remove.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.old ) ) ) + diffDetails.remove.each( ( item )=>print.indented( buildPath( item.path ) & ' = ' ).line( item.old ) ) } if( diffDetails.add.len() ) { print.line().boldGreenLine( 'Local-only settings:' ); - diffDetails.add.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.new ) ) ) + diffDetails.add.each( ( item )=>print.indented( buildPath( item.path ) & ' = ' ).line( item.new ) ) } if( diffDetails.change.len() ) { print.line().boldMagentaLine( 'Changed settings:' ); diffDetails.change.each( ( item )=>{ print.indentedLine( buildPath( item.path ) & ' = ' ) - .indentedIndentedBlue( 'Remote Value: ' ).line( serializeJSON( item.old ) ) - .indentedIndentedGreen( 'Local Value: ' ).line(serializeJSON( item.new ) ); + .indentedIndentedBlue( 'Remote Value: ' ).line( item.old ) + .indentedIndentedGreen( 'Local Value: ' ).line( item.new ); } ); } diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc index 4c262c26..c989ce04 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc @@ -58,20 +58,20 @@ component { if( diffDetails.remove.len() ) { print.line().boldGreenLine( 'New incoming settings:' ); - diffDetails.remove.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.old ) ) ) + diffDetails.remove.each( ( item )=>print.indented( buildPath( item.path ) & ' = ' ).line( item.old ) ) } if( diffDetails.add.len() && overwrite ) { print.line().boldRedLine( 'Removed local settings:' ); - diffDetails.add.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.new ) ) ) + diffDetails.add.each( ( item )=>print.indented( buildPath( item.path ) & ' = ' ).line( item.new ) ) } if( diffDetails.change.len() ) { print.line().boldYellowLine( 'Changed settings:' ); diffDetails.change.each( ( item )=>{ print.indentedLine( buildPath( item.path ) & ' = ' ) - .indentedIndentedRed( 'Old Value: ' ).line(serializeJSON( item.new ) ) - .indentedIndentedGreen( 'New Value: ' ).line( serializeJSON( item.old ) ); + .indentedIndentedRed( 'Old Value: ' ).line( item.new ) + .indentedIndentedGreen( 'New Value: ' ).line( item.old ); } ); } diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc index c44a1387..8c646207 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc @@ -60,20 +60,20 @@ component { if( diffDetails.remove.len() && overwrite ) { print.line().boldRedLine( 'Removed remote settings:' ); - diffDetails.remove.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.old ) ) ) + diffDetails.remove.each( ( item )=>print.indented( buildPath( item.path ) & ' = ' ).line( item.old ) ) } if( diffDetails.add.len() ) { print.line().boldGreenLine( 'New remote settings:' ); - diffDetails.add.each( ( item )=>print.indentedLine( buildPath( item.path ) & ' = ' & serializeJSON( item.new ) ) ) + diffDetails.add.each( ( item )=>print.indented( buildPath( item.path ) & ' = ' ).line( item.new ) ) } if( diffDetails.change.len() ) { print.line().boldYellowLine( 'Changed settings:' ); diffDetails.change.each( ( item )=>{ print.indentedLine( buildPath( item.path ) & ' = ' ) - .indentedIndentedRed( 'Old Value: ' ).line(serializeJSON( item.old ) ) - .indentedIndentedGreen( 'New Value: ' ).line( serializeJSON( item.new ) ); + .indentedIndentedRed( 'Old Value: ' ).line( item.old ) + .indentedIndentedGreen( 'New Value: ' ).line( item.new ); } ); } From ee62aad4dd815a0e6c44bebcaf48478abdb7fa72 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 3 Mar 2023 16:00:05 -0600 Subject: [PATCH 45/48] COMMANDBOX-1434 --- .../commands/config/sync/diff.cfc | 18 +++++++++- .../commands/config/sync/pull.cfc | 35 +++++++++++++++++-- .../commands/config/sync/push.cfc | 15 +++++++- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc index c9c17fbb..7db7bc60 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/diff.cfc @@ -8,6 +8,7 @@ **/ component { + property name="packageService" inject="packageService"; property name="ConfigService" inject="ConfigService"; property name="JSONService" inject="JSONService"; property name="endpointService" inject="endpointService"; @@ -39,9 +40,24 @@ component { error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); } } + + var modules = {}; + var directory = expandPath( '/commandbox' ); + // package check + if( packageService.isPackage( directory ) ) { + modules = packageService + .buildDependencyHierarchy( directory, 1 ) + .dependencies + .map( (s,p)=>p.version ); + } + var userData = forgebox.whoami( APIToken ); var remoteConfig = forgebox.getConfig( userData.username, APIToken ); - var configSettings = duplicate( configService.getconfigSettings( noOverrides=true ) ); + var configSettings = { + 'config' : duplicate( ConfigService.getconfigSettings( noOverrides=true ) ), + 'modules' : modules + }; + var diffDetails = jsondiff.diff(remoteConfig, configSettings ) .reduce( ( diffDetails, item )=>{ diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc index c989ce04..9e3f54ee 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/pull.cfc @@ -15,6 +15,7 @@ **/ component { + property name="packageService" inject="packageService"; property name="ConfigService" inject="ConfigService"; property name="JSONService" inject="JSONService"; property name="endpointService" inject="endpointService"; @@ -46,9 +47,23 @@ component { error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); } } + var modules = {}; + var directory = expandPath( '/commandbox' ); + // package check + if( packageService.isPackage( directory ) ) { + modules = packageService + .buildDependencyHierarchy( directory, 1 ) + .dependencies + .map( (s,p)=>p.version ); + } var userData = forgebox.whoami( APIToken ); var remoteConfig = forgebox.getConfig( userData.username, APIToken ); - var configSettings = duplicate( configService.getconfigSettings( noOverrides=true ) ); + var configSettings = { + 'config' : duplicate( ConfigService.getconfigSettings( noOverrides=true ) ), + 'modules' : modules + }; + + var diffDetails = jsondiff.diff(remoteConfig, configSettings ) .reduce( ( diffDetails, item )=>{ @@ -79,8 +94,22 @@ component { remoteConfig = JSONService.mergeData( configSettings, remoteConfig ); } - configService.setConfigSettings( remoteConfig ); - print.line().greenLine( "ClI Settings imported" ); + configService.setConfigSettings( remoteConfig.config ); + print.line().greenLine( "ClI Settings imported" ).line(); + + diffDetails.remove + .filter( ( item )=>buildPath( item.path ).lCase().startsWith( 'modules.' ) ) + .each( ( item )=>command( 'install' ).params( buildPath( item.path ).listRest( '.' ) & '@' & item.old ).flags( 'system' ).run() ) + + diffDetails.change + .filter( ( item )=>buildPath( item.path ).lCase().startsWith( 'modules.' ) ) + .each( ( item )=>command( 'install' ).params( buildPath( item.path ).listRest( '.' ) & '@' & item.old ).flags( 'system' ).run() ) + + if( overwrite ) { + diffDetails.add + .filter( ( item )=>buildPath( item.path ).lCase().startsWith( 'modules.' ) ) + .each( ( item )=>command( 'uninstall' ).params( buildPath( item.path ).listRest( '.' ) ).flags( 'system' ).run() ) + } } catch( forgebox var e ) { // This can include "expected" errors such as "Email already in use" diff --git a/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc b/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc index 8c646207..eb20b15c 100644 --- a/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/config/sync/push.cfc @@ -15,6 +15,7 @@ **/ component { + property name="packageService" inject="packageService"; property name="ConfigService" inject="ConfigService"; property name="JSONService" inject="JSONService"; property name="endpointService" inject="endpointService"; @@ -46,8 +47,20 @@ component { error( 'You don''t have a Forgebox API token set.', 'Use "endpoint login endpointName=#endpointName#" to authenticate as a user.' ); } } + var modules = {}; + var directory = expandPath( '/commandbox' ); + // package check + if( packageService.isPackage( directory ) ) { + modules = packageService + .buildDependencyHierarchy( directory, 1 ) + .dependencies + .map( (s,p)=>p.version ); + } var userData = forgebox.whoami( APIToken ); - var configSettings = duplicate( ConfigService.getconfigSettings( noOverrides=true ) ); + var configSettings = { + 'config' : duplicate( ConfigService.getconfigSettings( noOverrides=true ) ), + 'modules' : modules + }; var remoteConfig = forgebox.getConfig( userData.username, APIToken ); From a72a0f62c9c2e5b301244835367fcc4cb7486994 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 3 Mar 2023 17:06:02 -0600 Subject: [PATCH 46/48] COMMANDBOX-1567 --- .../forgebox-commands/commands/forgebox/use.cfc | 7 +++++-- src/cfml/system/services/ConfigService.cfc | 3 +++ src/cfml/system/services/EndpointService.cfc | 9 ++++++--- src/cfml/system/services/InterceptorService.cfc | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/use.cfc b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/use.cfc index a1156be0..07ff9294 100644 --- a/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/use.cfc +++ b/src/cfml/system/modules_app/forgebox-commands/commands/forgebox/use.cfc @@ -12,8 +12,9 @@ **/ component { - property name="configService" inject="configService"; - property name="endpointService" inject="endpointService"; + property name="configService" inject="configService"; + property name="endpointService" inject="endpointService"; + property name='interceptorService' inject='interceptorService'; /** * @username The ForgeBox username to switch to. @@ -55,6 +56,8 @@ component { } else { error( 'Username [#arguments.username#] isn''t authenticated. Please use "forgebox login".' ); } + + interceptorService.announceInterception( 'onEndpointLogin', { endpointName=endpointName, username=username, endpoint=oEndpoint, APIToken=APIToken } ); } function endpointNameComplete() { diff --git a/src/cfml/system/services/ConfigService.cfc b/src/cfml/system/services/ConfigService.cfc index d8e9c2af..08e7be2c 100644 --- a/src/cfml/system/services/ConfigService.cfc +++ b/src/cfml/system/services/ConfigService.cfc @@ -25,6 +25,7 @@ component accessors="true" singleton { property name='ModuleService' inject='ModuleService'; property name='JSONService' inject='JSONService'; property name='ServerService' inject='provider:ServerService'; + property name='interceptorService' inject='interceptorService'; /** * Constructor @@ -244,6 +245,8 @@ component accessors="true" singleton { // Update ModuleService ModuleService.overrideAllConfigSettings(); + + interceptorService.announceInterception( 'onConfigSettingSave', { configFilePath=getConfigFilePath(), configSettings=getConfigSettings( noOverrides=true ) } ); } diff --git a/src/cfml/system/services/EndpointService.cfc b/src/cfml/system/services/EndpointService.cfc index 2fd847cb..88655db6 100644 --- a/src/cfml/system/services/EndpointService.cfc +++ b/src/cfml/system/services/EndpointService.cfc @@ -16,6 +16,7 @@ component accessors="true" singleton { property name="fileSystemUtil" inject="FileSystem"; property name="consoleLogger" inject="logbox:logger:console"; property name="configService" inject="configService"; + property name='interceptorService' inject='interceptorService'; // Properties @@ -30,7 +31,7 @@ component accessors="true" singleton { setEndpointRegistry( {} ); return this; } - + function onCLIStart() { buildEndpointRegistry(); registerCustomForgeboxEndpoints(); @@ -158,10 +159,10 @@ component accessors="true" singleton { if( endpointName == 'file' || endpointName == 'folder' ) { package = fileSystemUtil.resolvePath( package, arguments.currentWorkingDirectory ); if( endpointName == 'file' && !fileExists( package ) ) { - throw( "The file [ #package# ] does not exist.", 'endpointException' ); + throw( "The file [ #package# ] does not exist.", 'endpointException' ); } if( endpointName == 'folder' && !directoryExists( package ) ) { - throw( "The folder [ #package# ] does not exist.", 'endpointException' ); + throw( "The folder [ #package# ] does not exist.", 'endpointException' ); } theID = endpointName & ':' & package; } @@ -284,6 +285,8 @@ component accessors="true" singleton { // Store the APIToken endpoint.storeAPIToken( arguments.username, APIToken ); + interceptorService.announceInterception( 'onEndpointLogin', { endpointName=endpointName, username=username, endpoint=endpoint, APIToken=APIToken } ); + } /** diff --git a/src/cfml/system/services/InterceptorService.cfc b/src/cfml/system/services/InterceptorService.cfc index d3f62014..dac7bc3d 100644 --- a/src/cfml/system/services/InterceptorService.cfc +++ b/src/cfml/system/services/InterceptorService.cfc @@ -39,7 +39,7 @@ component accessors=true singleton { // Package lifecycle 'preInstall','onInstall','postInstall','preUninstall','postUninstall','preVersion','postVersion','prePublish','postPublish','preUnpublish','postUnpublish','onRelease','preInstallAll','postInstallAll', // Misc - 'onSystemSettingExpansion' + 'onSystemSettingExpansion','onConfigSettingSave','onEndpointLogin' ] ); return this; From 3d97b5c5e3def41d47240b1b21467112a42541fc Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 4 Mar 2023 14:14:35 -0600 Subject: [PATCH 47/48] Update to stable runwar --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 63705c04..0da6631d 100644 --- a/build/build.properties +++ b/build/build.properties @@ -20,7 +20,7 @@ lucee.version=${cfml.version} lucee.config.version=5.2.4.37 jre.version=jdk-11.0.18+10 launch4j.version=3.14 -runwar.version=4.8.3-SNAPSHOT +runwar.version=4.8.3 jline.version=3.21.0 jansi.version=2.3.2 jgit.version=5.13.0.202109080827-r From c96498507c5a1f67e1dedb7387884f223e466337 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 4 Mar 2023 14:15:23 -0600 Subject: [PATCH 48/48] Bump for 5.8.0 stable --- build/build.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.xml b/build/build.xml index aa73a5b4..5fd7c115 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,8 +16,8 @@ External Dependencies: - - + +