From 9d1625ac1de5f77a4cdd77f01569dc2ba448c819 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 12 Aug 2018 00:05:55 -0500 Subject: [PATCH 01/49] bump after 4.2 release --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 5eaac423e..961075735 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,7 +16,7 @@ External Dependencies: - + From a03601a48412ff0c153a58a08bddb22e1c4ec2a0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 12 Aug 2018 17:16:10 -0500 Subject: [PATCH 02/49] Adding a couple docs --- .../modules_app/package-commands/commands/package/list.cfc | 6 ++++++ .../modules_app/system-commands/commands/assertEqual.cfc | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/package-commands/commands/package/list.cfc b/src/cfml/system/modules_app/package-commands/commands/package/list.cfc index 0abd588ff..2c7e25897 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/list.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/list.cfc @@ -13,6 +13,12 @@ * list --verbose * {code} * . + * Limit how many levels deep the list shows. See all top level packages like so: + * . + * {code:bash} + * list depth=1 + * {code} + * . * Get output in JSON format with the --JSON flag. JSON output always includes verbose details * . * {code:bash} diff --git a/src/cfml/system/modules_app/system-commands/commands/assertEqual.cfc b/src/cfml/system/modules_app/system-commands/commands/assertEqual.cfc index 2eecd2750..55d11cb08 100644 --- a/src/cfml/system/modules_app/system-commands/commands/assertEqual.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/assertEqual.cfc @@ -1,6 +1,6 @@ /** * Returns a passing (0) or failing (1) exit code whether both parameters match. Command outputs nothing. - * Comparsion is case insensitive. + * Comparison is case insensitive. * . * {code:bash} * assertEqual `package show name` "My Package" || package set name="My Package" From 4e03b32e2dbcf85b33acec89f457941dbab91fd0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 14 Aug 2018 16:28:05 -0500 Subject: [PATCH 03/49] COMMANDBOX-845 --- src/cfml/system/config/urlrewrite.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/config/urlrewrite.xml b/src/cfml/system/config/urlrewrite.xml index d773fcc9d..9f25a0627 100644 --- a/src/cfml/system/config/urlrewrite.xml +++ b/src/cfml/system/config/urlrewrite.xml @@ -4,7 +4,7 @@ Generic Front-Controller URLs - ^/(flex2gateway|flashservices/gateway|messagebroker|lucee|rest|cfide|CFIDE|cfformgateway|jrunscripts|cf_scripts)/.* + ^/(flex2gateway|flashservices/gateway|messagebroker|lucee|rest|cfide|CFIDE|cfformgateway|jrunscripts|cf_scripts|CFFileServlet)/.* ^/tuckey-status From 4e15aa37bf874f64b471c9d7c333cb5bb9bdc6dd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 17 Aug 2018 21:39:04 -0500 Subject: [PATCH 04/49] COMMANDBOX-846 --- src/cfml/system/Shell.cfc | 24 +++++++++++++++++---- src/cfml/system/services/CommandService.cfc | 5 +++++ src/cfml/system/util/ProgressBarGeneric.cfc | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index aa2fd2e03..158bdf28d 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -540,7 +540,17 @@ component accessors="true" singleton { } variables.keepRunning = false; - continue; + continue; + + // Catch all for custom user interrupt thrown from CFML + } catch( any var e ) { + + if( e.type == 'UserInterruptException' ) { + continue; + } else { + rethrow; + } + } // If the standard input isn't avilable, bail. This happens @@ -720,6 +730,7 @@ component accessors="true" singleton { boolean initialCommand=false ) { var job = wirebox.getInstance( 'interactiveJob' ); + var progressBarGeneric = wirebox.getInstance( 'progressBarGeneric' ); // Commands a loaded async in interactive mode, so this is a failsafe to ensure the CommandService // is finished. Especially useful for commands run onCLIStart. Wait up to 5 seconds. @@ -751,6 +762,7 @@ component accessors="true" singleton { rethrow; } else { + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining(); } @@ -763,6 +775,8 @@ component accessors="true" singleton { if( !initialCommand ) { rethrow; } else { + + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining(); } @@ -777,8 +791,9 @@ component accessors="true" singleton { if( !initialCommand ) { rethrow; // This type of error means the user hit Ctrl-C, when not in a readLine() call (and hit my custom signal handler). Duck out and move along. - } else if( e.getPageException().getRootCause().getClass().getName() == 'java.lang.InterruptedException' ) { - + } else if( e.getPageException().getRootCause().getClass().getName() == 'java.lang.InterruptedException' || e.type == 'UserInterruptException' || e.message == 'UserInterruptException' ) { + + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining(); } @@ -788,7 +803,8 @@ component accessors="true" singleton { variables.reader.getTerminal().writer().print( variables.print.boldRedLine( 'CANCELLED' ) ); // Anything else is completely unexpected and means boom booms happened-- full stack please. } else { - + + progressBarGeneric.clear(); if( job.isActive() ) { job.errorRemaining( e.message ); variables.reader.getTerminal().writer().println(); diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index 0eb7a04af..73df8561c 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -334,6 +334,11 @@ component accessors="true" singleton { lastCommandErrored = commandInfo.commandReference.CFC.hasError(); } catch( any e ){ lastCommandErrored = true; + // If this command didn't already set a failing exit code... + if( commandInfo.commandReference.CFC.getExitCode() == 0 ) { + // Go ahead and set one for it. The shell will inherit it below in the finally block. + commandInfo.commandReference.CFC.setExitCode( 1 ); + } // Dump out anything the command had printed so far var result = commandInfo.commandReference.CFC.getResult(); diff --git a/src/cfml/system/util/ProgressBarGeneric.cfc b/src/cfml/system/util/ProgressBarGeneric.cfc index 1c82d08c1..90a8e73dc 100644 --- a/src/cfml/system/util/ProgressBarGeneric.cfc +++ b/src/cfml/system/util/ProgressBarGeneric.cfc @@ -31,7 +31,7 @@ component singleton { var terminal = shell.getReader().getTerminal(); - // If Jline uses a "dumb" terminal, the width reports as zero, which throws devide by zero errors. + // If Jline uses a "dumb" terminal, the width reports as zero, which throws divide by zero errors. // TODO: I might be able to just fake a reasonable width. if( !shell.isTerminalInteractive() || terminal.getWidth() == 0 ) { return; From 2d3c07bec4ba53dbee5ce89c1191fc903dc8c374 Mon Sep 17 00:00:00 2001 From: Andrew Davis <35044908+blusol850@users.noreply.github.com> Date: Sun, 19 Aug 2018 19:02:42 -0400 Subject: [PATCH 05/49] typo setNextEvetn --- .../coldbox-commands/templates/crud/HandlerContent.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/crud/HandlerContent.txt b/src/cfml/system/modules_app/coldbox-commands/templates/crud/HandlerContent.txt index 252724e15..4d08bd2e9 100644 --- a/src/cfml/system/modules_app/coldbox-commands/templates/crud/HandlerContent.txt +++ b/src/cfml/system/modules_app/coldbox-commands/templates/crud/HandlerContent.txt @@ -92,7 +92,7 @@ component extends="coldbox.system.EventHandler"{ default:{ // Show a nice notice flash.put( "notice", { message="|entity| Created", type="success" } ); - // Redirect to listing: change to `setNextEvetn()` if using ColdBox <5 + // Redirect to listing: change to `setNextEvent()` if using ColdBox <5 relocate( '|entityPlural|' ); } } @@ -117,10 +117,10 @@ component extends="coldbox.system.EventHandler"{ default:{ // Show a nice notice flash.put( "notice", { message="|entity| Poofed!", type="success" } ); - // Redirect to listing: change to `setNextEvetn()` if using ColdBox <5 + // Redirect to listing: change to `setNextEvent()` if using ColdBox <5 relocate( '|entityPlural|' ); } } } -} \ No newline at end of file +} From 0a8f98e9ef366aa0673b44b7bf3c59ad2626807e Mon Sep 17 00:00:00 2001 From: John Berquist Date: Tue, 21 Aug 2018 10:17:00 -0700 Subject: [PATCH 06/49] Extract S3 signing to a service for reusability (#163) * Extract S3 signing to service for reusability * presignedPath now includes 'https:' --- src/cfml/system/endpoints/S3.cfc | 257 +-------------------- src/cfml/system/services/S3Service.cfc | 299 +++++++++++++++++++++++++ 2 files changed, 303 insertions(+), 253 deletions(-) create mode 100644 src/cfml/system/services/S3Service.cfc diff --git a/src/cfml/system/endpoints/S3.cfc b/src/cfml/system/endpoints/S3.cfc index 7ec706552..cfeaf36ca 100644 --- a/src/cfml/system/endpoints/S3.cfc +++ b/src/cfml/system/endpoints/S3.cfc @@ -10,48 +10,20 @@ component accessors="true" implements="IEndpoint" singleton { // DI - property name="configService" inject="configService"; - property name="systemSettings" inject="SystemSettings"; - property name="fileSystemUtil" inject="FileSystem"; - property name="httpsEndpoint" inject="commandbox.system.endpoints.HTTPS"; - property name='wirebox' inject='wirebox'; + property name="httpsEndpoint" inject="commandbox.system.endpoints.HTTPS"; + property name="S3Service" inject="S3Service"; // Properties property name="namePrefixes" type="string"; - property name="iamRolePath" type="string"; function init() { setNamePrefixes('s3'); - setIamRolePath('169.254.169.254/latest/meta-data/iam/security-credentials/'); return this; } public string function resolvePackage(required string package, boolean verbose=false) { - var bucket = package.listFirst('/').listFirst(':'); - var encodedRegion = package.listFirst('/').listRest(':'); - var objectKey = package.listRest('/'); - var awsSettings = resolveAwsSettings(bucket, verbose); - var bucketRegion = len(encodedRegion) ? encodedRegion : resolveBucketRegion(bucket, awsSettings.defaultRegion, verbose); - - // if objectKey does not end in `.zip` we have to do a HEAD request to see if the path is valid as is - // or if .zip should be appended to it - this is for backward compat with https://github.com/pixl8/s3-commandbox-commands - if (!objectKey.endsWith('.zip')) { - var job = wirebox.getInstance('interactiveJob'); - if (verbose) { - job.addLog('Validating object key since it does not have a .zip extension'); - } - var presignedPath = generatePresignedPath('HEAD', bucket, objectKey, bucketRegion, awsSettings.credentials); - var req = makeHTTPRequest('https:#presignedPath#', 'HEAD'); - if (req.status_code == 404) { - if (verbose) { - job.addLog('Object key does not exist on S3, appending .zip extension'); - } - objectKey &= '.zip'; - } - } - - var presignedPath = generatePresignedPath('GET', bucket, objectKey, bucketRegion, awsSettings.credentials); - return httpsEndpoint.resolvePackage(presignedPath, verbose); + var presignedPath = s3Service.generateSignedURL('s3:' & package, verbose); + return httpsEndpoint.resolvePackage(presignedPath.listRest(':'), verbose); } public function getDefaultName(required string package) { @@ -62,225 +34,4 @@ component accessors="true" implements="IEndpoint" singleton { return httpsEndpoint.getUpdate(argumentCollection = arguments); } - private function generatePresignedPath(method, bucket, objectKey, bucketRegion, credentials) { - var isoTime = iso8601(); - var host = 's3.#bucketRegion#.amazonaws.com'; - var path = encodeUrl('/#bucket#/#objectKey#', false); - - // query string - var qs = { - 'X-Amz-Algorithm': 'AWS4-HMAC-SHA256', - 'X-Amz-Credential': credentials.awsKey & '/' & isoTime.left( 8 ) & '/' & bucketRegion & '/s3/aws4_request', - 'X-Amz-Date': isoTime, - 'X-Amz-Expires': 300, - 'X-Amz-SignedHeaders': 'host' - }; - if (credentials.sessionToken.len()) { - qs['X-Amz-Security-Token'] = credentials.sessionToken; - } - qs = qs.keyArray() - .sort('text') - .reduce((r, key) => r.listAppend('#key#=#encodeUrl(qs[key])#', '&'), ''); - - // canonical request - var canonicalRequest = [ - method.uCase(), - path, - qs, - 'host:#host#', - '', - 'host', - 'UNSIGNED-PAYLOAD' - ].toList(chr(10)); - - // string to sign - var stringtoSign = [ - 'AWS4-HMAC-SHA256', - isoTime, - isoTime.left( 8 ) & '/' & bucketRegion & '/s3/aws4_request', - hash(canonicalRequest, 'SHA-256').lcase() - ].toList(chr(10)); - - // signature - var signingKey = binaryDecode(hmac(isoTime.left(8), 'AWS4' & credentials.awsSecretKey, 'hmacSHA256', 'utf-8'), 'hex'); - signingKey = binaryDecode(hmac(bucketRegion, signingKey, 'hmacSHA256', 'utf-8'), 'hex'); - signingKey = binaryDecode(hmac('s3', signingKey, 'hmacSHA256', 'utf-8'), 'hex'); - signingKey = binaryDecode(hmac('aws4_request', signingKey, 'hmacSHA256', 'utf-8'), 'hex'); - var signature = hmac(stringToSign, signingKey, 'hmacSHA256', 'utf-8').lcase(); - - qs &= '&X-Amz-Signature=' & signature; - return '//' & host & path & '?' & qs; - } - - private function resolveBucketRegion(bucket, defaultRegion, verbose=false) { - if (verbose) { - var job = wirebox.getInstance( 'interactiveJob' ); - job.addLog('Resolving bucket region'); - } - - var args = { - urlPath: 'https://s3.#defaultRegion#.amazonaws.com', - method: 'HEAD', - redirect: false, - headers: { - 'Host': '#bucket#.s3.amazonaws.com' - } - }; - var req = makeHTTPRequest(argumentCollection = args); - return req.responseheader['x-amz-bucket-region']; - } - - private function resolveAwsSettings(bucket, verbose=false) { - var endpointSettings = configService.getSetting('endpoint.s3', {}); - - var settings = { - defaultRegion: endpointSettings.aws_default_region ?: systemSettings.getSystemSetting('AWS_DEFAULT_REGION', ''), - profile: endpointSettings.aws_profile ?: systemSettings.getSystemSetting('AWS_PROFILE', 'default'), - configFile: endpointSettings.aws_config_file ?: systemSettings.getSystemSetting('AWS_CONFIG_FILE', '~/.aws/config'), - credentialsFile: endpointSettings.aws_shared_credentials_file ?: systemSettings.getSystemSetting('AWS_SHARED_CREDENTIALS_FILE', '~/.aws/credentials') - }; - - if (!settings.defaultRegion.len()) { - var configFilePath = fileSystemUtil.resolvePath(settings.configFile); - var region = getProfileString(configFilePath, settings.profile, 'region'); - settings.defaultRegion = len(region) ? region : 'us-east-1'; - } - - var endpointSettingsPerBucket = endpointSettings.keyExists(bucket) && isStruct(endpointSettings[bucket]) ? endpointSettings[bucket] : endpointSettings; - settings.credentials = resolveCredentials(endpointSettingsPerBucket, settings.credentialsFile, settings.profile, verbose); - - return settings; - } - - private function resolveCredentials(endpointSettings, credentialsFile, profile, verbose=false) { - var job = wirebox.getInstance( 'interactiveJob' ); - - // check CommandBox endpoint settings for AWS credentials - if (verbose) { - job.addLog('Checking for AWS credentials in CommandBox settings: `endpoint.s3`') - } - var credentials = { - awsKey: endpointSettings.aws_access_key_id ?: '', - awsSecretKey: endpointSettings.aws_secret_access_key ?: '', - sessionToken: endpointSettings.aws_session_token ?: '' - } - if (len(credentials.awsKey) && len(credentials.awsSecretKey)) { - if (verbose) { - job.addSuccessLog('AWS Credentials found in endpoint settings') - } - return credentials; - } - - // check for AWS credentials in environment - if (verbose) { - job.addLog('Checking for AWS credentials in environment variables and Java system properties') - } - var credentials = { - awsKey: systemSettings.getSystemSetting('AWS_ACCESS_KEY_ID', ''), - awsSecretKey: systemSettings.getSystemSetting('AWS_SECRET_ACCESS_KEY', ''), - sessionToken: systemSettings.getSystemSetting('AWS_SESSION_TOKEN', ''), - }; - if (len(credentials.awsKey) && len(credentials.awsSecretKey)) { - if (verbose) { - job.addSuccessLog('AWS Credentials found in environment variables') - } - return credentials; - } - - - // check for an AWS credentials file for current user - if (verbose) { - job.addLog('Checking for AWS credentials in #credentialsFile#') - } - var credentialsFilePath = fileSystemUtil.resolvePath(credentialsFile); - var credentials = { - awsKey: getProfileString(credentialsFilePath, profile, 'aws_access_key_id'), - awsSecretKey: getProfileString(credentialsFilePath, profile, 'aws_secret_access_key'), - sessionToken: getProfileString(credentialsFilePath, profile, 'aws_session_token') - }; - if (len(credentials.awsKey) && len(credentials.awsSecretKey)) { - if (verbose) { - job.addSuccessLog('AWS Credentials found in #credentialsFile#') - } - return credentials; - } - - // check for IAM role - if (verbose) { - job.addLog('Checking for AWS credentials via IAM Role') - } - try { - var roleName = makeHTTPRequest(urlPath=getIamRolePath(), timeout=1, allowProxy=false).filecontent; - var req = makeHTTPRequest(urlPath=getIamRolePath() & roleName, timeout=1, allowProxy=false); - var data = deserializeJSON( req.filecontent ); - var credentials = { - awsKey: data.AccessKeyId, - awsSecretKey: data.SecretAccessKey, - sessionToken: data.Token, - expires: parseDateTime(data.Expiration) - }; - if (verbose) { - job.addSuccessLog('AWS Credentials found via IAM Role: #roleName#') - } - return credentials; - } catch(any e) { - // pass - } - - // Credentials unable to be located - var errorMessage = ' - Could not locate S3 Credentials. Credentials can be set in CommandBox settings under the key `endpoint.s3` - or by following one of the configuration methods used for configuring the AWS CLI. - See https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html and in particular - "Configuration Settings and Precedence". - '.trim(); - throw( - errorMessage, - 'endpointException' - ); - } - - private function iso8601(dateToFormat = now()) { - return dateTimeFormat(dateToFormat, 'yyyymmdd', 'UTC') & 'T' & dateTimeFormat(dateToFormat, 'HHnnss', 'UTC') & 'Z'; - } - - private function encodeUrl(urlPath, encodeForwardSlash = true) { - var result = replacelist(urlEncodedFormat(urlPath, 'utf-8'), '%2D,%2E,%5F,%7E', '-,.,_,~'); - if (!encodeForwardSlash) { - result = result.replace('%2F', '/', 'all'); - } - return result; - } - - private function makeHTTPRequest(urlPath, method='GET', redirect=true, timeout=20, headers={}, allowProxy=true) { - var req = ''; - var attributeCol = { - url: urlPath, - method: method, - timeout: timeout, - redirect: redirect, - result = 'req' - }; - - if (allowProxy) { - var proxy = configService.getSetting('proxy', {}); - if (proxy.keyExists('server') && len(proxy.server)) { - attributeCol.proxyServer = proxy.server; - for (var key in ['port', 'user', 'password'] ) { - if (proxy.keyExists(key) && len(proxy[key])) { - attributeCol['proxy#key#'] = proxy[key]; - } - } - } - } - - cfhttp(attributeCollection = attributeCol) { - for (var key in headers) { - cfhttpparam(type='header', name=key, value=headers[key]); - } - } - - return req; - } - } diff --git a/src/cfml/system/services/S3Service.cfc b/src/cfml/system/services/S3Service.cfc new file mode 100644 index 000000000..04ed94d2e --- /dev/null +++ b/src/cfml/system/services/S3Service.cfc @@ -0,0 +1,299 @@ +/** +********************************************************************************* +* Copyright Since 2014 CommandBox by Ortus Solutions, Corp +* www.coldbox.org | www.ortussolutions.com +******************************************************************************** +* @author Brad Wood, Luis Majano, Denny Valliant +* +* I convert s3 paths (s3://bucket/path) to signed HTTPS urls +* +*/ +component accessors="true" singleton { + + property name="configService" inject="configService"; + property name="systemSettings" inject="SystemSettings"; + property name="fileSystemUtil" inject="FileSystem"; + property name='wirebox' inject='wirebox'; + + /** + * Local URL to query for IAM role + */ + property name="iamRolePath" type="string"; + + /** + * Constructor + */ + function init( + ){ + setIamRolePath('169.254.169.254/latest/meta-data/iam/security-credentials/'); + return this; + } + + function onDIComplete() { + } + + /** + * Generate a signed HTTPS URL from a S3 path. + * + * @s3Path.hint Path to S3 object specified in the format s3://bucket/path/to/object + * @verbose.hint Set to true to log additional information + **/ + public string function generateSignedURL( + required string s3Path, + boolean verbose = false + ){ + var job = wirebox.getInstance('interactiveJob'); + + // strip s3 prefix from path, if present + if (s3Path.left(5) == 's3://') { + s3Path = s3Path.right(s3Path.len() - 5); + } + + var bucket = s3Path.listFirst('/').listFirst(':'); + var encodedRegion = s3Path.listFirst('/').listRest(':'); + var objectKey = s3Path.listRest('/'); + var awsSettings = resolveAwsSettings(bucket, verbose); + var bucketRegion = len(encodedRegion) ? encodedRegion : resolveBucketRegion(bucket, awsSettings.defaultRegion, verbose); + + // if objectKey does not end in `.zip` we have to do a HEAD request to see if the path is valid as is + // or if .zip should be appended to it - this is for backward compat with https://github.com/pixl8/s3-commandbox-commands + if (!objectKey.endsWith('.zip')) { + var job = wirebox.getInstance('interactiveJob'); + if (verbose) { + job.addLog('Validating object key since it does not have a .zip extension'); + } + var presignedPath = generatePresignedPath('HEAD', bucket, objectKey, bucketRegion, awsSettings.credentials); + var req = makeHTTPRequest(presignedPath, 'HEAD'); + if (req.status_code == 404) { + if (verbose) { + job.addLog('Object key does not exist on S3, appending .zip extension'); + } + objectKey &= '.zip'; + } + } + + return generatePresignedPath('GET', bucket, objectKey, bucketRegion, awsSettings.credentials); + } + + private function generatePresignedPath(method, bucket, objectKey, bucketRegion, credentials) { + var isoTime = iso8601(); + var host = 's3.#bucketRegion#.amazonaws.com'; + var path = encodeUrl('/#bucket#/#objectKey#', false); + + // query string + var qs = { + 'X-Amz-Algorithm': 'AWS4-HMAC-SHA256', + 'X-Amz-Credential': credentials.awsKey & '/' & isoTime.left(8) & '/' & bucketRegion & '/s3/aws4_request', + 'X-Amz-Date': isoTime, + 'X-Amz-Expires': 300, + 'X-Amz-SignedHeaders': 'host' + }; + if (credentials.sessionToken.len()) { + qs['X-Amz-Security-Token'] = credentials.sessionToken; + } + qs = qs.keyArray() + .sort('text') + .reduce((r, key) => r.listAppend('#key#=#encodeUrl(qs[key])#', '&'), ''); + + // canonical request + var canonicalRequest = [ + method.uCase(), + path, + qs, + 'host:#host#', + '', + 'host', + 'UNSIGNED-PAYLOAD' + ].toList(chr(10)); + + // string to sign + var stringtoSign = [ + 'AWS4-HMAC-SHA256', + isoTime, + isoTime.left(8) & '/' & bucketRegion & '/s3/aws4_request', + hash(canonicalRequest, 'SHA-256').lcase() + ].toList(chr(10)); + + // signature + var signingKey = binaryDecode(hmac(isoTime.left(8), 'AWS4' & credentials.awsSecretKey, 'hmacSHA256', 'utf-8'), 'hex'); + signingKey = binaryDecode(hmac(bucketRegion, signingKey, 'hmacSHA256', 'utf-8'), 'hex'); + signingKey = binaryDecode(hmac('s3', signingKey, 'hmacSHA256', 'utf-8'), 'hex'); + signingKey = binaryDecode(hmac('aws4_request', signingKey, 'hmacSHA256', 'utf-8'), 'hex'); + var signature = hmac(stringToSign, signingKey, 'hmacSHA256', 'utf-8').lcase(); + + qs &= '&X-Amz-Signature=' & signature; + return 'https://' & host & path & '?' & qs; + } + + private function resolveBucketRegion(bucket, defaultRegion, verbose=false) { + if (verbose) { + var job = wirebox.getInstance('interactiveJob'); + job.addLog('Resolving bucket region'); + } + + var args = { + urlPath: 'https://s3.#defaultRegion#.amazonaws.com', + method: 'HEAD', + redirect: false, + headers: { + 'Host': '#bucket#.s3.amazonaws.com' + } + }; + var req = makeHTTPRequest(argumentCollection = args); + return req.responseheader['x-amz-bucket-region']; + } + + private function resolveAwsSettings(bucket, verbose=false) { + var endpointSettings = configService.getSetting('endpoint.s3', {}); + + var settings = { + defaultRegion: endpointSettings.aws_default_region ?: systemSettings.getSystemSetting('AWS_DEFAULT_REGION', ''), + profile: endpointSettings.aws_profile ?: systemSettings.getSystemSetting('AWS_PROFILE', 'default'), + configFile: endpointSettings.aws_config_file ?: systemSettings.getSystemSetting('AWS_CONFIG_FILE', '~/.aws/config'), + credentialsFile: endpointSettings.aws_shared_credentials_file ?: systemSettings.getSystemSetting('AWS_SHARED_CREDENTIALS_FILE', '~/.aws/credentials') + }; + + if (!settings.defaultRegion.len()) { + var configFilePath = fileSystemUtil.resolvePath(settings.configFile); + var region = getProfileString(configFilePath, settings.profile, 'region'); + settings.defaultRegion = len(region) ? region : 'us-east-1'; + } + + var endpointSettingsPerBucket = endpointSettings.keyExists(bucket) && isStruct(endpointSettings[bucket]) ? endpointSettings[bucket] : endpointSettings; + settings.credentials = resolveCredentials(endpointSettingsPerBucket, settings.credentialsFile, settings.profile, verbose); + + return settings; + } + + private function resolveCredentials(endpointSettings, credentialsFile, profile, verbose=false) { + var job = wirebox.getInstance('interactiveJob'); + + // check CommandBox endpoint settings for AWS credentials + if (verbose) { + job.addLog('Checking for AWS credentials in CommandBox settings: `endpoint.s3`') + } + var credentials = { + awsKey: endpointSettings.aws_access_key_id ?: '', + awsSecretKey: endpointSettings.aws_secret_access_key ?: '', + sessionToken: endpointSettings.aws_session_token ?: '' + } + if (len(credentials.awsKey) && len(credentials.awsSecretKey)) { + if (verbose) { + job.addSuccessLog('AWS Credentials found in endpoint settings') + } + return credentials; + } + + // check for AWS credentials in environment + if (verbose) { + job.addLog('Checking for AWS credentials in environment variables and Java system properties') + } + var credentials = { + awsKey: systemSettings.getSystemSetting('AWS_ACCESS_KEY_ID', ''), + awsSecretKey: systemSettings.getSystemSetting('AWS_SECRET_ACCESS_KEY', ''), + sessionToken: systemSettings.getSystemSetting('AWS_SESSION_TOKEN', ''), + }; + if (len(credentials.awsKey) && len(credentials.awsSecretKey)) { + if (verbose) { + job.addSuccessLog('AWS Credentials found in environment variables') + } + return credentials; + } + + + // check for an AWS credentials file for current user + if (verbose) { + job.addLog('Checking for AWS credentials in #credentialsFile#') + } + var credentialsFilePath = fileSystemUtil.resolvePath(credentialsFile); + var credentials = { + awsKey: getProfileString(credentialsFilePath, profile, 'aws_access_key_id'), + awsSecretKey: getProfileString(credentialsFilePath, profile, 'aws_secret_access_key'), + sessionToken: getProfileString(credentialsFilePath, profile, 'aws_session_token') + }; + if (len(credentials.awsKey) && len(credentials.awsSecretKey)) { + if (verbose) { + job.addSuccessLog('AWS Credentials found in #credentialsFile#') + } + return credentials; + } + + // check for IAM role + if (verbose) { + job.addLog('Checking for AWS credentials via IAM Role') + } + try { + var roleName = makeHTTPRequest(urlPath=getIamRolePath(), timeout=1, allowProxy=false).filecontent; + var req = makeHTTPRequest(urlPath=getIamRolePath() & roleName, timeout=1, allowProxy=false); + var data = deserializeJSON(req.filecontent); + var credentials = { + awsKey: data.AccessKeyId, + awsSecretKey: data.SecretAccessKey, + sessionToken: data.Token, + expires: parseDateTime(data.Expiration) + }; + if (verbose) { + job.addSuccessLog('AWS Credentials found via IAM Role: #roleName#') + } + return credentials; + } catch(any e) { + // pass + } + + // Credentials unable to be located + var errorMessage = ' + Could not locate S3 Credentials. Credentials can be set in CommandBox settings under the key `endpoint.s3` + or by following one of the configuration methods used for configuring the AWS CLI. + See https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html and in particular + "Configuration Settings and Precedence". + '.trim(); + throw( + errorMessage, + 'S3Exception' + ); + } + + private function iso8601(dateToFormat = now()) { + return dateTimeFormat(dateToFormat, 'yyyymmdd', 'UTC') & 'T' & dateTimeFormat(dateToFormat, 'HHnnss', 'UTC') & 'Z'; + } + + private function encodeUrl(urlPath, encodeForwardSlash = true) { + var result = replacelist(urlEncodedFormat(urlPath, 'utf-8'), '%2D,%2E,%5F,%7E', '-,.,_,~'); + if (!encodeForwardSlash) { + result = result.replace('%2F', '/', 'all'); + } + return result; + } + + private function makeHTTPRequest(urlPath, method='GET', redirect=true, timeout=20, headers={}, allowProxy=true) { + var req = ''; + var attributeCol = { + url: urlPath, + method: method, + timeout: timeout, + redirect: redirect, + result: 'req' + }; + + if (allowProxy) { + var proxy = configService.getSetting('proxy', {}); + if (proxy.keyExists('server') && len(proxy.server)) { + attributeCol.proxyServer = proxy.server; + for (var key in ['port', 'user', 'password'] ) { + if (proxy.keyExists(key) && len(proxy[key])) { + attributeCol['proxy#key#'] = proxy[key]; + } + } + } + } + + cfhttp(attributeCollection = attributeCol) { + for (var key in headers) { + cfhttpparam(type='header', name=key, value=headers[key]); + } + } + + return req; + } + +} From 03735542063f1927d22d1e5fa6042e83b15c3d8d Mon Sep 17 00:00:00 2001 From: John Berquist Date: Wed, 29 Aug 2018 16:16:15 -0700 Subject: [PATCH 07/49] Allow installing Java jars from S3 (#165) --- src/cfml/system/endpoints/Jar.cfc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/endpoints/Jar.cfc b/src/cfml/system/endpoints/Jar.cfc index 41079d505..aee46a6c7 100644 --- a/src/cfml/system/endpoints/Jar.cfc +++ b/src/cfml/system/endpoints/Jar.cfc @@ -18,6 +18,7 @@ component accessors=true implements="IEndpoint" singleton { property name="CR" inject="CR@constants"; property name='formatterUtil' inject='formatter'; property name='wirebox' inject='wirebox'; + property name='S3Service' inject='S3Service'; // Properties property name="namePrefixes" type="string"; @@ -36,10 +37,12 @@ component accessors=true implements="IEndpoint" singleton { job.addLog( "Downloading [#package#]" ); + var packageUrl = package.startsWith('s3://') ? S3Service.generateSignedURL(package, verbose) : package; + try { // Download File var result = progressableDownloader.download( - package, // URL to package + packageUrl, // URL to package fullJarPath, // Place to store it locally function( status ) { progressBar.update( argumentCollection = status ); From 14ef99cb800096aaebf34fd3f3178a4b4705f341 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Thu, 30 Aug 2018 18:52:53 -0700 Subject: [PATCH 08/49] S3 endpoint output (#166) * Download directly in S3 endpoint Cleans up logged download path. * Removed extra line This line is a copy of the first line in the method. * Whitespace consistency --- src/cfml/system/endpoints/S3.cfc | 43 +++++++++++++++++++++++--- src/cfml/system/services/S3Service.cfc | 1 - 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/endpoints/S3.cfc b/src/cfml/system/endpoints/S3.cfc index cfeaf36ca..fc5262e5a 100644 --- a/src/cfml/system/endpoints/S3.cfc +++ b/src/cfml/system/endpoints/S3.cfc @@ -10,8 +10,14 @@ component accessors="true" implements="IEndpoint" singleton { // DI - property name="httpsEndpoint" inject="commandbox.system.endpoints.HTTPS"; - property name="S3Service" inject="S3Service"; + property name="CR" inject="CR@constants"; + property name="fileEndpoint" inject="commandbox.system.endpoints.File"; + property name="httpsEndpoint" inject="commandbox.system.endpoints.HTTPS"; + property name="progressableDownloader" inject="ProgressableDownloader"; + property name="progressBar" inject="ProgressBar"; + property name="S3Service" inject="S3Service"; + property name="tempDir" inject="tempDir@constants"; + property name='wirebox' inject='wirebox'; // Properties property name="namePrefixes" type="string"; @@ -22,8 +28,37 @@ component accessors="true" implements="IEndpoint" singleton { } public string function resolvePackage(required string package, boolean verbose=false) { - var presignedPath = s3Service.generateSignedURL('s3:' & package, verbose); - return httpsEndpoint.resolvePackage(presignedPath.listRest(':'), verbose); + var job = wirebox.getInstance('interactiveJob'); + + var fileName = 'temp#randRange( 1, 1000 )#.zip'; + var fullPath = tempDir & '/' & fileName; + + job.addLog('Downloading [s3:#package#]'); + + try { + // Download File + var presignedPath = s3Service.generateSignedURL('s3:' & package, verbose); + if (verbose) { + job.addLog('Signed URL: ' & presignedPath); + } + var result = progressableDownloader.download( + presignedPath, // URL to package + fullPath, // Place to store it locally + function(status) { + progressBar.update(argumentCollection = status); + }, + function(newURL) { + job.addLog("Redirecting to: '#arguments.newURL#'..."); + } + ); + } catch(UserInterruptException var e) { + rethrow; + } catch(Any var e) { + throw('#e.message##CR##e.detail#', 'endpointException'); + }; + + // Defer to file endpoint + return fileEndpoint.resolvePackage(fullPath, arguments.verbose); } public function getDefaultName(required string package) { diff --git a/src/cfml/system/services/S3Service.cfc b/src/cfml/system/services/S3Service.cfc index 04ed94d2e..073136452 100644 --- a/src/cfml/system/services/S3Service.cfc +++ b/src/cfml/system/services/S3Service.cfc @@ -58,7 +58,6 @@ component accessors="true" singleton { // if objectKey does not end in `.zip` we have to do a HEAD request to see if the path is valid as is // or if .zip should be appended to it - this is for backward compat with https://github.com/pixl8/s3-commandbox-commands if (!objectKey.endsWith('.zip')) { - var job = wirebox.getInstance('interactiveJob'); if (verbose) { job.addLog('Validating object key since it does not have a .zip extension'); } From 90cad61883775dd836ad2386b8b5861769d68b61 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 31 Aug 2018 12:21:00 -0700 Subject: [PATCH 09/49] COMMANDBOX-852 --- src/cfml/system/services/ServerService.cfc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index 8e2c66066..dc6eb1423 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -138,6 +138,7 @@ component accessors="true" singleton { // Duplicate so onServerStart interceptors don't actually change config settings via reference. 'errorPages' : duplicate( d.web.errorPages ?: {} ), 'accessLogEnable' : d.web.accessLogEnable ?: false, + 'GZIPEnable' : d.web.GZIPEnable ?: true, 'welcomeFiles' : d.web.welcomeFiles ?: '', 'HTTP' : { 'port' : d.web.http.port ?: 0, @@ -658,7 +659,9 @@ component accessors="true" singleton { // trayOptions aren't accepted via command params due to no clean way to provide them serverInfo.trayOptions.append( serverJSON.trayOptions, true ); - serverInfo.accessLogEnable = serverJSON.web.accessLogEnable ?: defaults.web.accessLogEnable; + serverInfo.accessLogEnable = serverJSON.web.accessLogEnable ?: defaults.web.accessLogEnable; + serverInfo.GZIPEnable = serverJSON.web.GZIPEnable ?: defaults.web.GZIPEnable; + serverInfo.rewriteslogEnable = serverJSON.web.rewrites.logEnable ?: defaults.web.rewrites.logEnable; // Global defauls are always added on top of whatever is specified by the user or server.json @@ -1002,12 +1005,18 @@ component accessors="true" singleton { args.append( '--error-pages' ).append( errorPages ); } + if( serverInfo.GZIPEnable ) { + args + .append( '--gzip-enable' ).append( true ) + } + if( serverInfo.accesslogenable ) { args .append( '--logaccess-enable' ).append( true ) .append( '--logaccess-basename' ).append( 'access' ) .append( '--logaccess-dir' ).append( serverInfo.logDir ); } + if( serverInfo.rewritesLogEnable ) { args.append( '--urlrewrite-log' ).append( serverInfo.rewritesLogPath ); @@ -1884,6 +1893,7 @@ component accessors="true" singleton { 'aliases' : {}, 'errorPages' : {}, 'accessLogEnable' : false, + 'GZIPEnable' : true, 'rewritesLogEnable' : false, 'trayOptions' : {}, 'trayEnable' : true, From 04ac663c9e2a190c1c98c97e4cfb3b14c3cef0b8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 4 Sep 2018 17:57:16 -0500 Subject: [PATCH 10/49] COMMANDBOX-856 --- src/cfml/system/config/LogBox.cfc | 3 +-- .../system/modules_app/task-commands/models/TaskService.cfc | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/config/LogBox.cfc b/src/cfml/system/config/LogBox.cfc index c779c50c6..55694bc7c 100644 --- a/src/cfml/system/config/LogBox.cfc +++ b/src/cfml/system/config/LogBox.cfc @@ -19,8 +19,7 @@ component { fileMaxArchives = 5, filename = "commandbox", filepath = expandpath( '/commandbox-home' ) & "/logs", - autoExpand=false, - async=true + autoExpand=false } }, ANSIConsoleAppender = { diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc index 67d3b6e28..74d628520 100644 --- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc +++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc @@ -52,7 +52,7 @@ component singleton accessors=true { } // Create an instance of the taskCFC. To prevent caching of the actual code in the task, we're treating them as - // transients. Since is since the code is likely to change while devs are building and testing them. + // transients. Since the code is likely to change while devs are building and testing them. var taskCFC = createTaskCFC( taskFile ); // If target doesn't exist or isn't a UDF From 09d7178613386a9a223852f58e1466491ebb08b0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 5 Sep 2018 01:21:43 -0500 Subject: [PATCH 11/49] COMMANDBOX-849 --- src/cfml/system/BaseCommand.cfc | 9 +++- .../task-commands/commands/task/run.cfc | 18 ++++++-- .../task-commands/models/TaskService.cfc | 44 ++++++++++++++++++- src/cfml/system/services/CommandService.cfc | 8 +++- src/cfml/system/util/CommandDSL.cfc | 18 +++++++- src/cfml/system/util/TaskDSL.cfc | 11 ++--- 6 files changed, 94 insertions(+), 14 deletions(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index 306e15625..cd5eb5c6e 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -123,7 +123,14 @@ component accessors="true" singleton { * @command.hint The command to run. Pass the same string a user would type at the shell. **/ function runCommand( required command, returnOutput=false ) { - return shell.callCommand( arguments.command, arguments.returnOutput ); + var results = shell.callCommand( arguments.command, arguments.returnOutput ); + + // If the previous command chain failed + if( shell.getExitCode() != 0 ) { + error( 'Command returned failing exit code (#shell.getExitCode()#)', 'Failing Command: ' & command, shell.getExitCode(), errorCode=shell.getExitCode() ); + } + + return results; } /** diff --git a/src/cfml/system/modules_app/task-commands/commands/task/run.cfc b/src/cfml/system/modules_app/task-commands/commands/task/run.cfc index 2f678b120..d3663db9b 100644 --- a/src/cfml/system/modules_app/task-commands/commands/task/run.cfc +++ b/src/cfml/system/modules_app/task-commands/commands/task/run.cfc @@ -60,9 +60,21 @@ component { } ); } - // Run the task! - // We're printing the output here so we can capture it and pipe or redirect the output from "task run" - return taskService.runTask( taskFile, target, taskArgs ); + try { + + // Run the task! + // We're printing the output here so we can capture it and pipe or redirect the output from "task run" + var results = taskService.runTask( taskFile, target, taskArgs ); + + } catch( any e ) { + rethrow; + } finally{ + if( shell.getExitCode() != 0 ) { + setExitCode( shell.getExitCode() ); + } + } + + return results; } } diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc index 74d628520..35b907782 100644 --- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc +++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc @@ -18,6 +18,7 @@ component singleton accessors=true { property name='CommandService' inject='CommandService'; property name='consoleLogger' inject='logbox:logger:console'; property name='metadataCache' inject='cachebox:metadataCache'; + property name='job' inject='interactiveJob'; function onDIComplete() { // Check if base task class mapped? @@ -62,8 +63,47 @@ component singleton accessors=true { CommandService.ensureRequiredparams( taskArgs, getMetadata( taskCFC[ target ] ).parameters ); - // Run the task - taskCFC[ target ]( argumentCollection = taskArgs ); + try { + + // Run the task + taskCFC[ target ]( argumentCollection = taskArgs ); + } catch( any e ) { + + // If this task didn't already set a failing exit code... + if( taskCFC.getExitCode() == 0 ) { + // Go ahead and set one for it. The shell will inherit it below in the finally block. + if( val( e.errorCode ?: 0 ) > 0 ) { + taskCFC.setExitCode( e.errorCode ); + } else { + taskCFC.setExitCode( 1 ); + } + } + + // Dump out anything the task had printed so far + var result = taskCFC.getResult(); + if( len( result ) ){ + shell.printString( result & cr ); + } + + rethrow; + + } finally { + // Set task exit code into the shell + shell.setExitCode( taskCFC.getExitCode() ); + } + + // If the previous Task failed + if( taskCFC.getExitCode() != 0 ) { + + if( job.isActive() ) { + job.errorRemaining( message ); + // Distance ourselves from whatever other output the Task may have given so far. + shell.printString( chr( 10 ) ); + } + + throw( message='Task returned failing exit code (#taskCFC.getExitCode()#)', detail='Failing Task: #taskFile# #target#', type="commandException", errorCode=taskCFC.getExitCode() ); + } + // Return any output. It's up to the caller to output it. // This is so task output can be correctly captured and piped or redirected to a file. diff --git a/src/cfml/system/services/CommandService.cfc b/src/cfml/system/services/CommandService.cfc index 73df8561c..29146352d 100644 --- a/src/cfml/system/services/CommandService.cfc +++ b/src/cfml/system/services/CommandService.cfc @@ -336,8 +336,14 @@ component accessors="true" singleton { lastCommandErrored = true; // If this command didn't already set a failing exit code... if( commandInfo.commandReference.CFC.getExitCode() == 0 ) { + // Go ahead and set one for it. The shell will inherit it below in the finally block. - commandInfo.commandReference.CFC.setExitCode( 1 ); + if( val( e.errorCode ?: 0 ) > 0 ) { + commandInfo.commandReference.CFC.setExitCode( e.errorCode ); + } else { + commandInfo.commandReference.CFC.setExitCode( 1 ); + } + } // Dump out anything the command had printed so far diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index d265c1096..889f9cfa5 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -24,8 +24,9 @@ component accessors=true { // DI - property name='parser' inject='parser'; - property name='shell' inject='shell'; + property name='parser' inject='parser'; + property name='shell' inject='shell'; + property name="job" inject='interactiveJob'; /** * Create a new, executable command @@ -204,6 +205,19 @@ component accessors=true { } else { var result = shell.callCommand( getTokens(), arguments.returnOutput ); } + + // If the previous command chain failed + if( shell.getExitCode() != 0 ) { + + if( job.isActive() ) { + job.errorRemaining( message ); + // Distance ourselves from whatever other output the command may have given so far. + shell.printString( chr( 10 ) ); + } + + throw( message='Command returned failing exit code (#shell.getExitCode()#)', detail='Failing Command: ' & getTokens().toList( ' ' ), type="commandException", errorCode=shell.getExitCode() ); + } + } finally { var postCommandCWD = shell.getPWD(); diff --git a/src/cfml/system/util/TaskDSL.cfc b/src/cfml/system/util/TaskDSL.cfc index 6714e6394..ea7b7c6b3 100644 --- a/src/cfml/system/util/TaskDSL.cfc +++ b/src/cfml/system/util/TaskDSL.cfc @@ -23,9 +23,10 @@ component accessors=true { // DI - property name='parser' inject='parser'; - property name='shell' inject='shell'; - property name='wirebox' inject='wirebox'; + property name='parser' inject='parser'; + property name='shell' inject='shell'; + property name='wirebox' inject='wirebox'; + property name="job" inject='interactiveJob'; /** * Create a new, executable task @@ -143,7 +144,7 @@ component accessors=true { /** * Run this command **/ -string function run( returnOutput=false, boolean echo=false, boolean rawParams=false ) { + string function run( returnOutput=false, boolean echo=false, boolean rawParams=false ) { setRawParams( rawParams ); @@ -157,7 +158,7 @@ string function run( returnOutput=false, boolean echo=false, boolean rawParams=f } try { - var result = shell.callCommand( getTokens(), true ); + var result = shell.callCommand( getTokens(), true ); } finally { var postCommandCWD = shell.getPWD(); From f5cb2e4c25a8b61b913bdf6afb9b4a0111a346d0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 6 Sep 2018 10:45:59 -0500 Subject: [PATCH 12/49] COMMANDBOX-857 --- .../system/services/ServerEngineService.cfc | 75 ++++++++----------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index 6ce2c7f78..8e8ccb9fd 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -34,27 +34,25 @@ component accessors="true" singleton="true" { var engineName = listFirst( cfengine, "@" ); arguments.baseDirectory = !arguments.baseDirectory.endsWith( "/" ) ? arguments.baseDirectory & "/" : arguments.baseDirectory; - if( engineName == "adobe" ) { - return installAdobe( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo, serverHomeDirectory=serverHomeDirectory ); - } else if (engineName == "railo") { - return installRailo( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo, serverHomeDirectory=serverHomeDirectory ); - } else if (engineName == "lucee") { - return installLucee( destination=arguments.baseDirectory, version=version, serverInfo=serverInfo, serverHomeDirectory=serverHomeDirectory ); + var installDetails = installEngineArchive( cfengine, arguments.baseDirectory, serverInfo, serverHomeDirectory ); + + if( installDetails.engineName contains "adobe" ) { + return installAdobe( installDetails, serverInfo ); + } else if ( installDetails.engineName contains "railo" ) { + return installRailo( installDetails, serverInfo ); + } else if ( installDetails.engineName contains "lucee" ) { + return installLucee( installDetails, serverInfo ); } else { - return installEngineArchive( cfengine, arguments.baseDirectory, serverInfo, serverHomeDirectory ); + return installDetails; } + } /** * install adobe * - * @destination target directory - * @version Version number or empty to use default - * @serverInfo Struct of server settings - * @serverHomeDirectory Override where the server's home with be **/ - public function installAdobe( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { - var installDetails = installEngineArchive( 'adobe@#version#', destination, serverInfo, serverHomeDirectory ); + public function installAdobe( installDetails, serverInfo ) { // Fix Adobe's broken default /CFIDE mapping var runtimeConfigPath = installDetails.installDir & "/WEB-INF/cfusion/lib/neo-runtime.xml"; @@ -85,13 +83,8 @@ component accessors="true" singleton="true" { /** * install lucee * - * @destination target directory - * @version Version number or empty to use default - * @serverInfo struct of server settings - * @serverHomeDirectory Override where the server's home with be **/ - public function installLucee( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { - var installDetails = installEngineArchive( 'lucee@#version#', destination, serverInfo, serverHomeDirectory ); + public function installLucee( installDetails, serverInfo ) { if( installDetails.initialInstall ) { configureWebXML( cfengine="lucee", version=installDetails.version, source=serverInfo.webXML, destination=serverInfo.webXML, serverInfo=serverInfo ); @@ -102,13 +95,8 @@ component accessors="true" singleton="true" { /** * install railo * - * @destination target directory - * @version Version number or empty to use default - * @serverInfo struct of server settings - * @serverHomeDirectory Override where the server's home with be **/ - public function installRailo( required destination, required version, required struct serverInfo, required string serverHomeDirectory ) { - var installDetails = installEngineArchive( 'railo@#version#', destination, serverInfo, serverHomeDirectory ); + public function installRailo( installDetails, serverInfo ) { if( installDetails.initialInstall ) { configureWebXML( cfengine="railo", version=installDetails.version, source=serverInfo.webXML, destination=serverInfo.webXML, serverInfo=serverInfo ); @@ -224,24 +212,6 @@ component accessors="true" singleton="true" { if( !len( serverInfo.webXML ) ) { serverInfo.webXML = "#installDetails.installDir#/WEB-INF/web.xml"; } - // Set up server and web context dirs if Railo or Lucee - if( serverinfo.cfengine contains 'lucee' || serverinfo.cfengine contains 'railo' ) { - // Default web context - if( !len( serverInfo.webConfigDir ) ) { - serverInfo.webConfigDir = "/WEB-INF/#lcase( listFirst( serverinfo.cfengine, "@" ) )#-web"; - } - // Default server context - if( !len( serverInfo.serverConfigDir ) ) { - serverInfo.serverConfigDir = "/WEB-INF"; - } - // Make relative to WEB-INF if possible - serverInfo.webConfigDir = replace( serverInfo.webConfigDir, '\', '/', 'all' ); - serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, '\', '/', 'all' ); - installDetails.installDir = replace( installDetails.installDir, '\', '/', 'all' ); - - serverInfo.webConfigDir = replace( serverInfo.webConfigDir, installDetails.installDir, '' ); - serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, installDetails.installDir, '' ); - } var engineTagFile = installDetails.installDir & '/.engineInstall'; @@ -306,6 +276,25 @@ component accessors="true" singleton="true" { // This file is so we know the correct version of our server on disk thisEngineTag = boxJSON.slug & '@' & boxJSON.version; } + + // Set up server and web context dirs if Railo or Lucee + if( installDetails.engineName contains 'lucee' || installDetails.engineName contains 'railo' ) { + // Default web context + if( !len( serverInfo.webConfigDir ) ) { + serverInfo.webConfigDir = "/WEB-INF/#lcase( installDetails.engineName )#-web"; + } + // Default server context + if( !len( serverInfo.serverConfigDir ) ) { + serverInfo.serverConfigDir = "/WEB-INF"; + } + // Make relative to WEB-INF if possible + serverInfo.webConfigDir = replace( serverInfo.webConfigDir, '\', '/', 'all' ); + serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, '\', '/', 'all' ); + installDetails.installDir = replace( installDetails.installDir, '\', '/', 'all' ); + + serverInfo.webConfigDir = replace( serverInfo.webConfigDir, installDetails.installDir, '' ); + serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, installDetails.installDir, '' ); + } // Look for a war or zip archive inside the package var theArchive = ''; From fc14aa626c1f359a85518518b28af880bc4e7828 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 6 Sep 2018 16:16:00 -0500 Subject: [PATCH 13/49] Always need to calc context paths --- .../system/services/ServerEngineService.cfc | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index 8e8ccb9fd..6a79cfaec 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -234,7 +234,8 @@ component accessors="true" singleton="true" { installDetails.engineName = previousEngineTag.listFirst( '@' ); installDetails.version = previousEngineTag.listLast( '@' ); } - + + calcLuceeRailoContextPaths( installDetails, serverInfo ); return installDetails; } @@ -258,7 +259,8 @@ component accessors="true" singleton="true" { // Mark this WAR as being exploded already fileWrite( engineTagFile, thisEngineTag ); - + + calcLuceeRailoContextPaths( installDetails, serverInfo ); return installDetails; } @@ -277,24 +279,7 @@ component accessors="true" singleton="true" { thisEngineTag = boxJSON.slug & '@' & boxJSON.version; } - // Set up server and web context dirs if Railo or Lucee - if( installDetails.engineName contains 'lucee' || installDetails.engineName contains 'railo' ) { - // Default web context - if( !len( serverInfo.webConfigDir ) ) { - serverInfo.webConfigDir = "/WEB-INF/#lcase( installDetails.engineName )#-web"; - } - // Default server context - if( !len( serverInfo.serverConfigDir ) ) { - serverInfo.serverConfigDir = "/WEB-INF"; - } - // Make relative to WEB-INF if possible - serverInfo.webConfigDir = replace( serverInfo.webConfigDir, '\', '/', 'all' ); - serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, '\', '/', 'all' ); - installDetails.installDir = replace( installDetails.installDir, '\', '/', 'all' ); - - serverInfo.webConfigDir = replace( serverInfo.webConfigDir, installDetails.installDir, '' ); - serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, installDetails.installDir, '' ); - } + calcLuceeRailoContextPaths( installDetails, serverInfo ); // Look for a war or zip archive inside the package var theArchive = ''; @@ -328,6 +313,28 @@ component accessors="true" singleton="true" { } return installDetails; } + + private function calcLuceeRailoContextPaths( installDetails, serverInfo ) { + + // Set up server and web context dirs if Railo or Lucee + if( installDetails.engineName contains 'lucee' || installDetails.engineName contains 'railo' ) { + // Default web context + if( !len( serverInfo.webConfigDir ) ) { + serverInfo.webConfigDir = "/WEB-INF/#lcase( installDetails.engineName )#-web"; + } + // Default server context + if( !len( serverInfo.serverConfigDir ) ) { + serverInfo.serverConfigDir = "/WEB-INF"; + } + // Make relative to WEB-INF if possible + serverInfo.webConfigDir = replace( serverInfo.webConfigDir, '\', '/', 'all' ); + serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, '\', '/', 'all' ); + installDetails.installDir = replace( installDetails.installDir, '\', '/', 'all' ); + + serverInfo.webConfigDir = replace( serverInfo.webConfigDir, installDetails.installDir, '' ); + serverInfo.serverConfigDir = replace( serverInfo.serverConfigDir, installDetails.installDir, '' ); + } + } /** * configure web.xml file for Lucee and Railo From 77056171f4397f9c9e3033eb805906d11c873be4 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 9 Sep 2018 15:27:52 -0500 Subject: [PATCH 14/49] COMMANDBOX-858 --- .../modules_app/system-commands/commands/dir.cfc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/dir.cfc b/src/cfml/system/modules_app/system-commands/commands/dir.cfc index 8ec58730c..67769f166 100644 --- a/src/cfml/system/modules_app/system-commands/commands/dir.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/dir.cfc @@ -17,8 +17,9 @@ component aliases="ls,ll,directory" { /** * @directory.hint The directory to list the contents of or a file Globbing path to filter on * @recurse.hint Include nested files and folders + * @simple.hint Output only path names and nothing else. **/ - function run( Globber directory=globber( getCWD() ), Boolean recurse=false ) { + function run( Globber directory=globber( getCWD() ), Boolean recurse=false, boolean simple=false ) { // If the user gives us an existing directory foo, change it to the // glob pattern foo/* or foo/** if doing a recursive listing. @@ -32,6 +33,18 @@ component aliases="ls,ll,directory" { .matches(); for( var x=1; x lte results.recordcount; x++ ) { + + if( simple ) { + + print.line( + cleanRecursiveDir( arguments.directory.getBaseDir(), results.directory[ x ] ) + & results.name[ x ] + & ( results.type[ x ] == "Dir" ? "/" : "" ) + ); + + continue; + } + var printCommand = ( results.type[ x ] eq "File" ? "green" : "white" ); print.text( From 04d5ce1bbd30656bef4d7b40200ae20fcb0eadcd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sun, 9 Sep 2018 15:29:29 -0500 Subject: [PATCH 15/49] COMMANDBOX-859 --- .../system-commands/commands/cat.cfc | 6 +- .../system-commands/commands/forEach.cfc | 70 +++++++++++++++++++ src/cfml/system/util/SystemSettings.cfc | 11 +++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/cfml/system/modules_app/system-commands/commands/forEach.cfc diff --git a/src/cfml/system/modules_app/system-commands/commands/cat.cfc b/src/cfml/system/modules_app/system-commands/commands/cat.cfc index 3b5bca21e..ce86a4cf2 100644 --- a/src/cfml/system/modules_app/system-commands/commands/cat.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/cat.cfc @@ -52,8 +52,10 @@ component aliases="type" { } else { file.apply( function( thisFile ) { - if( buffer.len() ) { buffer &= CR } - buffer &= fileRead( thisFile ); + if( fileExists( thisFile ) ){ + if( buffer.len() ) { buffer &= CR } + buffer &= fileRead( thisFile ); + } } ); } diff --git a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc new file mode 100644 index 000000000..1176b7f57 --- /dev/null +++ b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc @@ -0,0 +1,70 @@ +/** + * Excecute a command against every item in an incoming list. The list can be passed directly + * or piped into this command. The default delimiter is a new line so this works great piping + * the output of file listings direclty in, which have a file name per line. + * . + * This powerful construct allows you to perform basic loops from the CLI over arbitrary input. + * Most of the examples show file listings, but any input can be used that you want to iterate over. + * . + * This example will use the echo command to output each filename returned by ls. The echo is called + * once for every line of output being piped in. + * {code} + * ls --simple | forEach + * {code} + * . + * The default command is "echo" but you can perform an action against the incoming list of items. + * This example will use "cat" to output the contents of each file in the incoming list. + * {code} + * ls *.json --simple | forEach cat + * {code} + * . + * You can customize the delimiter. This example passes a hard-coded input and spits it on commas. + * So here, the install command is run three times, once for each package. A contrived, but effective example. + * {code} + * forEach input="coldbox,testbox,cborm" delimiter="," command=install + * {code} + * . + * If you want a more complex command, you can choose exactly where you wish to use the incoming item + * by referencing the default system setting expansion of ${item}. Remember to escape the expansion in + * your command so it's resolution is deferred until the forEach runs it internally. + * Here we echo each file name followed by the contents of the file. + * {code} + * ls *.json --simple | foreach "echo \${item} && cat \${item}" + * {code} + * . + * You may also choose a custom placeholder name for readability. + * {code} + * ll *.json --simple | foreach "echo \${filename} && cat \${filename}" filename + * {code} + **/ +component { + property name='SystemSettings' inject='SystemSettings'; + + /** + * @input.hint The piped input to iterate over + * @command.hint Command to be run once per input item + * @itemName.hint Name of system setting to access each item per iteration + * @delimiter.hint Delimiter Char(s) to split input + **/ + function run( input='', command='echo', itemName='item', delimiter=CR ) { + // Turn output into an array, breaking on delimiter + var content = listToArray( arguments.input, delimiter ); + + // Loop over content + for( var line in content ) { + var theCommand = this.command( arguments.command ); + + // If it doesn't look like they are using the placeholder, then set the item as the next param + if( !arguments.command.findNoCase( '${#itemName#' ) ) { + theCommand.params( line ); + } + + // Set this as a localized environment variable so the command can access it. + systemSettings.setSystemSetting( itemName, line ); + + theCommand.run(); + + } + } + +} diff --git a/src/cfml/system/util/SystemSettings.cfc b/src/cfml/system/util/SystemSettings.cfc index 8c04a7e7f..79133b4d3 100644 --- a/src/cfml/system/util/SystemSettings.cfc +++ b/src/cfml/system/util/SystemSettings.cfc @@ -63,6 +63,17 @@ component singleton { ); } + /** + * Set a System Setting. + * + * @key The name of the setting to set. + * @value The value to use + */ + function setSystemSetting( required string key, required string value ) { + // TODO: change this to be context-aware env vars for the current command, not global + system.setProperty( arguments.key, arguments.value ); + } + /** * Set a Java System property. * From 1f5293a06abf51e559eb5733fff3a0b93ca0d5e4 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 14 Sep 2018 16:54:24 -0500 Subject: [PATCH 16/49] Auto complete server.json file names for name param of start --- .../system/modules_app/server-commands/commands/server/start.cfc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc index d322bdccf..00e8834f3 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/start.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/start.cfc @@ -47,6 +47,7 @@ component aliases="start" { /** * @name short name for this server or a path to the server.json file. + * @name.optionsFileComplete true * @name.optionsUDF serverNameComplete * @port port number * @host bind to a host/ip From 264e35e40ee50de67c8812a7cc3ecdb09a38029d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 17 Sep 2018 14:05:36 -0500 Subject: [PATCH 17/49] Update comment in module scaffold --- .../coldbox-commands/templates/modules/ModuleConfig.cfc | 2 +- .../coldbox-commands/templates/modules/ModuleConfigScript.cfc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc index 89df93733..228c3d643 100644 --- a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfig.cfc @@ -109,7 +109,7 @@ Optional Methods ]; // Binder Mappings - // binder.map("Alias").to("#moduleMapping#.model.MyService"); + // binder.map("Alias").to("#moduleMapping#.models.MyService"); } diff --git a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc index 8d51a7f5f..f14cda4d4 100644 --- a/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/templates/modules/ModuleConfigScript.cfc @@ -105,7 +105,7 @@ component { ]; // Binder Mappings - // binder.map("Alias").to("#moduleMapping#.model.MyService"); + // binder.map("Alias").to("#moduleMapping#.models.MyService"); } From a41a542b1d2a03bf9352186fb3b585bf2199cbb6 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 17 Sep 2018 17:57:23 -0500 Subject: [PATCH 18/49] COMMANDBOX-861 --- src/cfml/system/modules_app/system-commands/commands/tail.cfc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index 25f1ab02b..f8e7158cf 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -108,6 +108,7 @@ return cleanLine( line ); } ) .toList( chr( 10 ) ) + & chr( 10 ) ) .toConsole(); From caa19c81eafcd589ff7006f2d1a6e4552d3d18b1 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 17 Sep 2018 18:00:33 -0500 Subject: [PATCH 19/49] COMMANDBOX-862 --- .../modules_app/server-commands/commands/server/log.cfc | 7 +++++++ .../system/modules_app/system-commands/commands/tail.cfc | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc index 2519c07e6..c2cfcef2d 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc @@ -97,6 +97,13 @@ component { // [DEBUG] runwar.server: Starting open browser action line = reReplaceNoCase( line, '^(\[[^]]*])( runwar\.[^:]*: )(.*)', '\1 Runwar: \3' ); + // Strip off redundant severities that come from wrapping LogBox apenders in Log4j appenders + // [INFO ] DEBUG my.logger.name This rain in spain stays mainly in the plains + line = reReplaceNoCase( line, '^(\[(INFO |ERROR|DEBUG|WARN )] )(INFO|ERROR|DEBUG|WARN)( .*)', '[\3]\4' ); + + // Add extra space so [WARN] becomes [WARN ] + line = reReplaceNoCase( line, '^\[(INFO|WARN)]( .*)', '[\1 ]\2' ); + if( line.startsWith( '[INFO ]' ) ) { return reReplaceNoCase( line, '^(\[INFO ] )(.*)', '[#printUtil.boldCyan('INFO ')#] \2' ); } diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index f8e7158cf..763ef0c37 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -278,6 +278,13 @@ // [DEBUG] runwar.server: Starting open browser action line = reReplaceNoCase( line, '^(\[[^]]*])( runwar\.[^:]*: )(.*)', '\1 Runwar: \3' ); + // Strip off redundant severities that come from wrapping LogBox apenders in Log4j appenders + // [INFO ] DEBUG my.logger.name This rain in spain stays mainly in the plains + line = reReplaceNoCase( line, '^(\[(INFO |ERROR|DEBUG|WARN )] )(INFO|ERROR|DEBUG|WARN)( .*)', '[\3]\4' ); + + // Add extra space so [WARN] becomes [WARN ] + line = reReplaceNoCase( line, '^\[(INFO|WARN)]( .*)', '[\1 ]\2' ); + if( line.startsWith( '[INFO ]' ) ) { return reReplaceNoCase( line, '^(\[INFO ] )(.*)', '[#printUtil.boldCyan('INFO ')#] \2' ); } From de27978a7aa73990f9a2e7dea116a31a0cb64a4f Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 17 Sep 2018 22:00:12 -0500 Subject: [PATCH 20/49] COMMANDBOX-863 --- build/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.properties b/build/build.properties index 88c8206dd..da149caff 100644 --- a/build/build.properties +++ b/build/build.properties @@ -10,7 +10,7 @@ java.debug=true #dependencies dependencies.dir=${basedir}/lib -cfml.version=5.2.8.50 +cfml.version=5.2.9.31 cfml.loader.version=2.1.14 cfml.cli.version=${cfml.loader.version}.${cfml.version} lucee.version=${cfml.version} From 76d84eb8471429a5af98f6176877799ae5a5e180 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 17 Sep 2018 22:28:51 -0500 Subject: [PATCH 21/49] Turn on Pack200 for remaining jar pieces --- build/build.xml | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/build/build.xml b/build/build.xml index 961075735..624835b1e 100644 --- a/build/build.xml +++ b/build/build.xml @@ -327,8 +327,8 @@ External Dependencies: - @@ -346,28 +346,28 @@ External Dependencies: - + - + - + - + - + - + @@ -375,7 +375,7 @@ External Dependencies: - + @@ -386,36 +386,36 @@ External Dependencies: - --> + - + - - --> - - --> - - --> + - - --> + - - --> + From 33fd26a484a800ce460c77e4097d1e78da95c6cf Mon Sep 17 00:00:00 2001 From: KamasamaK Date: Wed, 19 Sep 2018 23:44:38 -0400 Subject: [PATCH 22/49] Added server.schema.json (#167) --- src/cfml/system/config/server.schema.json | 445 ++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 src/cfml/system/config/server.schema.json diff --git a/src/cfml/system/config/server.schema.json b/src/cfml/system/config/server.schema.json new file mode 100644 index 000000000..5c42a00a8 --- /dev/null +++ b/src/cfml/system/config/server.schema.json @@ -0,0 +1,445 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "CommandBox Server", + "description": "Configuration file for a CommandBox server", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "The name of the server", + "type": "string", + "default": "" + }, + "openBrowser": { + "title": "Open Browser", + "description": "", + "type": "boolean", + "default": true + }, + "openBrowserURL": { + "title": "Open Browser URL", + "description": "", + "type": "string", + "default": "" + }, + "startTimeout": { + "title": "Open Browser URL", + "description": "", + "type": "number", + "default": 240 + }, + "stopsocket": { + "title": "Stop Socket", + "description": "", + "type": "number", + "default": 0 + }, + "debug": { + "title": "Debug", + "description": "", + "type": "boolean", + "default": false + }, + "trace": { + "title": "Trace", + "description": "", + "type": "boolean", + "default": false + }, + "console": { + "title": "Console", + "description": "", + "type": "boolean", + "default": false + }, + "trayEnable": { + "title": "Tray Enable", + "description": "", + "type": "boolean", + "default": true + }, + "trayicon": { + "title": "Tray Icon", + "description": "Path to icon", + "type": "string", + "default": "" + }, + "trayOptions": { + "title": "Tray Options", + "description": "", + "type": "array", + "minItems": 0, + "items": { + "title": "Tray Option", + "description": "", + "type": "object", + "properties": { + "label": { + "title": "Label", + "description": "", + "type": "string" + }, + "action": { + "title": "Action", + "description": "", + "type": "string" + }, + "url": { + "title": "URL", + "description": "", + "type": "string" + }, + "disabled": { + "title": "Disabled", + "description": "", + "type": "boolean", + "default": false + }, + "image": { + "title": "Image", + "description": "Path to image", + "type": "string", + "default": "" + } + } + }, + "default": [] + }, + "jvm": { + "title": "JVM", + "description": "JVM Options", + "type": "object", + "properties": { + "heapSize": { + "title": "Heap Size", + "description": "The max heap size the server is allowed to have", + "type": "number", + "default": 512 + }, + "minHeapSize": { + "title": "Minimum Heap Size", + "description": "The starting heap size for the server", + "type": "number", + "default": 0 + }, + "args": { + "title": "Arguments", + "description": "Ad-hoc JVM args for the server", + "type": "string", + "default": "" + }, + "javaHome": { + "title": "Java Home", + "description": "Path to custom JRE. Default is the one that the CommandBox CLI is using", + "type": "string" + } + } + }, + "web": { + "title": "Web", + "description": "Web Server Options", + "type": "object", + "properties": { + "host": { + "title": "Host", + "description": "", + "type": "string", + "default": "127.0.0.1" + }, + "webroot": { + "title": "Webroot", + "description": "Webroot directory", + "type": "string", + "default": "" + }, + "directoryBrowsing": { + "title": "Directory Browsing", + "description": "", + "type": "boolean", + "default": true + }, + "accessLogEnable": { + "title": "Access Log Enable", + "description": "", + "type": "boolean", + "default": true + }, + "GZIPEnable": { + "title": "GZIP Enable", + "description": "", + "type": "boolean", + "default": true + }, + "welcomeFiles": { + "title": "Welcome Files", + "description": "A comma-delimited list of files that you would like CommandBox to look for when a user hits a directory on a running server", + "type": "string", + "default": "" + }, + "aliases": { + "title": "Aliases", + "description": "Web aliases for the web server, similar to virtual directories", + "type": "object", + "patternProperties": { + "^(/[^/]+)+$": { + "title": "Alias", + "description": "The key is the web-accessible virtual path and the value is the relative or absolute path to the folder the alias points to.", + "type": "string" + } + }, + "additionalProperties": false, + "default": {} + }, + "errorPages": { + "title": "Error Pages", + "description": "The error pages that CommandBox servers return. You can have a setting for each status code including a default error page to be used if no other setting applies.", + "type": "object", + "properties": { + "default": { + "title": "Default", + "description": "Path to default error page", + "type": "string", + "default": "" + } + }, + "patternProperties": { + "^[1-5][0-9]{2}$": { + "title": "Error Page", + "description": "The key is the status code integer and the value is a relative (to the web root) path to be loaded for that status code.", + "type": "string" + } + }, + "additionalProperties": false, + "default": {} + }, + "HTTP": { + "title": "HTTP", + "description": "", + "type": "object", + "properties": { + "enable": { + "title": "Enable", + "description": "", + "type": "boolean", + "default": true + }, + "port": { + "title": "Port", + "description": "", + "type": "number", + "default": 0 + } + } + }, + "SSL": { + "title": "SSL", + "description": "", + "type": "object", + "properties": { + "enable": { + "title": "Enable", + "description": "", + "type": "boolean", + "default": false + }, + "port": { + "title": "Port", + "description": "", + "type": "number", + "default": 1443 + }, + "certFile": { + "title": "Cert File", + "description": "", + "type": "string", + "default": "" + }, + "keyFile": { + "title": "Key File", + "description": "", + "type": "string", + "default": "" + }, + "keyPass": { + "title": "Key Pass", + "description": "", + "type": "string", + "default": "" + } + } + }, + "AJP": { + "title": "AJP", + "description": "", + "type": "object", + "properties": { + "enable": { + "title": "Enable", + "description": "", + "type": "boolean", + "default": false + }, + "port": { + "title": "Port", + "description": "", + "type": "number", + "default": 8009 + } + } + }, + "rewrites": { + "title": "Rewrites", + "description": "", + "type": "object", + "properties": { + "enable": { + "title": "Enable", + "description": "", + "type": "boolean", + "default": false + }, + "logEnable": { + "title": "Log Enable", + "description": "", + "type": "boolean", + "default": false + }, + "config": { + "title": "Config", + "description": "Path to config.xml", + "type": "string", + "default": "" + }, + "statusPath": { + "title": "Status Path", + "description": "", + "type": "string", + "default": "" + }, + "configReloadSeconds": { + "title": "Config Reload Seconds", + "description": "", + "type": "number" + } + } + }, + "basicAuth": { + "title": "Server Home Directory", + "description": "", + "type": "object", + "properties": { + "enable": { + "title": "Enable", + "description": "", + "type": "boolean", + "default": true + }, + "users": { + "title": "Users", + "description": "", + "type": "object", + "additionalProperties": { + "title": "User", + "description": "The key is the user name and the value is the password.", + "type": "string" + }, + "default": {} + } + } + } + } + }, + "app": { + "title": "Application", + "description": "Application Server Options", + "type": "object", + "properties": { + "logDir": { + "title": "Log Directory", + "description": "", + "type": "string", + "default": "" + }, + "libDirs": { + "title": "Library Directories", + "description": "A comma-delimited list of directories from which CommandBox will load JARs", + "type": "string", + "default": "" + }, + "args": { + "title": "Arguments", + "description": "", + "type": "string", + "default": "" + }, + "webConfigDir": { + "title": "Web Configuration Directory", + "description": "", + "type": "string", + "default": "" + }, + "serverConfigDir": { + "title": "Server Configuration Directory", + "description": "", + "type": "string", + "default": "" + }, + "webXML": { + "title": "Web XML", + "description": "", + "type": "string", + "default": "" + }, + "WARPath": { + "title": "WAR Path", + "description": "", + "type": "string", + "default": "" + }, + "cfengine": { + "title": "CF Engine", + "description": "", + "type": "string", + "default": "" + }, + "restMappings": { + "title": "REST Mappings", + "description": "", + "type": "string", + "default": "" + }, + "serverHomeDirectory": { + "title": "Server Home Directory", + "description": "", + "type": "string", + "default": "" + }, + "sessionCookieSecure": { + "title": "Session Cookie Secure", + "description": "", + "type": "boolean", + "default": false + }, + "sessionCookieHTTPOnly": { + "title": "Session Cookie HttpOnly", + "description": "", + "type": "boolean", + "default": false + } + } + }, + "runwar": { + "title": "Run WAR", + "description": "", + "type": "object", + "properties": { + "args": { + "title": "Arguments", + "description": "Ad-hoc options for the underlying Runwar library", + "type": "string", + "default": "" + } + } + } + } +} \ No newline at end of file From 9a9747fb539de7726236e9aa52623e9ae21faab2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 19 Sep 2018 23:22:05 -0500 Subject: [PATCH 23/49] Added descriptions for JSON schema --- src/cfml/system/config/server.schema.json | 169 ++++++++++++---------- 1 file changed, 91 insertions(+), 78 deletions(-) diff --git a/src/cfml/system/config/server.schema.json b/src/cfml/system/config/server.schema.json index 5c42a00a8..a2804fd39 100644 --- a/src/cfml/system/config/server.schema.json +++ b/src/cfml/system/config/server.schema.json @@ -12,94 +12,113 @@ }, "openBrowser": { "title": "Open Browser", - "description": "", + "description": "Controls whether browser opens by default when starting server", "type": "boolean", "default": true }, "openBrowserURL": { "title": "Open Browser URL", - "description": "", + "description": "Override the URL to open when starting the server", "type": "string", "default": "" }, "startTimeout": { - "title": "Open Browser URL", - "description": "", + "title": "Server start timeout", + "description": "The length of time in seconds to wait for the server to start", "type": "number", "default": 240 }, "stopsocket": { "title": "Stop Socket", - "description": "", + "description": "The port the server listens on to receive a stop command", "type": "number", "default": 0 }, "debug": { "title": "Debug", - "description": "", + "description": "Enable debug level logging for server", "type": "boolean", "default": false }, "trace": { "title": "Trace", - "description": "", + "description": "Enable trace level logging for server", "type": "boolean", "default": false }, "console": { "title": "Console", - "description": "", + "description": "Start the server in console mode instead of in the background", "type": "boolean", "default": false }, "trayEnable": { "title": "Tray Enable", - "description": "", + "description": "Control whether the server has an associated icon in the system tray", "type": "boolean", "default": true }, "trayicon": { "title": "Tray Icon", - "description": "Path to icon", + "description": "Path to the server's tray icon", "type": "string", "default": "" }, "trayOptions": { "title": "Tray Options", - "description": "", + "description": "An array of custom menu items to be added to the server's tray menu", "type": "array", "minItems": 0, "items": { "title": "Tray Option", - "description": "", + "description": "An object that represents a single tray menu item", "type": "object", "properties": { "label": { "title": "Label", - "description": "", + "description": "Text of menu item", "type": "string" }, "action": { "title": "Action", - "description": "", + "description": "Action to perform when user clicks this menu item. 'openfilesystem', 'openbrowser', or 'stopserver'", "type": "string" }, "url": { "title": "URL", - "description": "", + "description": "Url to open for 'openbrowser' action", "type": "string" }, "disabled": { "title": "Disabled", - "description": "", + "description": "Turn menu item grey and nothing happens when clicking on it", "type": "boolean", "default": false }, "image": { "title": "Image", - "description": "Path to image", + "description": "Path to PNG image to display on menu item next to the label", "type": "string", "default": "" + }, + "hotkey": { + "title": "Hotkey", + "description": "Keyboard shortcut to choose this menu item", + "type": "string", + "default": "" + }, + "path": { + "title": "Path", + "description": "Filesystem path to open for 'openfilesystem' action", + "type": "string", + "default": "" + }, + "items": { + "title": "Items", + "description": "Nested menu items", + "type": "array", + "minItems": 0, + "default": [] } } }, @@ -111,25 +130,25 @@ "type": "object", "properties": { "heapSize": { - "title": "Heap Size", - "description": "The max heap size the server is allowed to have", + "title": "Max Heap Size", + "description": "The max heap size of the server in MB", "type": "number", "default": 512 }, "minHeapSize": { - "title": "Minimum Heap Size", - "description": "The starting heap size for the server", + "title": "Min Heap Size", + "description": "The starting heap size for the server in MB", "type": "number", "default": 0 }, "args": { - "title": "Arguments", - "description": "Ad-hoc JVM args for the server", + "title": "JVM Arguments", + "description": "Ad-hoc JVM args for the server such as -X:name", "type": "string", "default": "" }, "javaHome": { - "title": "Java Home", + "title": "Java Home Path", "description": "Path to custom JRE. Default is the one that the CommandBox CLI is using", "type": "string" } @@ -142,7 +161,7 @@ "properties": { "host": { "title": "Host", - "description": "", + "description": "The default host name of the server", "type": "string", "default": "127.0.0.1" }, @@ -154,25 +173,25 @@ }, "directoryBrowsing": { "title": "Directory Browsing", - "description": "", + "description": "Enables file listing for directories with no welcome file", "type": "boolean", "default": true }, "accessLogEnable": { "title": "Access Log Enable", - "description": "", + "description": "Enable web server access log", "type": "boolean", "default": true }, "GZIPEnable": { "title": "GZIP Enable", - "description": "", + "description": "Enable GZip compression in HTTP responses", "type": "boolean", "default": true }, "welcomeFiles": { "title": "Welcome Files", - "description": "A comma-delimited list of files that you would like CommandBox to look for when a user hits a directory on a running server", + "description": "A comma-delimited list of files that you would like CommandBox to look for when a user hits a directory", "type": "string", "default": "" }, @@ -183,7 +202,7 @@ "patternProperties": { "^(/[^/]+)+$": { "title": "Alias", - "description": "The key is the web-accessible virtual path and the value is the relative or absolute path to the folder the alias points to.", + "description": "The key is the web-accessible virtual path and the value is the relative or absolute path to the folder the alias points to", "type": "string" } }, @@ -192,7 +211,7 @@ }, "errorPages": { "title": "Error Pages", - "description": "The error pages that CommandBox servers return. You can have a setting for each status code including a default error page to be used if no other setting applies.", + "description": "The error pages that CommandBox servers return. You can have a setting for each status code including a default error page to be used if no other setting applies", "type": "object", "properties": { "default": { @@ -205,7 +224,7 @@ "patternProperties": { "^[1-5][0-9]{2}$": { "title": "Error Page", - "description": "The key is the status code integer and the value is a relative (to the web root) path to be loaded for that status code.", + "description": "The key is the status code integer and the value is a relative (to the web root) path to be loaded for that status code", "type": "string" } }, @@ -213,19 +232,19 @@ "default": {} }, "HTTP": { - "title": "HTTP", - "description": "", + "title": "HTTP Settings", + "description": "Configure the HTTP listener on the server", "type": "object", "properties": { "enable": { "title": "Enable", - "description": "", + "description": "Enable HTTP for this serer", "type": "boolean", "default": true }, "port": { "title": "Port", - "description": "", + "description": "HTTP port to use", "type": "number", "default": 0 } @@ -233,36 +252,36 @@ }, "SSL": { "title": "SSL", - "description": "", + "description": "Configure the HTTPS listener on the server", "type": "object", "properties": { "enable": { "title": "Enable", - "description": "", + "description": "Enable HTTPS for this server", "type": "boolean", "default": false }, "port": { "title": "Port", - "description": "", + "description": "HTTPS port to use", "type": "number", "default": 1443 }, "certFile": { "title": "Cert File", - "description": "", + "description": "Path to SSL cert file", "type": "string", "default": "" }, "keyFile": { "title": "Key File", - "description": "", + "description": "Path to SSL key file", "type": "string", "default": "" }, "keyPass": { "title": "Key Pass", - "description": "", + "description": "Password for SSL key file", "type": "string", "default": "" } @@ -270,18 +289,18 @@ }, "AJP": { "title": "AJP", - "description": "", + "description": "Configure the AJP listener on the server", "type": "object", "properties": { "enable": { "title": "Enable", - "description": "", + "description": "Enable AJP for this server", "type": "boolean", "default": false }, "port": { "title": "Port", - "description": "", + "description": "AJP port to use", "type": "number", "default": 8009 } @@ -289,54 +308,54 @@ }, "rewrites": { "title": "Rewrites", - "description": "", + "description": "Configure URL Rewrites", "type": "object", "properties": { "enable": { "title": "Enable", - "description": "", + "description": "Enable URL Rewrites on this server", "type": "boolean", "default": false }, "logEnable": { "title": "Log Enable", - "description": "", + "description": "Enable Rewrite log file", "type": "boolean", "default": false }, "config": { "title": "Config", - "description": "Path to config.xml", + "description": "Path to xml config file or .htaccess", "type": "string", "default": "" }, "statusPath": { - "title": "Status Path", - "description": "", + "title": "Tuckey Status Path", + "description": "URL path to visit Tuckey status page like '/tuckey-status'", "type": "string", - "default": "" + "default": "/tuckey-status" }, "configReloadSeconds": { "title": "Config Reload Seconds", - "description": "", + "description": "Number of seconds to check rewrite config file for changes", "type": "number" } } }, "basicAuth": { - "title": "Server Home Directory", + "title": "Configure basic authentication", "description": "", "type": "object", "properties": { "enable": { "title": "Enable", - "description": "", + "description": "Enable basic auth for this server", "type": "boolean", "default": true }, "users": { "title": "Users", - "description": "", + "description": "Users who can authentiate to basic auth", "type": "object", "additionalProperties": { "title": "User", @@ -356,81 +375,75 @@ "properties": { "logDir": { "title": "Log Directory", - "description": "", + "description": "The folder path where the servlet out, rewrite, and access log are written to", "type": "string", "default": "" }, "libDirs": { - "title": "Library Directories", + "title": "Jar lib Directories", "description": "A comma-delimited list of directories from which CommandBox will load JARs", "type": "string", "default": "" }, - "args": { - "title": "Arguments", - "description": "", - "type": "string", - "default": "" - }, "webConfigDir": { - "title": "Web Configuration Directory", - "description": "", + "title": "Web Context Directory", + "description": "Directory for Lucee/Railo web context", "type": "string", "default": "" }, "serverConfigDir": { - "title": "Server Configuration Directory", - "description": "", + "title": "Server Context Directory", + "description": "Directory for Lucee/Railo server context", "type": "string", "default": "" }, "webXML": { "title": "Web XML", - "description": "", + "description": "Path to web.xml file", "type": "string", "default": "" }, "WARPath": { "title": "WAR Path", - "description": "", + "description": "Path to a local WAR archive or exploded WAR folder. Mutually exclusive with cfengine.", "type": "string", "default": "" }, "cfengine": { - "title": "CF Engine", - "description": "", + "title": "CFML Engine", + "description": "An Endpoint ID that resolves to a CF engine such as 'adobe' or 'lucee'. Include version as 'adobe@2016'", "type": "string", "default": "" }, "restMappings": { "title": "REST Mappings", - "description": "", + "description": "Comma delimted list of paths to map to the CF engine's REST servlet such as '/rest/*,/api/*'", "type": "string", "default": "" }, "serverHomeDirectory": { "title": "Server Home Directory", - "description": "", + "description": "Path to folder where the server WAR will be expanded", "type": "string", "default": "" }, "sessionCookieSecure": { "title": "Session Cookie Secure", - "description": "", + "description": "Enable secure session cookies", "type": "boolean", "default": false }, "sessionCookieHTTPOnly": { "title": "Session Cookie HttpOnly", - "description": "", + "description": "Enable HTTP-only session cookies", "type": "boolean", "default": false } } }, "runwar": { - "title": "Run WAR", - "description": "", + "title": "Confiure RunWar", + "description": "These settings apply to the underlying Runwar library that starts servers", "type": "object", "properties": { "args": { From 55db2ef2ad58ba4b8c177386cfe18056801529a5 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 21 Sep 2018 12:51:28 -0500 Subject: [PATCH 24/49] COMMANDBOX-865 --- .../system/modules_app/server-commands/commands/server/log.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc index c2cfcef2d..aaba84fbd 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc @@ -71,7 +71,7 @@ component { print.boldRedLine( "No log file found for '#serverInfo.webroot#'!" ) .line( "#logFile#" ); if( access ) { - print.yellowLine( 'Enable accesss logging with [server set web.acessLogEnable=true]' ); + print.yellowLine( 'Enable accesss logging with [server set web.accessLogEnable=true]' ); } } } From 3e0d3fb7ada2a237e9001a43a259baa4e6abe001 Mon Sep 17 00:00:00 2001 From: KamasamaK Date: Fri, 21 Sep 2018 13:59:15 -0400 Subject: [PATCH 25/49] Made nested menu items recursive in server schema. Spelling fixes. (#168) --- src/cfml/system/config/server.schema.json | 118 ++++++++++++---------- 1 file changed, 63 insertions(+), 55 deletions(-) diff --git a/src/cfml/system/config/server.schema.json b/src/cfml/system/config/server.schema.json index a2804fd39..b080c974a 100644 --- a/src/cfml/system/config/server.schema.json +++ b/src/cfml/system/config/server.schema.json @@ -1,5 +1,63 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", + "definitions": { + "trayOptionsItem": { + "title": "Tray Option", + "description": "An object that represents a single tray menu item", + "type": "object", + "properties": { + "label": { + "title": "Label", + "description": "Text of menu item", + "type": "string" + }, + "action": { + "title": "Action", + "description": "Action to perform when user clicks this menu item. 'openfilesystem', 'openbrowser', or 'stopserver'", + "type": "string" + }, + "url": { + "title": "URL", + "description": "Url to open for 'openbrowser' action", + "type": "string" + }, + "disabled": { + "title": "Disabled", + "description": "Turn menu item grey and nothing happens when clicking on it", + "type": "boolean", + "default": false + }, + "image": { + "title": "Image", + "description": "Path to PNG image to display on menu item next to the label", + "type": "string", + "default": "" + }, + "hotkey": { + "title": "Hotkey", + "description": "Keyboard shortcut to choose this menu item", + "type": "string", + "default": "" + }, + "path": { + "title": "Path", + "description": "Filesystem path to open for 'openfilesystem' action", + "type": "string", + "default": "" + }, + "items": { + "title": "Items", + "description": "Nested menu items", + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/definitions/trayOptionsItem" + }, + "default": [] + } + } + } + }, "title": "CommandBox Server", "description": "Configuration file for a CommandBox server", "type": "object", @@ -70,57 +128,7 @@ "type": "array", "minItems": 0, "items": { - "title": "Tray Option", - "description": "An object that represents a single tray menu item", - "type": "object", - "properties": { - "label": { - "title": "Label", - "description": "Text of menu item", - "type": "string" - }, - "action": { - "title": "Action", - "description": "Action to perform when user clicks this menu item. 'openfilesystem', 'openbrowser', or 'stopserver'", - "type": "string" - }, - "url": { - "title": "URL", - "description": "Url to open for 'openbrowser' action", - "type": "string" - }, - "disabled": { - "title": "Disabled", - "description": "Turn menu item grey and nothing happens when clicking on it", - "type": "boolean", - "default": false - }, - "image": { - "title": "Image", - "description": "Path to PNG image to display on menu item next to the label", - "type": "string", - "default": "" - }, - "hotkey": { - "title": "Hotkey", - "description": "Keyboard shortcut to choose this menu item", - "type": "string", - "default": "" - }, - "path": { - "title": "Path", - "description": "Filesystem path to open for 'openfilesystem' action", - "type": "string", - "default": "" - }, - "items": { - "title": "Items", - "description": "Nested menu items", - "type": "array", - "minItems": 0, - "default": [] - } - } + "$ref": "#/definitions/trayOptionsItem" }, "default": [] }, @@ -355,7 +363,7 @@ }, "users": { "title": "Users", - "description": "Users who can authentiate to basic auth", + "description": "Users who can authenticate to basic auth", "type": "object", "additionalProperties": { "title": "User", @@ -417,7 +425,7 @@ }, "restMappings": { "title": "REST Mappings", - "description": "Comma delimted list of paths to map to the CF engine's REST servlet such as '/rest/*,/api/*'", + "description": "Comma delimited list of paths to map to the CF engine's REST servlet such as '/rest/*,/api/*'", "type": "string", "default": "" }, @@ -442,7 +450,7 @@ } }, "runwar": { - "title": "Confiure RunWar", + "title": "Configure RunWar", "description": "These settings apply to the underlying Runwar library that starts servers", "type": "object", "properties": { From 6b3a658e4665ec81542f1079966c13239e8ffbea Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 21 Sep 2018 21:53:17 -0500 Subject: [PATCH 26/49] COMMANDBOX-866 --- src/cfml/system/BaseTask.cfc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/BaseTask.cfc b/src/cfml/system/BaseTask.cfc index 365410ef3..83af71a97 100644 --- a/src/cfml/system/BaseTask.cfc +++ b/src/cfml/system/BaseTask.cfc @@ -46,7 +46,8 @@ component accessors="true" extends='commandbox.system.BaseCommand' { * @taskFile The name of the task to run. **/ function task( required taskFile='task' ) { - return getinstance( name='TaskDSL', initArguments={ taskFile : arguments.taskFile } ); + return getinstance( name='TaskDSL', initArguments={ taskFile : arguments.taskFile } ) + .inWorkingDirectory( getDirectoryFromPath( getCurrentTemplatePath() ) ); } /** From bc4ed60fccd3f689eca0cbde9b7e6c042e7ffb37 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 21 Sep 2018 22:20:46 -0500 Subject: [PATCH 27/49] COMMANDBOX-848 --- src/cfml/system/BaseCommand.cfc | 4 ++++ .../modules_app/task-commands/models/TaskService.cfc | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index cd5eb5c6e..f56b08fd8 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -47,6 +47,10 @@ component accessors="true" singleton { return 'This command CFC has not implemented a run() method.'; } + function getPrinter() { + return variables.print; + } + // Convenience method for getting stuff from WireBox function getInstance( name, dsl, initArguments={}, targetObject='' ) { return wirebox.getInstance( argumentCollection = arguments ); diff --git a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc index 35b907782..90193b949 100644 --- a/src/cfml/system/modules_app/task-commands/models/TaskService.cfc +++ b/src/cfml/system/modules_app/task-commands/models/TaskService.cfc @@ -61,8 +61,16 @@ component singleton accessors=true { throw( message="Target [#target#] doesn't exist in Task CFC.", detail=arguments.taskFile, type="commandException"); } - CommandService.ensureRequiredparams( taskArgs, getMetadata( taskCFC[ target ] ).parameters ); - + var targetMD = getMetadata( taskCFC[ target ] ); + CommandService.ensureRequiredparams( taskArgs, targetMD.parameters ); + + // Check for, and run target dependencies + var taskDeps = targetMD.depends ?: ''; + taskDeps.listToArray() + .each( function( dep ) { + taskCFC.getPrinter().print( runTask( taskFile, dep, taskArgs ) ); + } ); + try { // Run the task From adfe5bd40704afe08cd02f541238be6cb64ea004 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Sat, 22 Sep 2018 23:42:58 -0500 Subject: [PATCH 28/49] make this work for tasks too --- 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 f56b08fd8..7a4740067 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -44,7 +44,7 @@ component accessors="true" singleton { // This method needs to be overridden by the concrete class. function run() { - return 'This command CFC has not implemented a run() method.'; + error( 'This command CFC has not implemented a run() method.' ); } function getPrinter() { From 157fc07303f0604311cbcf78d77ca5e219dcf270 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 24 Sep 2018 12:00:31 -0500 Subject: [PATCH 29/49] COMMANDBOX-867 --- .../commands/coldbox/create/app.cfc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc index a4991e4f6..ab840d304 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc @@ -134,11 +134,15 @@ component { } // Prepare defaults on box.json so we remove template based ones - runCommand( 'package set name="#arguments.name#"' ); - runCommand( 'package set slug="#variables.formatterUtil.slugify( arguments.name )#' ); - runCommand( 'package set version="1.0.0"' ); - runCommand( 'package set location=""' ); - runCommand( 'package set scripts={}' ); + command( 'package set' ) + .params( + name=arguments.name, + slug=variables.formatterUtil.slugify( arguments.name ), + version='1.0.0', + location='', + scripts='{}' + ) + .run(); } /** From c54b3008e7c9a22e25d4ba8e814288446369a9d6 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 24 Sep 2018 12:37:47 -0500 Subject: [PATCH 30/49] COMMANDBOX-868 --- .../coldbox-commands/commands/coldbox/create/app.cfc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc index ab840d304..ebf12992d 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc @@ -108,7 +108,7 @@ component { directory = arguments.directory, save = false, saveDev = false, - production = true, + production = false, currentWorkingDirectory = arguments.directory ); From 8841b63add25d24b52b4af12b1e082ed8fc3d10c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 24 Sep 2018 13:04:22 -0500 Subject: [PATCH 31/49] COMMANDBOX-869 --- src/cfml/system/services/ServerEngineService.cfc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/services/ServerEngineService.cfc b/src/cfml/system/services/ServerEngineService.cfc index 6a79cfaec..4724b442b 100644 --- a/src/cfml/system/services/ServerEngineService.cfc +++ b/src/cfml/system/services/ServerEngineService.cfc @@ -62,13 +62,15 @@ component accessors="true" singleton="true" { var runtimeConfigDoc = XMLParse( runtimeConfigPath ); // Looking for a tag whose sibling is a tag with a "name" attribute of "/CFIDE". var results = xmlSearch( runtimeConfigDoc, "//struct/var[@name='/CFIDE']/string" ); - // If we found a node in the XML - if( results.len() - // And it is blank - && !len( results[ 1 ].XMLText ) + var oldCFIDEPath = ''; + if( results.len() ) { + oldCFIDEPath = results[ 1 ].XMLText; + } + + if( !len( oldCFIDEPath ) // OR points to a nonexistent directory that is not what we think it should be. - || ( !directoryExists( results[ 1 ].XMLText ) - && results[ 1 ].XMLText != CFIDEPath ) ) { + || ( !directoryExists( oldCFIDEPath ) + && oldCFIDEPath != CFIDEPath ) ) { // Here you go, sir. results[ 1 ].XMLText = CFIDEPath; From 1b24b71992fc17d4c0b9b8c8f0841e75ced27f23 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Mon, 24 Sep 2018 15:19:45 -0700 Subject: [PATCH 32/49] Allow params() to be called more than once (#170) --- src/cfml/system/util/CommandDSL.cfc | 41 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index 889f9cfa5..e71c6a8aa 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -21,6 +21,7 @@ component accessors=true { property name='overwrite'; property name='workingDirectory'; property name='rawParams'; + property name='paramsType'; // DI @@ -47,6 +48,7 @@ component accessors=true { setOverwrite( '' ); setWorkingDirectory( '' ); setRawParams( false ); + setParamsType( 'none' ); return this; } @@ -54,8 +56,29 @@ component accessors=true { * Add params to the command **/ function params() { - - setParams( arguments ); + // Positional params + if ( isNumeric( listFirst( structKeyList( arguments ) ) ) ) { + if ( getParamsType() == 'named' ) { + throw( + message = 'You have passed both named and positional params to the command DSL. Named and positional params cannot be mixed.', + type = 'commandException' + ); + } + setParamsType( 'positional' ); + // Named params + } else { + if ( getParamsType() == 'positional' ) { + throw( + message = 'You have passed both named and positional params to the command DSL. Named and positional params cannot be mixed.', + type = 'commandException' + ); + } + if ( getParamsType() == 'none' ) { + setParams( {} ); + } + setParamsType( 'named' ); + } + getParams().append( arguments, true ); return this; } @@ -65,28 +88,28 @@ component accessors=true { private array function processParams() { var runCommand = ( getCommand().startsWith( '!' ) || getCommand().left( 3 ) == 'run' ); var processedParams = []; - if( !arraylen( getParams() ) ) { + if( getParamsType() == 'none' ) { return processedParams; } // Positional params - if( isNumeric( listFirst( structKeyList( getParams() ) ) ) ) { + if( getParamsType() == 'positional' ) { for( var param in getParams() ) { if( runCommand ) { - processedParams.append( getParams()[ param ] ); + processedParams.append( param ); } else if( getRawParams() ) { - processedParams.append( '"#getParams()[ param ]#"' ); + processedParams.append( '"#param#"' ); } else { - processedParams.append( '"#parser.escapeArg( getParams()[ param ] )#"' ); + processedParams.append( '"#parser.escapeArg( param )#"' ); } } // Named params } else { for( var param in getParams() ) { if( runCommand ) { - processedParams.append( '#param#=#getParams()[ param ]#' ); + processedParams.append( '#param#=#getParams()[ param ]#' ); } else { - processedParams.append( '#param#="#parser.escapeArg( getParams()[ param ] )#"' ); + processedParams.append( '#param#="#parser.escapeArg( getParams()[ param ] )#"' ); } } } From 74489638f7218c1520b31287667e9a579cd8d79e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 24 Sep 2018 17:37:10 -0500 Subject: [PATCH 33/49] COMMANDBOX-864 --- .../system/modules/JSONPrettyPrint/README.md | 35 +++- .../system/modules/JSONPrettyPrint/box.json | 6 +- .../JSONPrettyPrint/models/CFMLPrinter.cfc | 79 ++++++++ .../models/JSONPrettyPrint.cfc | 182 +++++------------- .../JSONPrettyPrint/models/JSONPrinter.cfc | 93 +++++++++ .../models/JSONSimpleParser.cfc | 162 ++++++++++++++++ 6 files changed, 407 insertions(+), 150 deletions(-) create mode 100644 src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc create mode 100644 src/cfml/system/modules/JSONPrettyPrint/models/JSONPrinter.cfc create mode 100644 src/cfml/system/modules/JSONPrettyPrint/models/JSONSimpleParser.cfc diff --git a/src/cfml/system/modules/JSONPrettyPrint/README.md b/src/cfml/system/modules/JSONPrettyPrint/README.md index 570453954..6307620cc 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/README.md +++ b/src/cfml/system/modules/JSONPrettyPrint/README.md @@ -1,7 +1,7 @@ [![Build Status](https://travis-ci.org/Ortus-Solutions/JSONPrettyPrint.svg?branch=master)](https://travis-ci.org/Ortus-Solutions/JSONPrettyPrint) -Pretty print JSON objects with line breaks and indentation to make it more human readable. -If you have an app that writes JSON files that humans need to easily be able to read, run the JSON through this library first. It doesn't modify the data at all-- only the whitespace. +Pretty print JSON objects with line breaks and indentation to make it more human readable. +If you have an app that writes JSON files that humans need to easily be able to read, run the JSON through this library first. By default, it doesn't modify the data at all-- only the whitespace. It can, however, sort JSON object keys for you if you wish. Turns this: ```js @@ -36,31 +36,46 @@ CommandBox> install JSONPrettyPrint ``` ## Usage - + ```js var formatted = getInstance( 'JSONPrettyPrint' ).formatJSON( '{ "foo" : "bar" }' ); ``` Or pass a complex CFML object and it will serialize for you. - + ```js var formatted = getInstance( 'JSONPrettyPrint' ).formatJSON( { foo : 'bar' } ); ``` -You can customize the indent chars used or the line break chars. The default is an indent of 4 spaces and CRLF for line endings. - +You can customize the indent characters and the line break characters used for formatting. The defaults are an indent of 4 spaces, and a CRLF line ending on Windows and LF otherwise. You can also pass in a `sortKeys` argument of `"text"` or `"textnocase"` to have JSONPrettyPrint sort JSON object keys when formatting. + ```js var formatted = getInstance( 'JSONPrettyPrint' ).formatJSON( json={ foo : 'bar' }, indent=' ', lineEnding=chr( 10 ) ); +var formatted = getInstance( 'JSONPrettyPrint' ).formatJSON( json={ b: 1, a: 2 }, sortKeys='text' ); ``` -The `JSONPrettyPrint` is a threadsafe singleton and suitable for injection. Inject the library like so: +`JSONPrettyPrint` is a threadsafe singleton and suitable for injection. Inject the library like so: ```js component { property name='JSONPrettyPrint' inject; - + function writeJSON( required JSON, required path ) { - fileWrite( path, JSONPrettyPrint.formatJSON( JSON ) ); + fileWrite( path, JSONPrettyPrint.formatJSON( JSON ) ); } } -``` \ No newline at end of file +``` + +#### Under the Hood + +JSONPrettyPrint uses an alternate JSON formatter on Lucee 5. Lucee 5 deserializes JSON to ordered structs and preserves data types accurately when serializing to JSON, so it is possible to use `deserializeJSON()` and then work from native CFML types to print out formatted JSON without losing key order or data types. This approach is advantageous as it results in a significant performance boost. Adobe ColdFusion does not preserve key order when deserializing and, prior to ACF 2018, it does not accurately serialize to JSON where strings can be cast to boolean or numeric data types. Because of this the Lucee 5 formatter is not used by default. However, if you are using ColdFusion, want the performance boost of the Lucee 5 formatter, and you don't mind the loss of key order or the possible inaccuracy of data types (on versions prior to ACF 2018) you can ask WireBox for the Lucee 5 formatter directly: + +```js +component { + property name='JSONPrettyPrint' inject="CFMLPrinter@JSONPrettyPrint"; + + function writeJSON( required JSON, required path ) { + fileWrite( path, JSONPrettyPrint.formatJSON( JSON ) ); + } +} +``` diff --git a/src/cfml/system/modules/JSONPrettyPrint/box.json b/src/cfml/system/modules/JSONPrettyPrint/box.json index 50a91e3e0..88bae9413 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/box.json +++ b/src/cfml/system/modules/JSONPrettyPrint/box.json @@ -1,8 +1,8 @@ { "name":"JSONPrettyPrint", - "version":"1.2.6", + "version":"1.3.0", "author":"", - "location":"Ortus-Solutions/JSONPrettyPrint#v1.2.6", + "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.0", "homepage":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "documentation":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "repository":{ @@ -21,7 +21,7 @@ }, "installPaths":{ "testbox":"testbox", - "coldbox":"tests/resources/app/coldbox/" + "coldbox":"tests/resources/app/coldbox" }, "scripts":{ "postVersion":"package set location='Ortus-Solutions/JSONPrettyPrint#v`package version`'", diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc new file mode 100644 index 000000000..806cd9b6b --- /dev/null +++ b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc @@ -0,0 +1,79 @@ +component { + public any function init() { + var osName = createObject( 'java', 'java.lang.System' ).getProperty( 'os.name' ); + variables.defaultLineEnding = osName.findNoCase( 'windows' ) ? chr( 13 ) & chr( 10 ) : chr( 10 ); + variables.defaultIndent = ' '; + return this; + } + + /** + * Pretty JSON + * @json A string containing JSON, or a complex value that can be serialized to JSON + * @indent String to use for indenting lines. Defaults to four spaces. + * @lineEnding String to use for line endings. Defaults to CRLF on Windows and LF on *nix + * @spaceAfterColon Add space after each colon like "value": true instead of"value":true + * @sortKeys Specify a sort type to sort the keys of json objects: "text" or "textnocase" + **/ + public string function formatJson( + required any json, + string indent = defaultIndent, + string lineEnding = defaultLineEnding, + boolean spaceAfterColon = false, + string sortKeys = '' + ) { + if ( isSimpleValue( json ) ) { + json = deserializeJSON( json ); + } + var settings = { + indent: indent, + lineEnding: lineEnding, + colon: spaceAfterColon ? ': ' : ':', + sortKeys: sortKeys + }; + return printString( json, settings ); + } + + private string function printString( json, settings, baseIndent = '' ) { + if ( isStruct( json ) ) { + if ( structIsEmpty( json ) ) { + return '{}'; + } + var keys = json.keyArray(); + if ( len( settings.sortKeys ) ) { + keys.sort( settings.sortKeys ); + } + var strs = [ ]; + for ( var key in keys ) { + var str = baseIndent & settings.indent & '"#key#"' & settings.colon; + if ( !structKeyExists( json, key ) || isNull( json[ key ] ) ) { + str &= 'null'; + } else { + str &= printString( json[ key ], settings, baseIndent & settings.indent ); + } + strs.append( str ); + } + return '{' & settings.lineEnding & strs.toList( ',' & settings.lineEnding ) & settings.lineEnding & baseIndent & '}'; + } + if ( isArray( json ) ) { + if ( arrayIsEmpty( json ) ) { + return '[]'; + } + var strs = [ ]; + for ( var item in json ) { + var str = baseIndent & settings.indent; + if ( isNull( item ) ) { + str &= 'null'; + } else { + str &= printString( item, settings, baseIndent & settings.indent ); + } + strs.append( str ); + } + return '[' & settings.lineEnding & strs.toList( ',' & settings.lineEnding ) & settings.lineEnding & baseIndent & ']'; + } + /* + Simple types don't require any special formatting so we can let + serializeJSON convert them to JSON for us. + */ + return serializeJSON( json ); + } +} diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrettyPrint.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrettyPrint.cfc index 97b1d3755..9edbb23d5 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrettyPrint.cfc +++ b/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrettyPrint.cfc @@ -4,147 +4,55 @@ * www.coldbox.org | www.ortussolutions.com ******************************************************************************** * @author Brad Wood, Luis Majano -* +* */ component accessors="true" singleton alias='JSONPrettyPrint' { - function init() { - variables.os = createObject( "java", "java.lang.System" ).getProperty( "os.name" ).toLowerCase(); - return this; - } - - // OS detector - private boolean function isWindows(){ return variables.os.contains( "win" ); } + property name="CFMLPrinter" inject="CFMLPrinter@JSONPrettyPrint"; + property name="JSONPrinter" inject="JSONPrinter@JSONPrettyPrint"; - /** - * Pretty JSON - * @json A string containing JSON, or a complex value that can be serialized to JSON - * @indent String to use for indenting lines. Defaults to four spaces. - * @lineEnding String to use for line endings. Defaults to CRLF on Windows and LF on *nix - * @spaceAfterColon Add space after each colon like "value": true instead of"value":true - **/ - public function formatJson( any json, string indent=' ', lineEnding, boolean spaceAfterColon=false ) { - - // Default line ending based on OS - if( isNull( arguments.lineEnding ) ) { - if( isWindows() ) { - arguments.lineEnding = chr( 13 ) & chr( 10 ); - } else { - arguments.lineEnding = chr( 10 ); - } - } - - // Overload this method to accept a struct or array - if( !isSimpleValue( arguments.json ) ) { - arguments.json = serializeJSON( arguments.json ); - } - - arguments.json = arguments.json.replace( ' ', '', 'all' ); - arguments.json = arguments.json.replace( chr( 10 ), '', 'all' ); - arguments.json = arguments.json.replace( chr( 13 ), '', 'all' ); - - var retval = createObject( 'java', 'java.lang.StringBuilder' ).init( '' ); - var str = json; - var strLen = str.len(); - var pos = 0; - var strLen = str.length(); - var indentStr = arguments.indent; - var newLine = arguments.lineEnding; - var char = ''; - var nextChar = ''; - var inQuote = false; - var isEscaped = false; - var itemsInCollection = []; + property name="DefaultPrinter" type="string"; - for ( var i=0; i i+offset && nextChar == ' ' ) { - nextChar = str.mid( i+offset+1, 1 ); - offset++; - } - - if( nextChar != ']' && nextChar != '}' ) { - itemsInCollection.append( true ); - retval.append( newLine ); - retval.append( repeatString( indentStr, pos ) ); - } else { - itemsInCollection.append( false ); - } - } - - // Colon between key and value. - if (char == ':' ) { - if( spaceAfterColon ) { - retval.append( ' ' ); - } - } - - } // End for loop - - return retval.toString(); - } + function init() { + if ( server.coldfusion.productname == 'Lucee' && listFirst( server.lucee.version, '.' ) >= 5 ) { + setDefaultPrinter( 'CFMLPrinter' ); + } else { + setDefaultPrinter( 'JSONPrinter' ); + } + variables.os = createObject( 'java', 'java.lang.System' ).getProperty( 'os.name' ).toLowerCase(); + return this; + } + + // OS detector + private boolean function isWindows() { + return variables.os.contains( 'win' ); + } + + /** + * Pretty JSON + * @json A string containing JSON, or a complex value that can be serialized to JSON + * @indent String to use for indenting lines. Defaults to four spaces. + * @lineEnding String to use for line endings. Defaults to CRLF on Windows and LF on *nix + * @spaceAfterColon Add space after each colon like "value": true instead of"value":true + * @sortKeys Specify a sort type to sort the keys of json objects: "text" or "textnocase" + **/ + public function formatJson( + any json, + string indent = ' ', + lineEnding, + boolean spaceAfterColon = false, + string sortKeys = '' + ) { + // Default line ending based on OS + if ( isNull( arguments.lineEnding ) ) { + if ( isWindows() ) { + arguments.lineEnding = chr( 13 ) & chr( 10 ); + } else { + arguments.lineEnding = chr( 10 ); + } + } + + return variables[ getDefaultPrinter() ].formatJson( argumentCollection = arguments ); + } } diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrinter.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrinter.cfc new file mode 100644 index 000000000..44342125a --- /dev/null +++ b/src/cfml/system/modules/JSONPrettyPrint/models/JSONPrinter.cfc @@ -0,0 +1,93 @@ +component accessors="true" { + + property name="parser" inject="JSONSimpleParser@JSONPrettyPrint"; + + public any function init() { + var osName = createObject( 'java', 'java.lang.System' ).getProperty( 'os.name' ); + variables.defaultLineEnding = osName.findNoCase( 'windows' ) ? chr( 13 ) & chr( 10 ) : chr( 10 ); + variables.defaultIndent = ' '; + return this; + } + + /** + * Pretty JSON + * @json A string containing JSON, or a complex value that can be serialized to JSON + * @indent String to use for indenting lines. Defaults to four spaces. + * @lineEnding String to use for line endings. Defaults to CRLF on Windows and LF on *nix + * @spaceAfterColon Add space after each colon like "value": true instead of"value":true + * @sortKeys Specify a sort type to sort the keys of json objects: "text" or "textnocase" + **/ + public string function formatJson( + required any json, + string indent = defaultIndent, + string lineEnding = defaultLineEnding, + boolean spaceAfterColon = false, + string sortKeys = '' + ) { + if ( !isSimpleValue( json ) ) { + json = serializeJSON( json ); + } + var parsedJSON = parser.parse( json ); + var settings = { + indent: indent, + lineEnding: lineEnding, + colon: spaceAfterColon ? ': ' : ':', + sortKeys: sortKeys + }; + return printByType( json, parsedJSON, settings ); + } + + private string function printByType( json, parsedJSON, settings, indent='' ) { + switch ( parsedJSON.type ) { + case 'object': + return printObject( json, parsedJSON, settings, indent ); + case 'array': + return printArray( json, parsedJSON, settings, indent ); + case 'element': + return printElement( json, parsedJSON ); + } + } + + private string function printElement( json, parsedJSON ) { + return mid( json, parsedJSON.start + 1, parsedJSON.end - parsedJSON.start ); + } + + private string function printArray( json, parsedJSON, settings, indent ) { + if ( !parsedJSON.elements.len() ) return '[]'; + var nested_indent = indent & settings.indent; + var printed_elements = parsedJSON.elements + .map( function( e ) { + return nested_indent & printByType( json, e, settings, nested_indent ); + } ) + .toList( ',' & settings.lineEnding ); + + return '[' & settings.lineEnding & printed_elements & settings.lineEnding & indent & ']'; + } + + private string function printObject( json, parsedJSON, settings, indent ) { + if ( !parsedJSON.elements.len() ) return '{}'; + var nested_indent = indent & settings.indent; + var elementData = parsedJSON.elements.reduce( function( r, e ) { + var key = printElement( json, e.key ); + r.keys.append( key ); + r.valuesByKey[ key ] = printByType( + json, + e.value, + settings, + nested_indent + ); + return r; + }, { keys: [ ], valuesByKey: { } } ); + + if ( len( settings.sortKeys ) ) { + elementData.keys.sort( settings.sortKeys ); + } + + var printed_elements = elementData.keys.map( function( k ) { + return nested_indent & k & settings.colon & elementData.valuesByKey[ k ]; + } ) + .toList( ',' & settings.lineEnding ); + + return '{' & settings.lineEnding & printed_elements & settings.lineEnding & indent & '}'; + } +} diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/JSONSimpleParser.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/JSONSimpleParser.cfc new file mode 100644 index 000000000..37b1f75ab --- /dev/null +++ b/src/cfml/system/modules/JSONPrettyPrint/models/JSONSimpleParser.cfc @@ -0,0 +1,162 @@ +component { + + variables.regex = { + object_start: '\s*\{', + object_end: '\s*[\},]', + object_key: '\s*(?""|"(?:[^"]|\\")*[^\\]")\s*:', + array_start: '\s*\[', + array_end: '\s*[\],]', + element: '\s*(?""|"(?:[^"\\]|\\.)+"|[^\s",\{\}\[\]]+)', + newline: '\r?\n|$' + }; + variables.patterns = { }; + + public any function init() { + var patternClass = createObject( 'java', 'java.util.regex.Pattern' ); + for ( var regex_name in regex ) { + variables.patterns[ regex_name ] = patternClass.compile( regex[ regex_name ] ); + } + return this; + } + + public struct function parse( src, pos = 0 ) { + var parsed = parseObject( src, pos ); + if ( !isNull( parsed ) ) return parsed; + var parsed = parseArray( src, pos ); + if ( !isNull( parsed ) ) return parsed; + var parsed = parseElement( src, pos ); + if ( !isNull( parsed ) ) return parsed; + invalidJSON( src, pos ); + } + + private any function parseObject( src, pos ) { + var matcher = patterns.object_start.matcher( src ).region( pos, len( src ) ); + if ( !matcher.lookingAt() ) return; + + var parsed = { + type: 'object', + elements: [ ] + }; + + pos = matcher.end(); + + var object_end_matcher = patterns.object_end.matcher( src ); + var looking_for_element = true; + + while ( true ) { + object_end_matcher.region( pos, len( src ) ); + if ( object_end_matcher.lookingAt() ) { + if ( src.mid( object_end_matcher.end(), 1 ) == '}' ) { + parsed.end = object_end_matcher.end(); + break; + } + if ( looking_for_element ) invalidJSON( src, pos ); + looking_for_element = true; + pos = object_end_matcher.end(); + } + + if ( looking_for_element ) { + var keyvalue = parseKeyValue( src, pos ); + if ( !isNull( keyvalue ) ) { + parsed.elements.append( keyvalue ); + looking_for_element = false; + pos = keyvalue.end; + continue; + } + } + + invalidJSON( src, pos ); + } + + return parsed; + } + + private any function parseArray( src, pos ) { + var matcher = patterns.array_start.matcher( src ).region( pos, len( src ) ); + if ( !matcher.lookingAt() ) return; + + var parsed = { + type: 'array', + elements: [ ] + }; + + pos = matcher.end(); + + var array_end_matcher = patterns.array_end.matcher( src ); + var looking_for_element = true; + + while ( true ) { + array_end_matcher.region( pos, len( src ) ); + if ( array_end_matcher.lookingAt() ) { + parsed.end = array_end_matcher.end(); + if ( src.mid( array_end_matcher.end(), 1 ) == ']' ) break; + if ( looking_for_element ) invalidJSON( src, pos ); + looking_for_element = true; + pos = parsed.end; + } + if ( looking_for_element ) { + var value = parse( src, pos ); + if ( !isNull( value ) ) { + parsed.elements.append( value ); + pos = value.end; + looking_for_element = false; + continue; + } + } + + invalidJSON( src, pos ); + } + + return parsed; + } + + private any function parseElement( src, pos ) { + var matcher = patterns.element.matcher( src ).region( pos, len( src ) ); + if ( matcher.lookingAt() ) { + return { + type: 'element', + start: matcher.start( 'token' ), + end: matcher.end( 'token' ) + }; + } + } + + private any function parseKeyValue( src, pos ) { + var parsed = { type: 'keyvalue' }; + + // key + var matcher = patterns.object_key.matcher( src ).region( pos, len( src ) ); + if ( !matcher.lookingAt() ) return; + + parsed.key = { + type: 'element', + start: matcher.start( 'token' ), + end: matcher.end( 'token' ) + }; + + pos = matcher.end(); + + // value + parsed.value = parse( src, pos ); + if ( isNull( parsed.value ) ) invalidJSON( src, pos ); + + parsed.start = parsed.key.start; + parsed.end = parsed.value.end; + return parsed; + } + + private void function invalidJSON( src, pos ) { + throw( 'Unable to parse JSON on line #lineFromChar( src, pos )#' ); + } + + private numeric function lineFromChar( src, pos ) { + var lineNum = 1; + var matcher = patterns.newline.matcher( src ); + while ( matcher.find() ) { + if ( matcher.end() >= pos ) break; + lineNum++; + } + return lineNum; + } + +} From fed717bed4453103e7ca939b8d21a1b6f16a7f06 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Tue, 25 Sep 2018 06:00:27 -0700 Subject: [PATCH 34/49] Fix for null params in Command DSL (#171) It seems null args have to be explicitly replaced with empty strings once an arguments object is converted to a struct. --- src/cfml/system/util/CommandDSL.cfc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index e71c6a8aa..1c67e1561 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -105,11 +105,12 @@ component accessors=true { } // Named params } else { - for( var param in getParams() ) { + var paramStruct = getParams(); + for( var param in paramStruct ) { if( runCommand ) { - processedParams.append( '#param#=#getParams()[ param ]#' ); + processedParams.append( '#param#=#paramStruct[ param ] ?: ''#' ); } else { - processedParams.append( '#param#="#parser.escapeArg( getParams()[ param ] )#"' ); + processedParams.append( '#param#="#parser.escapeArg( paramStruct[ param ] ?: '' )#"' ); } } } From 33797f7a5e03d91845f7cefe6c4e34b0e6011c6c Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 25 Sep 2018 21:52:49 -0500 Subject: [PATCH 35/49] COMMANDBOX-871 --- .../system/modules_app/system-commands/commands/forEach.cfc | 2 +- src/cfml/system/util/CommandDSL.cfc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc index 1176b7f57..e86553dd7 100644 --- a/src/cfml/system/modules_app/system-commands/commands/forEach.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/forEach.cfc @@ -62,7 +62,7 @@ component { // Set this as a localized environment variable so the command can access it. systemSettings.setSystemSetting( itemName, line ); - theCommand.run(); + theCommand.run(); } } diff --git a/src/cfml/system/util/CommandDSL.cfc b/src/cfml/system/util/CommandDSL.cfc index 1c67e1561..70224b540 100644 --- a/src/cfml/system/util/CommandDSL.cfc +++ b/src/cfml/system/util/CommandDSL.cfc @@ -178,7 +178,7 @@ component accessors=true { array function getTokens() { var tokens = []; // Break the command name on the spaces - tokens.append( listToArray( getCommand(), ' ' ), true ); + tokens.append( parser.tokenizeInput( getCommand() ), true ); tokens.append( processParams(), true ); tokens.append( getFlags(), true ); From 69419b4b7fa7c20168b7880d254c24376af26395 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 25 Sep 2018 23:41:17 -0500 Subject: [PATCH 36/49] COMMANDBOX-824 --- src/cfml/system/config/box.json.txt | 3 +- .../testbox-commands/commands/testbox/run.cfc | 30 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/cfml/system/config/box.json.txt b/src/cfml/system/config/box.json.txt index eb662d74f..f0e47bbf6 100644 --- a/src/cfml/system/config/box.json.txt +++ b/src/cfml/system/config/box.json.txt @@ -72,6 +72,7 @@ "testSpecs":"", "verbose":true, "watchDelay":500, - "watchPaths":"**.cfc" + "watchPaths":"**.cfc", + "options":{} } } \ No newline at end of file diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc index ecfb9a74c..956d82b3d 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc @@ -35,6 +35,17 @@ * {code:bash} * testbox run /tests/runner.cfm * {code} +* . +* You can set arbitrary URL options in our box.json like so +* {code:bash} +* package set testbox.options.opt1=value1 +* package set testbox.options.opt2=value2 +* {code} +* . +* You can set arbitrary URL options when you run the command like so +* {code:bash} +* testbox run options:opt1=value1 options:opt2=value2 +* {code} * **/ component { @@ -53,7 +64,7 @@ component { * @recurse Recurse the directory mapping or not, by default it does * @reporter The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or a class path to the reporter to use. * @labels The list of labels that a suite or spec must have in order to execute. - * @options A JSON struct literal of configuration options that are optionally used to configure a runner. + * @options Add adhoc URL options to the runner as options:name=value options:name2=value2 * @testBundles A list or array of bundle names that are the ones that will be executed ONLY! * @testSuites A list of suite names that are the ones that will be executed ONLY! * @testSpecs A list of test names that are the ones that will be executed ONLY! @@ -67,7 +78,7 @@ component { boolean recurse, string reporter, string labels, - string options, + struct options={}, string testBundles, string testSuites, string testSpecs, @@ -133,21 +144,30 @@ component { for( var thisOption in RUNNER_OPTIONS ){ // Check argument overrides if( !isNull( arguments[ thisOption ] ) ){ - testboxURL &= "&#thisOption#=#arguments[ thisOption ]#"; + testboxURL &= "&#encodeForURL( thisOption )#=#encodeForURL( arguments[ thisOption ] )#"; } // Check runtime options now else if( boxOptions.keyExists( thisOption ) && len( boxOptions[ thisOption ] ) ){ if( isSimpleValue( boxOptions[ thisOption ] ) ) { - testboxURL &= "&#thisOption#=#boxOptions[ thisOption ]#"; + testboxURL &= "&#encodeForURL( thisOption )#=#encodeForURL( boxOptions[ thisOption ] )#"; } else { print.yellowLine( 'Ignoring [testbox.#thisOption#] in your box.json since it''s not a string. We can''t append it to a URL like that.' ); } } // Defaults else if( len( RUNNER_OPTIONS[ thisOption ] ) ) { - testboxURL &= "&#thisOption#=#RUNNER_OPTIONS[ thisOption ]#"; + testboxURL &= "&#encodeForURL( thisOption )#=#encodeForURL( RUNNER_OPTIONS[ thisOption ] )#"; } } + + // Get global URL options from box.json + var extraOptions = boxOptions.options ?: {}; + // Add in command-specific options + extraOptions.append( arguments.options ); + // Append to URL. + for( var opt in extraOptions ) { + testboxURL &= "&#encodeForURL( opt )#=#encodeForURL( extraOptions[ opt ] )#"; + } // Advise we are running print.boldCyanLine( "Executing tests via #testBoxURL# please wait..." ) From af9a2dca40c58f1a757ccaab40ddf0bf5bfed2cb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 26 Sep 2018 12:51:02 -0500 Subject: [PATCH 37/49] Helpful note for missing rewrite log --- .../system/modules_app/server-commands/commands/server/log.cfc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc index aaba84fbd..acc716337 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc @@ -73,6 +73,9 @@ component { if( access ) { print.yellowLine( 'Enable accesss logging with [server set web.accessLogEnable=true]' ); } + if( rewrites ) { + print.yellowLine( 'Enable Rewrite logging with [server set web.rewrites.logEnable=true] and ensure you are started in debug mode.' ); + } } } From b7a877fec802cbeea57430b5a8abe8c02f2d4c5e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 26 Sep 2018 16:00:42 -0500 Subject: [PATCH 38/49] better handling of queries --- .../system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc index 806cd9b6b..84e4315bd 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc +++ b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc @@ -70,6 +70,14 @@ component { } return '[' & settings.lineEnding & strs.toList( ',' & settings.lineEnding ) & settings.lineEnding & baseIndent & ']'; } + // This could be a query, a Java object like a HashMap, or an XML Doc. + // Before giving up, we'll give the CF engine a chance to turn it into something useful. + if( !isSimpleValue( json ) ) { + // Attempt to convert to native JSON data types... + arguments.json = deserializeJSON( serializeJSON( json ) ); + // ... and start over. + return formatJson( argumentCollection=arguments ); + } /* Simple types don't require any special formatting so we can let serializeJSON convert them to JSON for us. From e131586ef709d212d4a4d073dd3422d64de01dee Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 26 Sep 2018 16:03:58 -0500 Subject: [PATCH 39/49] update JSON pretty print --- src/cfml/system/modules/JSONPrettyPrint/box.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cfml/system/modules/JSONPrettyPrint/box.json b/src/cfml/system/modules/JSONPrettyPrint/box.json index 88bae9413..aa94eb90e 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/box.json +++ b/src/cfml/system/modules/JSONPrettyPrint/box.json @@ -1,8 +1,8 @@ { "name":"JSONPrettyPrint", - "version":"1.3.0", + "version":"1.3.1", "author":"", - "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.0", + "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.1", "homepage":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "documentation":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "repository":{ From 59dd8e3925b95bebba3aad7e4716dce1b608f8ba Mon Sep 17 00:00:00 2001 From: John Berquist Date: Wed, 26 Sep 2018 14:41:02 -0700 Subject: [PATCH 40/49] COMMANDBOX-864 (#172) * Support sorting JSON object keys in CommandBox JSON output * Move server.json write to JSONService * Trim trailing whitespace * Allow spaceAfterColon to be configured * Update formatJson calls to not pre serialize to JSON Since it will be working from the native CFML types the formatter would just be deserializing the JSON again. * Update JSON file writes to use new method in JSON Service It will check before writing the JSON so see if it should sort keys or not. * Update JSONService to pre check JSON files on disk for diff Avoid updating JSON file last modified time if not necessary * Fix sortKeys logic * Fix typo * Add javadocs, change setting namespace to "json." * fix re-typo of fileSystemUtil * Remove formatJson sortKeys default * writeOnChangeOnly is always the case now --- src/cfml/system/Shell.cfc | 2 +- src/cfml/system/endpoints/Jar.cfc | 4 +- .../commands/package/list.cfc | 2 +- .../commands/package/outdated.cfc | 2 +- .../commands/server/status.cfc | 8 +- src/cfml/system/services/ConfigService.cfc | 2 +- src/cfml/system/services/JSONService.cfc | 60 +++++++++- src/cfml/system/services/PackageService.cfc | 7 +- src/cfml/system/services/ServerService.cfc | 113 ++++++++---------- src/cfml/system/util/Formatter.cfc | 62 +++++----- src/cfml/system/util/REPLParser.cfc | 2 +- 11 files changed, 155 insertions(+), 109 deletions(-) diff --git a/src/cfml/system/Shell.cfc b/src/cfml/system/Shell.cfc index 158bdf28d..ea4dc2001 100644 --- a/src/cfml/system/Shell.cfc +++ b/src/cfml/system/Shell.cfc @@ -830,7 +830,7 @@ component accessors="true" singleton { if( isArray( result ) ){ return variables.reader.getTerminal().writer().printColumns( result ); } - result = variables.formatterUtil.formatJson( serializeJSON( result ) ); + result = variables.formatterUtil.formatJson( result ); printString( result ); } else if( !isNull( result ) && len( result ) ) { // If there is an active job, print our output through it diff --git a/src/cfml/system/endpoints/Jar.cfc b/src/cfml/system/endpoints/Jar.cfc index aee46a6c7..ce270f821 100644 --- a/src/cfml/system/endpoints/Jar.cfc +++ b/src/cfml/system/endpoints/Jar.cfc @@ -16,7 +16,7 @@ component accessors=true implements="IEndpoint" singleton { property name="progressableDownloader" inject="ProgressableDownloader"; property name="progressBar" inject="ProgressBar"; property name="CR" inject="CR@constants"; - property name='formatterUtil' inject='formatter'; + property name='JSONService' inject='JSONService'; property name='wirebox' inject='wirebox'; property name='S3Service' inject='S3Service'; @@ -63,7 +63,7 @@ component accessors=true implements="IEndpoint" singleton { 'location' : 'jar:#package#', 'type' : 'jars' }; - fileWrite( fullBoxJSONPath, formatterUtil.formatJSON( boxJSON ) ); + JSONService.writeJSONFile( fullBoxJSONPath, boxJSON ); // Here is where our alleged so-called "package" lives. return folderName; diff --git a/src/cfml/system/modules_app/package-commands/commands/package/list.cfc b/src/cfml/system/modules_app/package-commands/commands/package/list.cfc index 2c7e25897..dcc0a790f 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/list.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/list.cfc @@ -58,7 +58,7 @@ component aliases="list" { // JSON output if( arguments.JSON ) { - print.line( formatterUtil.formatJson( serializeJSON( tree ) ) ); + print.line( formatterUtil.formatJson( tree ) ); return; } // normal output diff --git a/src/cfml/system/modules_app/package-commands/commands/package/outdated.cfc b/src/cfml/system/modules_app/package-commands/commands/package/outdated.cfc index bcbbf3d0b..7a67a44e1 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/outdated.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/outdated.cfc @@ -61,7 +61,7 @@ component aliases="outdated" { // JSON output if( arguments.JSON ) { - print.line( formatterUtil.formatJson( serializeJSON( aOutdatedDependencies ) ) ); + print.line( formatterUtil.formatJson( aOutdatedDependencies ) ); return; } diff --git a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc index d69338b19..2dba84872 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc @@ -65,7 +65,7 @@ component aliases='status,server info' { // Display ALL as JSON? if( arguments.showALL && arguments.json ){ print.line( - formatterUtil.formatJson( serializeJSON( servers ) ) + formatterUtil.formatJson( servers ) ); return; } @@ -114,7 +114,7 @@ component aliases='status,server info' { // Format Complex values as JSON } else { print.line( - formatterUtil.formatJson( serializeJSON( thisValue ) ) + formatterUtil.formatJson( thisValue ) ); } @@ -122,7 +122,7 @@ component aliases='status,server info' { // Output the entire object print.line( - formatterUtil.formatJson( serializeJSON( thisServerInfo ) ) + formatterUtil.formatJson( thisServerInfo ) ); } @@ -171,7 +171,7 @@ component aliases='status,server info' { /** * AutoComplete server names - */ + */ function serverNameComplete() { return serverService .getServerNames() diff --git a/src/cfml/system/services/ConfigService.cfc b/src/cfml/system/services/ConfigService.cfc index 839ca78c1..82b74666e 100644 --- a/src/cfml/system/services/ConfigService.cfc +++ b/src/cfml/system/services/ConfigService.cfc @@ -153,7 +153,7 @@ component accessors="true" singleton { * Persists config settings to disk */ function saveConfig(){ - fileWrite( getConfigFilePath(), formatterUtil.formatJSON( serializeJSON( getConfigSettings() ) ) ); + JSONService.writeJSONFile( getConfigFilePath(), getConfigSettings() ); // Update ModuleService ModuleService.overrideAllConfigSettings(); diff --git a/src/cfml/system/services/JSONService.cfc b/src/cfml/system/services/JSONService.cfc index 04806c001..50b1d1824 100644 --- a/src/cfml/system/services/JSONService.cfc +++ b/src/cfml/system/services/JSONService.cfc @@ -10,7 +10,10 @@ component accessors="true" singleton { // DI - property name="logger" inject="logbox:logger:{this}"; + property name="configService" inject="ConfigService"; + property name="fileSystemUtil" inject="FileSystem"; + property name="formatterUtil" inject="Formatter"; + property name="logger" inject="logbox:logger:{this}"; /** * Constructor @@ -204,4 +207,59 @@ component accessors="true" singleton { return props; } + /** + * I write JSON objects to disk after pretty printing them. + * (I also work for CFML objects that can be serialized to JSON.) + * @path.hint The file path to write to + * @json.hint A string containing JSON, or a complex value that can be serialized to JSON + * @locking.hint Set to true to have file system access wrapped in a lock + */ + function writeJSONFile( required string path, required any json, boolean locking = false ) { + var sortKeysIsSet = configService.settingExists( 'json.sortKeys' ); + var sortKeys = configService.getSetting( 'json.sortKeys', 'textnocase' ); + var oldJSON = ''; + + if ( fileExists( path ) ) { + oldJSON = locking ? fileSystemUtil.lockingFileRead( path ) : fileRead( path ); + // if sortKeys is not explicitly set try to determine current file state + if ( !sortKeysIsSet && !isSortedJSON( oldJSON, sortKeys ) ) { + sortKeys = ''; + } + } + + var newJSON = formatterUtil.formatJson( json = json, sortKeys = sortKeys ); + + if ( oldJSON == newJSON ) { + return; + } + + // ensure we are writing to an existing directory + directoryCreate( getDirectoryFromPath( path ), true, true ); + + if ( locking ) { + fileSystemUtil.lockingFileWrite( path, newJSON ); + } else { + fileWrite( path, newJSON ); + } + } + + /** + * I check to see if a JSON object has sorted keys. + * (I also work for CFML objects that can be serialized to JSON.) + * @json.hint A string containing JSON, or a complex value that can be serialized to JSON + * @sortKeys.hint The type of key sorting to check for - i.e. "text" or "textnocase" + */ + function isSortedJSON( required any json, required string sortKeys ) { + if ( isSimpleValue( json ) ) { + json = deserializeJSON( json ); + } + + // simple check - are top level keys sorted, default to true if we can't tell + if ( isStruct( json ) ) { + return json.keyList() == json.keyArray().sort( sortKeys ).toList(); + } + + return true; + } + } diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index c7f3e50a5..d5ed6faf7 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -921,12 +921,7 @@ component accessors="true" singleton { * @directory The directory to write the box.json */ function writePackageDescriptor( required any JSONData, required directory ){ - - if( !isSimpleValue( JSONData ) ) { - JSONData = serializeJSON( JSONData ); - } - - fileWrite( getDescriptorPath( arguments.directory ), formatterUtil.formatJSON( JSONData ) ); + JSONService.writeJSONFile( getDescriptorPath( arguments.directory ), JSONData ); } /** diff --git a/src/cfml/system/services/ServerService.cfc b/src/cfml/system/services/ServerService.cfc index dc6eb1423..b3bd4a4cf 100644 --- a/src/cfml/system/services/ServerService.cfc +++ b/src/cfml/system/services/ServerService.cfc @@ -195,7 +195,7 @@ component accessors="true" singleton { function start( Struct serverProps ){ - + var job = wirebox.getInstance( 'interactiveJob' ); job.start( 'Starting Server', 10 ); @@ -288,16 +288,16 @@ component accessors="true" singleton { serverProps.name = newName; var newServerJSONFile = fileSystemUtil.resolvePath( serverProps.directory ?: '' ) & "/server-#serverProps.name#.json"; // copy the orig server's server.json file to the new file so it starts with the same properties as the original. lots of alternative ways to do this but the file copy works and is simple - if( fileExists( defaultServerConfigFile ) && newServerJSONFile != defaultServerConfigFile ) { - file action='copy' source="#defaultServerConfigFile#" destination="#newServerJSONFile#" mode ='777'; + if( fileExists( defaultServerConfigFile ) && newServerJSONFile != defaultServerConfigFile ) { + file action='copy' source="#defaultServerConfigFile#" destination="#newServerJSONFile#" mode ='777'; } return start( serverProps ); } - + } else { job.addWarnLog( 'Overwriting previous server [#serverInfo.name#].' ); } - + } // ************************************************************************************* @@ -561,22 +561,22 @@ component accessors="true" singleton { serverInfo.SSLEnable = serverProps.SSLEnable ?: serverJSON.web.SSL.enable ?: defaults.web.SSL.enable; serverInfo.HTTPEnable = serverProps.HTTPEnable ?: serverJSON.web.HTTP.enable ?: defaults.web.HTTP.enable; serverInfo.SSLPort = serverProps.SSLPort ?: serverJSON.web.SSL.port ?: defaults.web.SSL.port; - + serverInfo.AJPEnable = serverProps.AJPEnable ?: serverJSON.web.AJP.enable ?: defaults.web.AJP.enable; serverInfo.AJPPort = serverProps.AJPPort ?: serverJSON.web.AJP.port ?: defaults.web.AJP.port; - + // 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 if( len( defaults.web.SSL.certFile ?: '' ) ) { defaults.web.SSL.certFile = fileSystemUtil.resolvePath( defaults.web.SSL.certFile, defaultwebroot ); } serverInfo.SSLCertFile = serverProps.SSLCertFile ?: serverJSON.web.SSL.certFile ?: defaults.web.SSL.certFile; - + // relative keyFile in server.json is resolved relative to the server.json if( isDefined( 'serverJSON.web.SSL.keyFile' ) ) { serverJSON.web.SSL.keyFile = fileSystemUtil.resolvePath( serverJSON.web.SSL.keyFile, defaultServerConfigFileDirectory ); } // relative trayIcon in config setting server defaults is resolved relative to the web root if( len( defaults.web.SSL.keyFile ?: '' ) ) { defaults.web.SSL.keyFile = fileSystemUtil.resolvePath( defaults.web.SSL.keyFile, defaultwebroot ); } serverInfo.SSLKeyFile = serverProps.SSLKeyFile ?: serverJSON.web.SSL.keyFile ?: defaults.web.SSL.keyFile; - + serverInfo.SSLKeyPass = serverProps.SSLKeyPass ?: serverJSON.web.SSL.keyPass ?: defaults.web.SSL.keyPass; serverInfo.rewritesEnable = serverProps.rewritesEnable ?: serverJSON.web.rewrites.enable ?: defaults.web.rewrites.enable; serverInfo.rewritesStatusPath = serverJSON.web.rewrites.statusPath ?: defaults.web.rewrites.statusPath; @@ -588,7 +588,7 @@ component accessors="true" singleton { serverInfo.trayEnable = serverJSON.trayEnable ?: defaults.trayEnable; serverInfo.defaultBaseURL = serverInfo.SSLEnable ? 'https://#serverInfo.host#:#serverInfo.SSLPort#' : 'http://#serverInfo.host#:#serverInfo.port#'; - + // If there's no open URL, let's create a complete one if( !serverInfo.openbrowserURL.len() ) { serverInfo.openbrowserURL = serverInfo.defaultBaseURL; @@ -636,7 +636,7 @@ component accessors="true" singleton { serverInfo.trayOptions = defaults.trayOptions; serverJSON.trayOptions = serverJSON.trayOptions ?: []; - + // global defaults are relative to web root // TODO: perform this recursivley into "items" sub arrays serverInfo.trayOptions = serverInfo.trayOptions.map( function( item ){ @@ -645,25 +645,25 @@ component accessors="true" singleton { } return item; } ); - + // server.json settings are relative to the folder server.json lives // TODO: perform this recursivley into "items" sub arrays serverJSON.trayOptions = serverJSON.trayOptions.map( function( item ){ - if( item.keyExists( 'image' ) && item.image.len() ) { + if( item.keyExists( 'image' ) && item.image.len() ) { item.image = fileSystemUtil.resolvePath( item.image, defaultServerConfigFileDirectory ); } return item; } ); - + // Global trayOptions are always added on top of server.json (but don't overwrite) // trayOptions aren't accepted via command params due to no clean way to provide them serverInfo.trayOptions.append( serverJSON.trayOptions, true ); serverInfo.accessLogEnable = serverJSON.web.accessLogEnable ?: defaults.web.accessLogEnable; serverInfo.GZIPEnable = serverJSON.web.GZIPEnable ?: defaults.web.GZIPEnable; - + serverInfo.rewriteslogEnable = serverJSON.web.rewrites.logEnable ?: defaults.web.rewrites.logEnable; - + // Global defauls are always added on top of whatever is specified by the user or server.json serverInfo.JVMargs = ( serverProps.JVMargs ?: serverJSON.JVM.args ?: '' ) & ' ' & defaults.JVM.args; @@ -820,12 +820,12 @@ component accessors="true" singleton { var displayServerName = processName; var displayEngineName = 'WAR'; } - + // logdir is set above and is different for WARs and CF engines serverInfo.consolelogPath = serverInfo.logdir & '/server.out.txt'; serverInfo.accessLogPath = serverInfo.logDir & '/access.txt'; serverInfo.rewritesLogPath = serverInfo.logDir & '/rewrites.txt'; - + // Find the correct tray icon for this server if( !len( serverInfo.trayIcon ) ) { var iconSize = fileSystemUtil.isWindows() ? '-32px' : ''; @@ -867,10 +867,10 @@ component accessors="true" singleton { var lastFolder = appFileSystemPathDisplay.listLast( '/' ); var middleStuff = appFileSystemPathDisplay.listDeleteAt( pathLength, '/' ).listDeleteAt( 1, '/' ); // Ignoring slashes here. Doesn't need to be exact. - var leftOverLen = max( 50 - (firstFolder.len() + lastFolder.len() ), 1 ); + var leftOverLen = max( 50 - (firstFolder.len() + lastFolder.len() ), 1 ); // This will shorten the path to C:/firstfolder/somes/tuff.../lastFolder/ // with a final result that is close to 50 characters - appFileSystemPathDisplay = firstFolder & '/' & middleStuff.left( leftOverLen ) & '.../' & lastFolder & '/'; + appFileSystemPathDisplay = firstFolder & '/' & middleStuff.left( leftOverLen ) & '.../' & lastFolder & '/'; } serverInfo.trayOptions.prepend( @@ -900,11 +900,11 @@ component accessors="true" singleton { openItems.prepend( { 'label':'Site Home', 'action':'openbrowser', 'url': serverInfo.openbrowserURL, 'image' : expandPath('/commandbox/system/config/server-icons/home.png' ) } ); openItems.prepend( { "label" : "File System", "hotkey" : "B", "action" : "openfilesystem", "path" : serverInfo.appFileSystemPath, "image" : expandPath('/commandbox/system/config/server-icons/folder.png' ) } ); - + serverInfo.trayOptions.prepend( { 'label':'Open...', 'items': openItems, "image" : expandPath('/commandbox/system/config/server-icons/open.png' ) } ); // serverInfo.trayOptions.prepend( { 'label' : 'Restart Server', 'hotkey':'R', 'action' : 'restartserver', 'image': expandPath('/commandbox/system/config/server-icons/home.png' ) } ); - + serverInfo.trayOptions.prepend( { 'label':'Stop Server', 'action':'stopserver', 'image' : expandPath('/commandbox/system/config/server-icons/stop.png' ) } ); // This is due to a bug in RunWar not creating the right directory for the logs @@ -974,7 +974,7 @@ component accessors="true" singleton { .append( '--host' ).append( serverInfo.host ) .append( '--stop-port' ).append( serverInfo.stopsocket ) .append( '--processname' ).append( processName ) - .append( '--log-dir' ).append( serverInfo.logDir ) + .append( '--log-dir' ).append( serverInfo.logDir ) .append( '--server-name' ).append( serverInfo.name ) .append( '--tray-icon' ).append( serverInfo.trayIcon ) .append( '--tray-config' ).append( trayOptionsPath ) @@ -990,7 +990,7 @@ component accessors="true" singleton { // 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 ); } - + if( len( serverInfo.restMappings ) ) { args.append( '--servlet-rest-mappings' ).append( serverInfo.restMappings ); } else { @@ -1017,17 +1017,17 @@ component accessors="true" singleton { .append( '--logaccess-dir' ).append( serverInfo.logDir ); } - + if( serverInfo.rewritesLogEnable ) { args.append( '--urlrewrite-log' ).append( serverInfo.rewritesLogPath ); } - + /* .append( '--logrequests-enable' ).append( true ) .append( '--logrequests-basename' ).append( 'request' ) .append( '--logrequests-dir' ).append( serverInfo.logDir ) */ - - + + if( len( CFEngineName ) ) { args.append( '--cfengine-name' ).append( CFEngineName ); } @@ -1071,17 +1071,17 @@ component accessors="true" singleton { .append( '--http-enable' ).append( serverInfo.HTTPEnable ) .append( '--ssl-enable' ).append( serverInfo.SSLEnable ) .append( '--ajp-enable' ).append( serverInfo.AJPEnable ); - - + + if( serverInfo.HTTPEnable || serverInfo.SSLEnable ) { args .append( '--open-browser' ).append( serverInfo.openbrowser ) - .append( '--open-url' ).append( serverInfo.openbrowserURL ); + .append( '--open-url' ).append( serverInfo.openbrowserURL ); } else { - args.append( '--open-browser' ).append( false ); + args.append( '--open-browser' ).append( false ); } - - + + // Send HTTP port if it's enabled if( serverInfo.HTTPEnable ){ args.append( '--port' ).append( serverInfo.port ) @@ -1096,7 +1096,7 @@ component accessors="true" singleton { if( serverInfo.AJPEnable ){ args.append( '--ajp-port' ).append( serverInfo.AJPPort ); } - + // Send SSL cert info if SSL is enabled and there's cert info if( serverInfo.SSLEnable && serverInfo.SSLCertFile.len() ) { args @@ -1148,7 +1148,7 @@ component accessors="true" singleton { var processBuilder = createObject( "java", "java.lang.ProcessBuilder" ); // Pass array of tokens comprised of command plus arguments args.prepend( serverInfo.javaHome ); - + // In *nix OS's we need to separate the server process from the CLI process // so SIGINTs from Ctrl-C won't also kill previously started servers if( !fileSystemUtil.isWindows() && background ) { @@ -1166,7 +1166,7 @@ component accessors="true" singleton { var cleanedArgs = cr & ' ' & trim( reReplaceNoCase( args.toList( ' ' ), ' (-|"-)', cr & ' \1', 'all' ) ); job.addLog("Server start command: #cleanedargs#"); } - + processBuilder.init( args ); // Conjoin standard error and output for convenience. processBuilder.redirectErrorStream( true ); @@ -1175,17 +1175,17 @@ component accessors="true" singleton { // She'll be coming 'round the mountain when she comes... job.addWarnLog( "The server for #serverInfo.webroot# is starting on #serverInfo.openbrowserURL# ..." ); - + job.complete( serverInfo.debug ); consoleLogger.debug( '.' ); - + // If the user is running a one-off command to start a server or specified the debug flag, stream the output and wait until it's finished starting. var interactiveStart = ( shell.getShellType() == 'command' || serverInfo.debug || !background ); // A reference to the current thread so the thread we're about to spin up can access it. // This may be available as parent thread or something. var thisThread = createObject( 'java', 'java.lang.Thread' ).currentThread(); - variables.waitingOnConsoleStart = false; + variables.waitingOnConsoleStart = false; // Spin up a thread to capture the standard out and error from the server thread name="#threadName#" interactiveStart=interactiveStart serverInfo=serverInfo args=args startTimeout=serverInfo.startTimeout parentThread=thisThread { try{ @@ -1203,12 +1203,12 @@ component accessors="true" singleton { var line = bufferedReader.readLine(); while( !isNull( line ) ){ - + // Log messages from the CF engine or app code writing direclty to std/err out strip off "runwar.context" but leave color coded severity // Ex: // [INFO ] runwar.context: 04/11 15:47:10 INFO Starting Flex 1.5 CF Edition line = reReplaceNoCase( line, '^(#chr( 27 )#\[m\[[^]]*])( runwar\.context: )(.*)', '\1 \3' ); - + // Log messages from runwar itself, simplify the logging category to just "Runwar:" and leave color coded severity // Ex: // [DEBUG] runwar.config: Enabling Proxy Peer Address handling @@ -1231,7 +1231,7 @@ component accessors="true" singleton { .line( line ) .toConsole(); } - + line = bufferedReader.readLine(); } // End of inputStream @@ -1327,7 +1327,7 @@ component accessors="true" singleton { function resolveServerDetails( required struct serverProps ) { - + var job = wirebox.getInstance( 'interactiveJob' ); var locDebug = serverProps.debug ?: false; @@ -1429,7 +1429,7 @@ component accessors="true" singleton { // Get server descriptor again if( locDebug ) { consoleLogger.debug("Switching to the last-used server JSON file for this server: #serverInfo.serverConfigFile#"); } var serverJSON_rawSystemSettings = readServerJSON( serverInfo.serverConfigFile ); - var serverJSON = systemSettings.expandDeepSystemSettings( duplicate( serverJSON_rawSystemSettings ) ); + var serverJSON = systemSettings.expandDeepSystemSettings( duplicate( serverJSON_rawSystemSettings ) ); defaultServerConfigFile = serverInfo.serverConfigFile; // Now that we changed server JSONs, we need to recalculate the webroot. @@ -1452,7 +1452,7 @@ component accessors="true" singleton { // By now we've figured out the name, webroot, and serverConfigFile for this server. // Also return the serverInfo of the last values the server was started with (if ever) - // and the serverJSON setting for the server, if they exist. + // and the serverJSON setting for the server, if they exist. return { defaultName : defaultName, defaultwebroot : defaultwebroot, @@ -1560,7 +1560,7 @@ component accessors="true" singleton { // Same as above-- the IP address/host isn't bound to any local adapters. Probably a host file entry went missing. throw( "The IP address that [#arguments.host#] resovles to can't be bound. If you ping it, does it point to a local network adapter?", 'serverException', e.message & ' ' & e.detail ); } - + return portNumber; } @@ -1603,7 +1603,7 @@ component accessors="true" singleton { portToCheck = serverInfo.SSLPort; } else if( serverInfo.AJPEnable ) { portToCheck = serverInfo.AJPPort; - } + } return !isPortAvailable( serverInfo.host, portToCheck ); } @@ -1650,7 +1650,7 @@ component accessors="true" singleton { * @servers.hint struct of serverInfos **/ ServerService function setServers( required Struct servers ){ - fileSystemUtil.lockingfileWrite( serverConfig, formatterUtil.formatJson( serializeJSON( servers ) ) ); + JSONService.writeJSONFile( serverConfig, servers, true ); return this; } @@ -1800,7 +1800,7 @@ component accessors="true" singleton { * @webroot.hint root directory for served content **/ struct function getServerInfo( required webroot , required name){ - var servers = getServers(); + var servers = getServers(); var normalizedWebroot = normalizeWebroot( arguments.webroot ); var webrootHash = hash( normalizedWebroot & ucase( arguments.name ) ); var statusInfo = {}; @@ -1893,7 +1893,7 @@ component accessors="true" singleton { 'aliases' : {}, 'errorPages' : {}, 'accessLogEnable' : false, - 'GZIPEnable' : true, + 'GZIPEnable' : true, 'rewritesLogEnable' : false, 'trayOptions' : {}, 'trayEnable' : true, @@ -1926,16 +1926,7 @@ component accessors="true" singleton { * Save a server.json file. */ function saveServerJSON( required string configFilePath, required struct data ) { - var oldJSON = ''; - if( fileExists( arguments.configFilePath ) ) { - oldJSON = fileRead( arguments.configFilePath ); - } - var newJSON = formatterUtil.formatJSON( serializeJSON( arguments.data ) ); - // Try to prevent bunping the date modified for no reason - if( oldJSON != newJSON ) { - directoryCreate( getDirectoryFromPath( arguments.configFilePath ), true, true ); - fileWrite( arguments.configFilePath, newJSON ); - } + JSONService.writeJSONFile( configFilePath, data ); } /** diff --git a/src/cfml/system/util/Formatter.cfc b/src/cfml/system/util/Formatter.cfc index e941fc0b2..54a6015cd 100644 --- a/src/cfml/system/util/Formatter.cfc +++ b/src/cfml/system/util/Formatter.cfc @@ -11,6 +11,7 @@ component singleton { // DI + property name="configService" inject="ConfigService"; property name="shell" inject="shell"; property name="print" inject="print"; property name="JSONPrettyPrint" inject="provider:JSONPrettyPrint"; @@ -127,7 +128,7 @@ component singleton { } return text; } - + /** * Converts markdown into ANSI text @@ -139,12 +140,12 @@ component singleton { var codeBlockContents = ''; var formattedText = []; var previousLineNeedsPadding = false; - + if( len( trim( text ) ) == 0 ) { return ""; } - - // Turn all line endings into LF or it will be impossible to + + // Turn all line endings into LF or it will be impossible to // parse the lines and keep empty rows. text = replace( text, CRLF, LF, 'all' ); text = replace( text, CR, LF, 'all' ); @@ -152,20 +153,20 @@ component singleton { text .listToArray( LF, true ) .each( function( line ) { - - + + // Detect the start of code blocks if( line.trim().startsWith( '```' ) && !inCodeBlock ) { inCodeBlock=true; return; } - + // If we're already in a code block if( inCodeBlock ) { // Is this the end of the code block? if( line.trim().startsWith( '```' ) ) { inCodeBlock = false; - + // Turn code block into array, var codeArray = codeBlockContents .listToArray( LF, true ) @@ -175,18 +176,18 @@ component singleton { } ) // pad before and after .prepend( '' ) - + // Find the longest line of code var longestLine = codeArray.reduce( function( prev, codeLine) { return max( prev, codeLine.len() ); }, 0 ) + 2; - + // pad each row so the lengths are all the same codeArray = codeArray.map( function( codeLine ) { return ' ' & print.indentedBlackOnWhite( codeLine & repeatString( ' ', longestLine-codeLine.len() ) ); } ); - - // Turn array back into string with + + // Turn array back into string with line = LF & codeArray.toList( LF ) & print.text( '', additionalFormatting, true ); codeBlockContents = ''; previousLineNeedsPadding = true; @@ -195,59 +196,60 @@ component singleton { return; } } else { - + // Add extra line after heading and code blocks if it's not already there if( previousLineNeedsPadding && !line.trim() == '' ) { formattedText.append( '' ); } - + // Let the next interation know we just had a section heading if( reFindNoCase( '^##{1,4}\s*(.*)$', line ) ) { previousLineNeedsPadding = true; } else { previousLineNeedsPadding = false; } - + // Convert section headings line = reReplaceNoCase( line, '^##{1,4}\s*(.*)$', print.bold( LF & '\1' ) & print.text( '', additionalFormatting, true ) ); - + // Convert inline blocks line = reReplaceNoCase( line, '`([^`]*)`', print.bold( '\1' ) & print.text( '', additionalFormatting, true ), 'all' ); - + // Convert bold blocks line = reReplaceNoCase( line, '\*\*([^`]*)\*\*', print.bold( '\1' ) & print.text( '', additionalFormatting, true ), 'all' ); - + // Convert italics blocks line = reReplaceNoCase( line, '`([^`]*)`', print.bold( '\1' ) & print.text( '', additionalFormatting, true ), 'all' ); - + // Indent lists if( line.startsWith( '* ' ) || line.startsWith( '- ' ) ) { - line = ' ' & line; + line = ' ' & line; } - + // Horizontal Rule if( line.trim().reFind( '^([-]{3,}|[_]{3,}|[*]{3,})$' ) ) { // Repeat across 75% of the terminal width line = repeatString( '_', int( shell.getTermWidth() * .75 ) ); - previousLineNeedsPadding = true; + previousLineNeedsPadding = true; } - + } - + formattedText.append( line ); } ); - - + + return formattedText.toList( LF ); } - + /** * Pretty JSON * @json A string containing JSON, or a complex value that can be serialized to JSON - **/ - public function formatJson( json, indent, lineEnding ) { + **/ + public function formatJson( json, indent, lineEnding, spaceAfterColon, sortKeys ) { // This is an external lib now. Leaving here for backwards compat. - return JSONPrettyPrint.formatJSON( argumentCollection=arguments ); + structAppend( arguments, configService.getSetting( 'json', { } ), false ); + return JSONPrettyPrint.formatJSON( argumentCollection = arguments ); } } diff --git a/src/cfml/system/util/REPLParser.cfc b/src/cfml/system/util/REPLParser.cfc index 0ff0a4cf5..7af14c6a6 100644 --- a/src/cfml/system/util/REPLParser.cfc +++ b/src/cfml/system/util/REPLParser.cfc @@ -121,7 +121,7 @@ component accessors="true" singleton { return '[Object #getMetaData( result ).name#]'; // Serializable types } else if( isArray( result ) || isStruct( result ) || isQuery( result ) ) { - return formatterUtil.formatJson( serializeJson( result ) ); + return formatterUtil.formatJson( result ); // Yeah, I give up } else { return '[#result.getClass().getName()#]'; From cca926ff5e2a164d9c9110dc6b5be65560d0e551 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 26 Sep 2018 16:53:00 -0500 Subject: [PATCH 41/49] Updating JSON pretty print again --- src/cfml/system/modules/JSONPrettyPrint/box.json | 4 ++-- .../system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/modules/JSONPrettyPrint/box.json b/src/cfml/system/modules/JSONPrettyPrint/box.json index aa94eb90e..7e261d5f3 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/box.json +++ b/src/cfml/system/modules/JSONPrettyPrint/box.json @@ -1,8 +1,8 @@ { "name":"JSONPrettyPrint", - "version":"1.3.1", + "version":"1.3.2", "author":"", - "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.1", + "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.2", "homepage":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "documentation":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "repository":{ diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc index 84e4315bd..fb7956e0e 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc +++ b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc @@ -70,13 +70,17 @@ component { } return '[' & settings.lineEnding & strs.toList( ',' & settings.lineEnding ) & settings.lineEnding & baseIndent & ']'; } - // This could be a query, a Java object like a HashMap, or an XML Doc. + // This could be a query, a Java object like a HashMap, or an XML Doc. // Before giving up, we'll give the CF engine a chance to turn it into something useful. if( !isSimpleValue( json ) ) { // Attempt to convert to native JSON data types... arguments.json = deserializeJSON( serializeJSON( json ) ); + // ensure we have something that we can work with + if ( !isStruct( json ) && !isArray( json ) && !isSimpleValue( json ) ) { + throw( 'Sorry, we can''t convert an object of type [#json.getClass().getName()#] to JSON.' ); + } // ... and start over. - return formatJson( argumentCollection=arguments ); + return printString( argumentCollection=arguments ); } /* Simple types don't require any special formatting so we can let From 85afe2e76422c2aa5b7e0c4dcc5b74a0e5212109 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Wed, 26 Sep 2018 21:53:15 -0700 Subject: [PATCH 42/49] Cache S3 bucket region lookups (#173) --- src/cfml/system/services/S3Service.cfc | 29 +++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/cfml/system/services/S3Service.cfc b/src/cfml/system/services/S3Service.cfc index 073136452..e027ab1e4 100644 --- a/src/cfml/system/services/S3Service.cfc +++ b/src/cfml/system/services/S3Service.cfc @@ -19,13 +19,14 @@ component accessors="true" singleton { * Local URL to query for IAM role */ property name="iamRolePath" type="string"; + property name="bucketRegionCache" type="struct"; /** * Constructor */ - function init( - ){ + function init() { setIamRolePath('169.254.169.254/latest/meta-data/iam/security-credentials/'); + setBucketRegionCache({}); return this; } @@ -130,16 +131,20 @@ component accessors="true" singleton { job.addLog('Resolving bucket region'); } - var args = { - urlPath: 'https://s3.#defaultRegion#.amazonaws.com', - method: 'HEAD', - redirect: false, - headers: { - 'Host': '#bucket#.s3.amazonaws.com' - } - }; - var req = makeHTTPRequest(argumentCollection = args); - return req.responseheader['x-amz-bucket-region']; + if (!bucketRegionCache.keyExists(bucket)) { + var args = { + urlPath: 'https://s3.#defaultRegion#.amazonaws.com', + method: 'HEAD', + redirect: false, + headers: { + 'Host': '#bucket#.s3.amazonaws.com' + } + }; + var req = makeHTTPRequest(argumentCollection = args); + bucketRegionCache[bucket] = req.responseheader['x-amz-bucket-region']; + } + + return bucketRegionCache[bucket]; } private function resolveAwsSettings(bucket, verbose=false) { From 2a1e4dfb07300fff6c47cb90904c1761b27eac3a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 27 Sep 2018 00:20:33 -0500 Subject: [PATCH 43/49] COMMANDBOX-872 --- src/cfml/system/BaseCommand.cfc | 29 +++++++++++- src/cfml/system/BaseTask.cfc | 47 ++++++------------- .../commands/coldbox/create/app.cfc | 2 +- .../commands/coldbox/create/handler.cfc | 6 +-- .../coldbox/create/integration-test.cfc | 2 +- .../coldbox/create/interceptor-test.cfc | 2 +- .../commands/coldbox/create/interceptor.cfc | 4 +- .../commands/coldbox/create/layout.cfc | 2 +- .../commands/coldbox/create/model-test.cfc | 2 +- .../commands/coldbox/create/model.cfc | 4 +- .../commands/coldbox/create/module.cfc | 2 +- .../commands/coldbox/create/orm-crud.cfc | 8 ++-- .../commands/coldbox/create/orm-entity.cfc | 4 +- .../coldbox/create/orm-event-handler.cfc | 2 +- .../commands/coldbox/create/orm-service.cfc | 4 +- .../coldbox/create/orm-virtual-service.cfc | 4 +- .../commands/coldbox/create/resource.cfc | 12 ++--- .../commands/coldbox/create/view.cfc | 2 +- .../commands/contentbox/create/module.cfc | 2 +- .../commands/endpoint/publish.cfc | 2 +- .../commands/endpoint/unpublish.cfc | 2 +- .../commands/package/clear.cfc | 2 +- .../commands/package/install.cfc | 2 +- .../commands/package/link.cfc | 2 +- .../commands/package/run-script.cfc | 2 +- .../package-commands/commands/package/set.cfc | 2 +- .../commands/package/show.cfc | 2 +- .../commands/package/uninstall.cfc | 2 +- .../commands/package/unlink.cfc | 2 +- .../server-commands/commands/server/clear.cfc | 4 +- .../commands/server/forget.cfc | 4 +- .../server-commands/commands/server/log.cfc | 4 +- .../server-commands/commands/server/open.cfc | 4 +- .../commands/server/restart.cfc | 4 +- .../server-commands/commands/server/set.cfc | 2 +- .../server-commands/commands/server/show.cfc | 4 +- .../commands/server/status.cfc | 4 +- .../server-commands/commands/server/stop.cfc | 4 +- .../system-commands/commands/cat.cfc | 2 +- .../system-commands/commands/cd.cfc | 2 +- .../system-commands/commands/cp.cfc | 2 +- .../system-commands/commands/execute.cfc | 2 +- .../system-commands/commands/fileAppend.cfc | 2 +- .../system-commands/commands/fileWrite.cfc | 2 +- .../system-commands/commands/mkdir.cfc | 2 +- .../system-commands/commands/mv.cfc | 2 +- .../system-commands/commands/pathExists.cfc | 2 +- .../commands/propertyFile/clear.cfc | 2 +- .../commands/propertyFile/set.cfc | 2 +- .../commands/propertyFile/show.cfc | 2 +- .../system-commands/commands/recipe.cfc | 2 +- .../system-commands/commands/repl.cfc | 2 +- .../system-commands/commands/run.cfc | 2 +- .../system-commands/commands/tail.cfc | 2 +- .../task-commands/commands/task/create.cfc | 2 +- .../task-commands/commands/task/run.cfc | 2 +- .../commands/testbox/create/bdd.cfc | 2 +- .../commands/testbox/create/unit.cfc | 2 +- .../testbox-commands/commands/testbox/run.cfc | 2 +- 59 files changed, 122 insertions(+), 114 deletions(-) diff --git a/src/cfml/system/BaseCommand.cfc b/src/cfml/system/BaseCommand.cfc index 7a4740067..d896bb228 100644 --- a/src/cfml/system/BaseCommand.cfc +++ b/src/cfml/system/BaseCommand.cfc @@ -152,6 +152,19 @@ component accessors="true" singleton { return getinstance( 'watcher' ); } + /** + * This resolves an absolute or relative path using the rules of the operating system and CLI. + * It doesn't follow CF mappings and will also always return a trailing slash if pointing to + * an existing directory. + * + * Resolve the incoming path from the file system + * @path.hint The directory to resolve + * @basePath.hint An expanded base path to resolve the path against. Defaults to CWD. + */ + function resolvePath( required string path, basePath=shell.pwd() ) { + return filesystemUtil.resolvepath( path, basePath ); + } + /** * Return a new globber **/ @@ -168,8 +181,22 @@ component accessors="true" singleton { **/ function propertyFile( propertyFilePath='' ) { var propertyFile = wirebox.getInstance( 'propertyFile@propertyFile' ); + + // If the user passed a propertyFile path if( propertyFilePath.len() ) { - propertyFile.load( propertyFilePath ); + + // Make relative paths resolve to the current folder that the task lives in. + propertyFilePath = resolvePath( propertyFilePath ); + + // If it exists, go ahead and load it now + if( fileExists( propertyFilePath ) ){ + propertyFile.load( propertyFilePath ); + } else { + // Otherwise, just set it so it can be used later on save. + propertyFile + .setPath( propertyFilePath ); + } + } return propertyFile; } diff --git a/src/cfml/system/BaseTask.cfc b/src/cfml/system/BaseTask.cfc index 83af71a97..ed4d903cb 100644 --- a/src/cfml/system/BaseTask.cfc +++ b/src/cfml/system/BaseTask.cfc @@ -12,35 +12,6 @@ component accessors="true" extends='commandbox.system.BaseCommand' { // Tasks mostly just do everything commands do - - /** - * Return a new PropertyFile instance - **/ - function propertyFile( propertyFilePath='' ) { - var propertyFile = wirebox.getInstance( 'propertyFile@propertyFile' ); - - // If the user passed a propertyFile path - if( propertyFilePath.len() ) { - - // Make relative paths resolve to the current folder that the task lives in. - propertyFilePath = fileSystemUtil.resolvePath( - propertyFilePath, - getDirectoryFromPath( getCurrentTemplatePath() ) - ); - - // If it exists, go ahead and load it now - if( fileExists( propertyFilePath ) ){ - propertyFile.load( propertyFilePath ); - } else { - // Otherwise, just set it so it can be used later on save. - propertyFile - .setPath( propertyFilePath ); - } - - } - return propertyFile; - } - /** * Run another task by DSL. * @taskFile The name of the task to run. @@ -61,10 +32,7 @@ component accessors="true" extends='commandbox.system.BaseCommand' { function loadModule( required string moduleDirectory ) { // Expand path relative to the task CFC. - moduleDirectory = fileSystemUtil.resolvePath( - moduleDirectory, - getDirectoryFromPath( getCurrentTemplatePath() ) - ); + moduleDirectory = resolvePath( moduleDirectory ); // A little validation... if( !directoryExists( moduleDirectory ) ) { @@ -85,5 +53,18 @@ component accessors="true" extends='commandbox.system.BaseCommand' { // Load it up!! wirebox.getInstance( 'moduleService' ).registerAndActivateModule( moduleName, invocationPath ); } + + /** + * This resolves an absolute or relative path using the rules of the operating system and CLI. + * It doesn't follow CF mappings and will also always return a trailing slash if pointing to + * an existing directory. + * + * Resolve the incoming path from the file system + * @path.hint The directory to resolve + * @basePath.hint An expanded base path to resolve the path against. Defaults to direcory that the task lives in. + */ + function resolvePath( required string path, basePath=getDirectoryFromPath( getCurrentTemplatePath() ) ) { + return filesystemUtil.resolvepath( path, basePath ); + } } diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc index ebf12992d..9c68143b6 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/app.cfc @@ -89,7 +89,7 @@ component { } // This will make the directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory, if it doesn't exist, create it. if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc index 2bb917230..4fe59651c 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/handler.cfc @@ -37,9 +37,9 @@ component aliases='coldbox create controller' { boolean open=false ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); - arguments.viewsDirectory = fileSystemUtil.resolvePath( arguments.viewsDirectory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.directory = resolvePath( arguments.directory ); + arguments.viewsDirectory = resolvePath( arguments.viewsDirectory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/integration-test.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/integration-test.cfc index 02ce7889e..593974b0e 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/integration-test.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/integration-test.cfc @@ -47,7 +47,7 @@ component { ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor-test.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor-test.cfc index 9c0445dd1..d38d15c7e 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor-test.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor-test.cfc @@ -22,7 +22,7 @@ component { boolean open=false ){ // This will make each directory canonical and absolute - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.testsDirectory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor.cfc index c3c7a929d..9ddc85afa 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/interceptor.cfc @@ -34,8 +34,8 @@ component { ){ // This will make each directory canonical and absolute var relativeDirectory = arguments.directory; - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.directory = resolvePath( arguments.directory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/layout.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/layout.cfc index 5cf0d06c3..503fda97b 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/layout.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/layout.cfc @@ -21,7 +21,7 @@ component { directory='layouts' ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc index 8e713bd5e..08d919c2e 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model-test.cfc @@ -32,7 +32,7 @@ component { boolean open=false ) { // This will make each directory canonical and absolute - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.testsDirectory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model.cfc index 30f75b189..483dd8d74 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/model.cfc @@ -56,8 +56,8 @@ component { // store incoming relative path for testing purposes var modelTestPath = arguments.directory; // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.directory = resolvePath( arguments.directory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/module.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/module.cfc index 5163fa6aa..61ee5e7c6 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/module.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/module.cfc @@ -34,7 +34,7 @@ component { boolean script=true ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-crud.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-crud.cfc index 2e8c90506..148b26f55 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-crud.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-crud.cfc @@ -27,13 +27,13 @@ component { testsDirectory='tests/specs/integration' ) { // This will make each directory canonical and absolute - arguments.handlersDirectory = fileSystemUtil.resolvePath( arguments.handlersDirectory ); - arguments.viewsDirectory = fileSystemUtil.resolvePath( arguments.viewsDirectory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.handlersDirectory = resolvePath( arguments.handlersDirectory ); + arguments.viewsDirectory = resolvePath( arguments.viewsDirectory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // entity defaults var entityName = listLast( arguments.entity, "." ); - var entityCFC = fileSystemUtil.makePathRelative( fileSystemUtil.resolvePath( replace( arguments.entity, ".", "/", "all" ) ) ); + var entityCFC = fileSystemUtil.makePathRelative( resolvePath( replace( arguments.entity, ".", "/", "all" ) ) ); var entityPath = entityCFC & ".cfc"; // verify it diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-entity.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-entity.cfc index dbddcb997..20e413ff7 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-entity.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-entity.cfc @@ -62,8 +62,8 @@ component { // non-canonical path var nonCanonicalDirectory = arguments.directory; // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.directory = resolvePath( arguments.directory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-event-handler.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-event-handler.cfc index 66ae1e214..dc614a594 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-event-handler.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-event-handler.cfc @@ -20,7 +20,7 @@ component { boolean open=false ) { // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-service.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-service.cfc index 1bb0083ce..6126353c4 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-service.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-service.cfc @@ -32,8 +32,8 @@ component { // non-canonical path var nonCanonicalDirectory = arguments.directory; // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.directory = resolvePath( arguments.directory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-virtual-service.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-virtual-service.cfc index 9e34d7b3b..cc989e5e2 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-virtual-service.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/orm-virtual-service.cfc @@ -32,8 +32,8 @@ component { // non-canonical path var nonCanonicalDirectory = arguments.directory; // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); - arguments.testsDirectory = fileSystemUtil.resolvePath( arguments.testsDirectory ); + arguments.directory = resolvePath( arguments.directory ); + arguments.testsDirectory = resolvePath( arguments.testsDirectory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc index 0e081ac2e..7faa7acca 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/resource.cfc @@ -88,9 +88,9 @@ component { ) { // This will make each directory canonical and absolute - arguments.specsDirectory = fileSystemUtil.resolvePath( arguments.specsDirectory ); - arguments.modulesDirectory = fileSystemUtil.resolvePath( arguments.modulesDirectory ); - var configPath = fileSystemUtil.resolvePath( "config" ); + arguments.specsDirectory = resolvePath( arguments.specsDirectory ); + arguments.modulesDirectory = resolvePath( arguments.modulesDirectory ); + var configPath = resolvePath( "config" ); /********************** Verify Module ************************/ @@ -113,9 +113,9 @@ component { arguments.modelsDirectory = modulePath & "/" & arguments.modelsDirectory & "/"; } else { - arguments.handlersDirectory = fileSystemUtil.resolvePath( arguments.handlersDirectory ); - arguments.viewsDirectory = fileSystemUtil.resolvePath( arguments.viewsDirectory ); - arguments.modelsDirectory = fileSystemUtil.resolvePath( arguments.modelsDirectory ); + arguments.handlersDirectory = resolvePath( arguments.handlersDirectory ); + arguments.viewsDirectory = resolvePath( arguments.viewsDirectory ); + arguments.modelsDirectory = resolvePath( arguments.modelsDirectory ); } /********************** GENERATE HANDLER ************************/ diff --git a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/view.cfc b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/view.cfc index 157d8fe64..2ee7ce73c 100644 --- a/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/view.cfc +++ b/src/cfml/system/modules_app/coldbox-commands/commands/coldbox/create/view.cfc @@ -34,7 +34,7 @@ component { } // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/contentbox-commands/commands/contentbox/create/module.cfc b/src/cfml/system/modules_app/contentbox-commands/commands/contentbox/create/module.cfc index be82bc799..d181367e0 100644 --- a/src/cfml/system/modules_app/contentbox-commands/commands/contentbox/create/module.cfc +++ b/src/cfml/system/modules_app/contentbox-commands/commands/contentbox/create/module.cfc @@ -34,7 +34,7 @@ component { boolean script=true ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/package-commands/commands/endpoint/publish.cfc b/src/cfml/system/modules_app/package-commands/commands/endpoint/publish.cfc index 51bd6ac58..5a396fd0b 100644 --- a/src/cfml/system/modules_app/package-commands/commands/endpoint/publish.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/endpoint/publish.cfc @@ -22,7 +22,7 @@ component { boolean force = false ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); var boxJSON = packageService.readPackageDescriptor( arguments.directory ); interceptorService.announceInterception( 'prePublish', { publishArgs=arguments, boxJSON=boxJSON } ); diff --git a/src/cfml/system/modules_app/package-commands/commands/endpoint/unpublish.cfc b/src/cfml/system/modules_app/package-commands/commands/endpoint/unpublish.cfc index 26dd6e17b..224cdbec9 100644 --- a/src/cfml/system/modules_app/package-commands/commands/endpoint/unpublish.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/endpoint/unpublish.cfc @@ -25,7 +25,7 @@ component { boolean force=false ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); var boxJSON = packageService.readPackageDescriptor( arguments.directory ); if( !arguments.force && !confirm( 'Are you sure you want to unpublish? This is destructive and can''t be undone. (y/n)' ) ) { diff --git a/src/cfml/system/modules_app/package-commands/commands/package/clear.cfc b/src/cfml/system/modules_app/package-commands/commands/package/clear.cfc index 871be0de0..8d6284e22 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/clear.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/clear.cfc @@ -56,7 +56,7 @@ component { // Dynamic completion for property name based on contents of box.json function completeProperty() { - var directory = fileSystemUtil.resolvePath( '' ); + var directory = resolvePath( '' ); return packageService.completeProperty( directory ); } 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 04878db1f..015721b30 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 @@ -121,7 +121,7 @@ component aliases="install" { // specifically typed in a param or not since it overrides the package's box.json install dir. if( structKeyExists( arguments, 'directory' ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/package-commands/commands/package/link.cfc b/src/cfml/system/modules_app/package-commands/commands/package/link.cfc index 65c149bc8..7b833cd8b 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/link.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/link.cfc @@ -48,7 +48,7 @@ component aliases='link' { arguments.moduleDrectory = expandPath( '/commandbox/modules' ); commandBoxCoreLinked = true; } else { - arguments.moduleDrectory = fileSystemUtil.resolvePath( arguments.moduleDrectory ); + arguments.moduleDrectory = resolvePath( arguments.moduleDrectory ); } // package check diff --git a/src/cfml/system/modules_app/package-commands/commands/package/run-script.cfc b/src/cfml/system/modules_app/package-commands/commands/package/run-script.cfc index 4acbafaee..5b94ee73f 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/run-script.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/run-script.cfc @@ -18,7 +18,7 @@ component aliases="run-script" { function run( required string scriptname, string directory='' ){ // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // package check if( !packageService.isPackage( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/package-commands/commands/package/set.cfc b/src/cfml/system/modules_app/package-commands/commands/package/set.cfc index d014e73c6..5fd16d70b 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/set.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/set.cfc @@ -86,7 +86,7 @@ component { // Dynamic completion for property name based on contents of box.json function completeProperty() { - var directory = fileSystemUtil.resolvePath( '' ); + var directory = resolvePath( '' ); // all=true will cause "package set" to prompt all possible box.json properties return packageService.completeProperty( directory, true, true ); } diff --git a/src/cfml/system/modules_app/package-commands/commands/package/show.cfc b/src/cfml/system/modules_app/package-commands/commands/package/show.cfc index dce088ccb..1a35ad55f 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/show.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/show.cfc @@ -72,7 +72,7 @@ component { // Dynamic completion for property name based on contents of box.json function completeProperty() { - var directory = fileSystemUtil.resolvePath( '' ); + var directory = resolvePath( '' ); return packageService.completeProperty( directory ); } } diff --git a/src/cfml/system/modules_app/package-commands/commands/package/uninstall.cfc b/src/cfml/system/modules_app/package-commands/commands/package/uninstall.cfc index aa1c486a5..ef87d4725 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/uninstall.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/uninstall.cfc @@ -41,7 +41,7 @@ component aliases="uninstall" { // specifically typed in a param or not since it overrides the package's box.json install dir. if( structKeyExists( arguments, 'directory' ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/package-commands/commands/package/unlink.cfc b/src/cfml/system/modules_app/package-commands/commands/package/unlink.cfc index 542f28a6d..4912f16c9 100644 --- a/src/cfml/system/modules_app/package-commands/commands/package/unlink.cfc +++ b/src/cfml/system/modules_app/package-commands/commands/package/unlink.cfc @@ -34,7 +34,7 @@ component aliases='unlink' { arguments.moduleDrectory = expandPath( '/commandbox/modules' ); commandBoxCoreLinked = true; } else { - arguments.moduleDrectory = fileSystemUtil.resolvePath( arguments.moduleDrectory ); + arguments.moduleDrectory = resolvePath( arguments.moduleDrectory ); } // package check diff --git a/src/cfml/system/modules_app/server-commands/commands/server/clear.cfc b/src/cfml/system/modules_app/server-commands/commands/server/clear.cfc index 659b47772..8360aa7dc 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/clear.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/clear.cfc @@ -25,14 +25,14 @@ component { // As a convenient shorcut, allow the serverConfigFile and propery parameter to be reversed because // "server show foo.json name" reads better than "server show name foo.json" but maintains backwards compat // for the simple use case of no JSON file as in "server show name" - var tmpPropertyResolved = fileSystemUtil.resolvePath( arguments.property ); + var tmpPropertyResolved = resolvePath( arguments.property ); // Check if the property name end with ".json" and happens to exist as a file on disk, if so it's probably the property file if( listLen( arguments.property, '.' ) > 1 && listLast( arguments.property, '.' ) == 'json' && fileExists( tmpPropertyResolved ) ) { // If so, swap the property into the server config param. arguments.property = arguments.serverConfigFile; arguments.serverConfigFile = tmpPropertyResolved; } else if( len( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); if( !fileExists( arguments.serverConfigFile ) ) { error( 'The serverConfigFile does not exist. [#arguments.serverConfigFile#]' ); } diff --git a/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc b/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc index 0028a3d7e..aeed2e169 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/forget.cfc @@ -33,10 +33,10 @@ component { Boolean force=false ){ if( !isNull( arguments.directory ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); } if( !isNull( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } var serverInfo = serverService.resolveServerDetails( arguments ).serverinfo; diff --git a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc index acc716337..d933a0e00 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/log.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/log.cfc @@ -31,10 +31,10 @@ component { Boolean rewrites=false ){ if( !isNull( arguments.directory ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); } if( !isNull( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } var serverDetails = serverService.resolveServerDetails( arguments ); var serverInfo = serverDetails.serverInfo; 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 e2556b64d..c0a40ffd9 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 @@ -30,10 +30,10 @@ component { String serverConfigFile ){ if( !isNull( arguments.directory ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); } if( !isNull( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } var serverDetails = serverService.resolveServerDetails( arguments ); var serverInfo = serverDetails.serverInfo; diff --git a/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc b/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc index d26791b50..9bb3757aa 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/restart.cfc @@ -30,10 +30,10 @@ component aliases="restart" { boolean debug=false ){ if( !isNull( arguments.directory ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); } if( !isNull( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } var serverDetails = serverService.resolveServerDetails( arguments ); var serverInfo = serverDetails.serverInfo; diff --git a/src/cfml/system/modules_app/server-commands/commands/server/set.cfc b/src/cfml/system/modules_app/server-commands/commands/server/set.cfc index 360e1dd45..0b9c9c337 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/set.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/set.cfc @@ -59,7 +59,7 @@ component { var thisAppend = arguments.append; if( len( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } var thisServerConfigFile = ( len( arguments.serverConfigFile ) ? arguments.serverConfigFile : getCWD() & '/server.json' ); diff --git a/src/cfml/system/modules_app/server-commands/commands/server/show.cfc b/src/cfml/system/modules_app/server-commands/commands/server/show.cfc index 53044cae0..14b01b015 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/show.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/show.cfc @@ -36,14 +36,14 @@ component { // As a convenient shorcut, allow the serverConfigFile and propery parameter to be reversed because // "server show foo.json name" reads better than "server show name foo.json" but maintains backwards compat // for the simple use case of no JSON file as in "server show name" - var tmpPropertyResolved = fileSystemUtil.resolvePath( arguments.property ); + var tmpPropertyResolved = resolvePath( arguments.property ); // Check if the property name end with ".json" and happens to exist as a file on disk, if so it's probably the property file if( listLen( arguments.property, '.' ) > 1 && listLast( arguments.property, '.' ) == 'json' && fileExists( tmpPropertyResolved ) ) { // If so, swap the property into the server config param. arguments.property = arguments.serverConfigFile; arguments.serverConfigFile = tmpPropertyResolved; } else if( len( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); if( !fileExists( arguments.serverConfigFile ) ) { error( 'The serverConfigFile does not exist. [#arguments.serverConfigFile#]' ); } diff --git a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc index 2dba84872..92fafc8be 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/status.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/status.cfc @@ -71,10 +71,10 @@ component aliases='status,server info' { } if( !isNull( arguments.directory ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); } if( !isNull( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } var serverDetails = serverService.resolveServerDetails( arguments ); var serverInfo = serverDetails.serverInfo; diff --git a/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc b/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc index 540ed2fc6..e7195eade 100644 --- a/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc +++ b/src/cfml/system/modules_app/server-commands/commands/server/stop.cfc @@ -34,10 +34,10 @@ component aliases="stop" { } else { if( !isNull( arguments.directory ) ) { - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); } if( !isNull( arguments.serverConfigFile ) ) { - arguments.serverConfigFile = fileSystemUtil.resolvePath( arguments.serverConfigFile ); + arguments.serverConfigFile = resolvePath( arguments.serverConfigFile ); } // Look up the server that we're starting diff --git a/src/cfml/system/modules_app/system-commands/commands/cat.cfc b/src/cfml/system/modules_app/system-commands/commands/cat.cfc index ce86a4cf2..27c7d326c 100644 --- a/src/cfml/system/modules_app/system-commands/commands/cat.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/cat.cfc @@ -40,7 +40,7 @@ component aliases="type" { if( isSimpleValue( file ) ) { // Make file canonical and absolute - file = fileSystemUtil.resolvePath( file ); + file = resolvePath( file ); if( !fileExists( file ) ){ return error( "File: #file# does not exist!" ); diff --git a/src/cfml/system/modules_app/system-commands/commands/cd.cfc b/src/cfml/system/modules_app/system-commands/commands/cd.cfc index 2523cc9af..504b2caca 100644 --- a/src/cfml/system/modules_app/system-commands/commands/cd.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/cd.cfc @@ -19,7 +19,7 @@ component { function run( directory="" ) { // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/system-commands/commands/cp.cfc b/src/cfml/system/modules_app/system-commands/commands/cp.cfc index 6bd4019f3..ec43df6fb 100644 --- a/src/cfml/system/modules_app/system-commands/commands/cp.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/cp.cfc @@ -28,7 +28,7 @@ component aliases="copy" { function run( required Globber path, required newPath, boolean recurse=false, string filter="*" ) { // Make path canonical and absolute - var thisNewPath = fileSystemUtil.resolvePath( arguments.newPath ); + var thisNewPath = resolvePath( arguments.newPath ); if( path.count() > 1 && !directoryExists( thisNewPath ) ) { error( '[#thisNewPath#] is not a directory.' ); diff --git a/src/cfml/system/modules_app/system-commands/commands/execute.cfc b/src/cfml/system/modules_app/system-commands/commands/execute.cfc index a6759236c..78125947e 100644 --- a/src/cfml/system/modules_app/system-commands/commands/execute.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/execute.cfc @@ -33,7 +33,7 @@ component aliases="exec"{ clearTemplateCache(); // Make file canonical and absolute - arguments.file = fileSystemUtil.resolvePath( arguments.file ); + arguments.file = resolvePath( arguments.file ); if( !fileExists( arguments.file ) ){ return error( "File: #arguments.file# does not exist!" ); diff --git a/src/cfml/system/modules_app/system-commands/commands/fileAppend.cfc b/src/cfml/system/modules_app/system-commands/commands/fileAppend.cfc index 3524a6af4..69a7a5ccb 100644 --- a/src/cfml/system/modules_app/system-commands/commands/fileAppend.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/fileAppend.cfc @@ -27,7 +27,7 @@ component excludeFromHelp=true { function run( required contents='', required string file ) { // This will make the file path canonical and absolute - arguments.file = fileSystemUtil.resolvePath( arguments.file ); + arguments.file = resolvePath( arguments.file ); // Clean out any ANI escape codes from the text arguments.contents = print.unansi( arguments.contents ); diff --git a/src/cfml/system/modules_app/system-commands/commands/fileWrite.cfc b/src/cfml/system/modules_app/system-commands/commands/fileWrite.cfc index e134e4b7f..ff44fd7a4 100644 --- a/src/cfml/system/modules_app/system-commands/commands/fileWrite.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/fileWrite.cfc @@ -27,7 +27,7 @@ component excludeFromHelp=true { function run( required contents='', required string file ) { // This will make the file path canonical and absolute - arguments.file = fileSystemUtil.resolvePath( arguments.file ); + arguments.file = resolvePath( arguments.file ); // Clean out any ANI escape codes from the text arguments.contents = print.unansi( arguments.contents ); diff --git a/src/cfml/system/modules_app/system-commands/commands/mkdir.cfc b/src/cfml/system/modules_app/system-commands/commands/mkdir.cfc index 81335c8b3..70ecd2cf7 100644 --- a/src/cfml/system/modules_app/system-commands/commands/mkdir.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/mkdir.cfc @@ -26,7 +26,7 @@ component { } // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Create dir. Ignore if it exists and also create parent folders if missing directorycreate( arguments.directory, true, true ); diff --git a/src/cfml/system/modules_app/system-commands/commands/mv.cfc b/src/cfml/system/modules_app/system-commands/commands/mv.cfc index 43ff5161d..b4ef1cee9 100644 --- a/src/cfml/system/modules_app/system-commands/commands/mv.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/mv.cfc @@ -31,7 +31,7 @@ component aliases="rename" { function run( required Globber path, required newPath ) { // Make path canonical and absolute - var thisNewPath = fileSystemUtil.resolvePath( arguments.newPath ); + var thisNewPath = resolvePath( arguments.newPath ); if( path.count() > 1 && !directoryExists( thisNewPath ) ) { error( '[#thisNewPath#] is not a directory.' ); diff --git a/src/cfml/system/modules_app/system-commands/commands/pathExists.cfc b/src/cfml/system/modules_app/system-commands/commands/pathExists.cfc index 8703a3c11..410c52678 100644 --- a/src/cfml/system/modules_app/system-commands/commands/pathExists.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/pathExists.cfc @@ -26,7 +26,7 @@ component { error( 'Path not provided!' ); } - thepath = fileSystemUtil.resolvepath( thepath ); + thepath = resolvePath( thepath ); // Must be a file if( file ) { diff --git a/src/cfml/system/modules_app/system-commands/commands/propertyFile/clear.cfc b/src/cfml/system/modules_app/system-commands/commands/propertyFile/clear.cfc index 69ff0f3a3..cc0ded4ee 100644 --- a/src/cfml/system/modules_app/system-commands/commands/propertyFile/clear.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/propertyFile/clear.cfc @@ -18,7 +18,7 @@ component { ) { // This will make each directory canonical and absolute - propertyFilePath = fileSystemUtil.resolvePath( propertyFilePath ); + propertyFilePath = resolvePath( propertyFilePath ); // Create and load property file object propertyFile( propertyFilePath ) diff --git a/src/cfml/system/modules_app/system-commands/commands/propertyFile/set.cfc b/src/cfml/system/modules_app/system-commands/commands/propertyFile/set.cfc index d205bc779..0eeec4470 100644 --- a/src/cfml/system/modules_app/system-commands/commands/propertyFile/set.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/propertyFile/set.cfc @@ -20,7 +20,7 @@ component { ) { // This will make each directory canonical and absolute - propertyFilePath = fileSystemUtil.resolvePath( propertyFilePath ); + propertyFilePath = resolvePath( propertyFilePath ); // Create and load property file object if( fileExists( propertyFilePath ) ){ diff --git a/src/cfml/system/modules_app/system-commands/commands/propertyFile/show.cfc b/src/cfml/system/modules_app/system-commands/commands/propertyFile/show.cfc index 664f0dcef..ff9bdf1c2 100644 --- a/src/cfml/system/modules_app/system-commands/commands/propertyFile/show.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/propertyFile/show.cfc @@ -23,7 +23,7 @@ component { ) { // This will make each directory canonical and absolute - propertyFilePath = fileSystemUtil.resolvePath( propertyFilePath ); + propertyFilePath = resolvePath( propertyFilePath ); // Create and load property file object var propertyFile = propertyFile( propertyFilePath ); diff --git a/src/cfml/system/modules_app/system-commands/commands/recipe.cfc b/src/cfml/system/modules_app/system-commands/commands/recipe.cfc index e1c862416..8a3198d5b 100644 --- a/src/cfml/system/modules_app/system-commands/commands/recipe.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/recipe.cfc @@ -56,7 +56,7 @@ component { // store original path var originalPath = getCWD(); // Make file canonical and absolute - arguments.recipeFile = fileSystemUtil.resolvePath( arguments.recipeFile ); + arguments.recipeFile = resolvePath( arguments.recipeFile ); // Start clean so we can tell if any of our commands error without being affected by whatever may have run prior to this recipe shell.setExitCode( 0 ); diff --git a/src/cfml/system/modules_app/system-commands/commands/repl.cfc b/src/cfml/system/modules_app/system-commands/commands/repl.cfc index 630ec3bd8..2e5c788fc 100644 --- a/src/cfml/system/modules_app/system-commands/commands/repl.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/repl.cfc @@ -39,7 +39,7 @@ component { var executor = wirebox.getInstance( "executor" ); var newHistory = arguments.script ? variables.REPLScriptHistoryFile : variables.REPLTagHistoryFile; - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Setup REPL history file shell.setHistory( newHistory ); diff --git a/src/cfml/system/modules_app/system-commands/commands/run.cfc b/src/cfml/system/modules_app/system-commands/commands/run.cfc index 2348dcc50..b600eee55 100644 --- a/src/cfml/system/modules_app/system-commands/commands/run.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/run.cfc @@ -64,7 +64,7 @@ component{ var exitCode = 1; // grab the current working directory - var CWDFile = createObject( 'java', 'java.io.File' ).init( fileSystemUtil.resolvePath( '' ) ); + var CWDFile = createObject( 'java', 'java.io.File' ).init( resolvePath( '' ) ); try{ diff --git a/src/cfml/system/modules_app/system-commands/commands/tail.cfc b/src/cfml/system/modules_app/system-commands/commands/tail.cfc index 763ef0c37..de1898cb6 100644 --- a/src/cfml/system/modules_app/system-commands/commands/tail.cfc +++ b/src/cfml/system/modules_app/system-commands/commands/tail.cfc @@ -28,7 +28,7 @@ if( inputAsArray.len() > 1 ) { var rawText = true; } - var filePath = fileSystemUtil.resolvePath( arguments.path ); + var filePath = resolvePath( arguments.path ); if( !fileExists( filePath ) ){ var rawText = true; diff --git a/src/cfml/system/modules_app/task-commands/commands/task/create.cfc b/src/cfml/system/modules_app/task-commands/commands/task/create.cfc index 199fa98bf..63809e96d 100644 --- a/src/cfml/system/modules_app/task-commands/commands/task/create.cfc +++ b/src/cfml/system/modules_app/task-commands/commands/task/create.cfc @@ -29,7 +29,7 @@ component { ) { // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Read in Templates var taskContent = fileRead( '/task-commands/templates/TaskContent.txt' ); diff --git a/src/cfml/system/modules_app/task-commands/commands/task/run.cfc b/src/cfml/system/modules_app/task-commands/commands/task/run.cfc index d3663db9b..3ec6ea99d 100644 --- a/src/cfml/system/modules_app/task-commands/commands/task/run.cfc +++ b/src/cfml/system/modules_app/task-commands/commands/task/run.cfc @@ -32,7 +32,7 @@ component { string target='run' ) { - arguments.taskFile = fileSystemUtil.resolvePath( arguments.taskFile ); + arguments.taskFile = resolvePath( arguments.taskFile ); var taskArgs = {}; // Named task args will come through in a struct called args diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/bdd.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/bdd.cfc index 0ab82ec00..802aea56e 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/bdd.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/bdd.cfc @@ -29,7 +29,7 @@ component { } // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/unit.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/unit.cfc index 60a7953ee..db6b78a08 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/unit.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/create/unit.cfc @@ -29,7 +29,7 @@ component { } // This will make each directory canonical and absolute - arguments.directory = fileSystemUtil.resolvePath( arguments.directory ); + arguments.directory = resolvePath( arguments.directory ); // Validate directory if( !directoryExists( arguments.directory ) ) { diff --git a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc index 956d82b3d..3fc080c25 100644 --- a/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc +++ b/src/cfml/system/modules_app/testbox-commands/commands/testbox/run.cfc @@ -188,7 +188,7 @@ component { // Do we have an output file if( !isNull( arguments.outputFile ) ){ // This will make each directory canonical and absolute - arguments.outputFile = fileSystemUtil.resolvePath( arguments.outputFile ); + arguments.outputFile = resolvePath( arguments.outputFile ); var thisDir = getDirectoryFromPath( arguments.outputFile ); From defb08cb96e3f98cf37fc2b767edbae8b7be1499 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 28 Sep 2018 15:22:49 -0500 Subject: [PATCH 44/49] COMMANDBOX-873 --- src/cfml/system/services/PackageService.cfc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/PackageService.cfc b/src/cfml/system/services/PackageService.cfc index d5ed6faf7..0493fccce 100644 --- a/src/cfml/system/services/PackageService.cfc +++ b/src/cfml/system/services/PackageService.cfc @@ -312,10 +312,6 @@ component accessors="true" singleton { installDirectory = expandPath( '/commandbox/modules' ); } - // Flag the shell to reload after this command is finished. - job.addWarnLog( "Shell will be reloaded after installation." ); - shell.reload( false ); - shellWillReload = true; // If this is a plugin } else if( packageType == 'plugins' ) { installDirectory = arguments.packagePathRequestingInstallation & '/plugins'; @@ -336,6 +332,15 @@ component accessors="true" singleton { } } + + // If this package is being installed anywhere south of the CommandBox system folder, + // flag the shell to reload after this command is finished. + if( fileSystemUtil.normalizeSlashes( installDirectory ).startsWith( fileSystemUtil.normalizeSlashes( expandPath( '/commandbox' ) ) ) ) { + job.addWarnLog( "Shell will be reloaded after installation." ); + shell.reload( false ); + shellWillReload = true; + } + // I give up, just stick it in the CWD if( !len( installDirectory ) ) { installDirectory = arguments.currentWorkingDirectory; From 79b2bdd8682195047c6434281043ed69b6783dd2 Mon Sep 17 00:00:00 2001 From: John Berquist Date: Fri, 28 Sep 2018 13:39:58 -0700 Subject: [PATCH 45/49] Recursively check JSON files for sorting (#174) --- src/cfml/system/services/JSONService.cfc | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/services/JSONService.cfc b/src/cfml/system/services/JSONService.cfc index 50b1d1824..91275289c 100644 --- a/src/cfml/system/services/JSONService.cfc +++ b/src/cfml/system/services/JSONService.cfc @@ -254,12 +254,22 @@ component accessors="true" singleton { json = deserializeJSON( json ); } - // simple check - are top level keys sorted, default to true if we can't tell - if ( isStruct( json ) ) { - return json.keyList() == json.keyArray().sort( sortKeys ).toList(); + var isSorted = function( obj ) { + if ( isStruct( obj ) ) { + if ( obj.keyList() != obj.keyArray().sort( sortKeys ).toList() ) { + return false; + } + return obj.every( ( k, v ) => isSorted( v ) ); + } + + if ( isArray( obj ) ) { + return obj.every( isSorted ); + } + + return true; } - return true; + return isSorted( json ); } } From 65d2748d58eb35e59f9265c8e3ce3d62a990b599 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 28 Sep 2018 16:17:08 -0500 Subject: [PATCH 46/49] Update JSON pretty print --- src/cfml/system/modules/JSONPrettyPrint/box.json | 4 ++-- .../system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cfml/system/modules/JSONPrettyPrint/box.json b/src/cfml/system/modules/JSONPrettyPrint/box.json index 7e261d5f3..a4d8821d9 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/box.json +++ b/src/cfml/system/modules/JSONPrettyPrint/box.json @@ -1,8 +1,8 @@ { "name":"JSONPrettyPrint", - "version":"1.3.2", + "version":"1.3.3", "author":"", - "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.2", + "location":"Ortus-Solutions/JSONPrettyPrint#v1.3.3", "homepage":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "documentation":"https://github.com/Ortus-Solutions/JSONPrettyPrint", "repository":{ diff --git a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc index fb7956e0e..d63cd0456 100644 --- a/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc +++ b/src/cfml/system/modules/JSONPrettyPrint/models/CFMLPrinter.cfc @@ -38,7 +38,7 @@ component { if ( structIsEmpty( json ) ) { return '{}'; } - var keys = json.keyArray(); + var keys = structKeyArray( json ); if ( len( settings.sortKeys ) ) { keys.sort( settings.sortKeys ); } From 30bf3ce3c9d198bc3afdec5a635ce23395b1e8dd Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 28 Sep 2018 16:47:53 -0500 Subject: [PATCH 47/49] COMMANDBOX-874 --- src/cfml/system/util/FileSystem.cfc | 3 ++- src/cfml/system/util/Print.cfc | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cfml/system/util/FileSystem.cfc b/src/cfml/system/util/FileSystem.cfc index 7776fd5c8..e6081b63a 100644 --- a/src/cfml/system/util/FileSystem.cfc +++ b/src/cfml/system/util/FileSystem.cfc @@ -380,12 +380,13 @@ component accessors="true" singleton { /* * Turns all slashes in a path to forward slashes except for \\ in a Windows UNC network share + * Also changes double slashes to a single slash */ function normalizeSlashes( string path ) { if( path.left( 2 ) == '\\' ) { return '\\' & path.replace( '\', '/', 'all' ).right( -2 ); } else { - return path.replace( '\', '/', 'all' ); + return path.replace( '\', '/', 'all' ).replace( '//', '/', 'all' ); } } diff --git a/src/cfml/system/util/Print.cfc b/src/cfml/system/util/Print.cfc index d5c7e8d20..78a7ea430 100644 --- a/src/cfml/system/util/Print.cfc +++ b/src/cfml/system/util/Print.cfc @@ -36,6 +36,7 @@ component { property name='cr' inject='cr@constants'; property name='shell' inject='shell'; property name='colors256Data' inject='colors256Data@constants'; + property name='formatterUtil' inject='formatter'; this.tab = chr( 9 ); this.esc = chr( 27 ); @@ -88,6 +89,18 @@ component { // Text needing formatting var text = arrayLen(missingMethodArguments) ? missingMethodArguments[ 1 ] : ''; + // Convert complex values to a string representation + if( !isSimpleValue( text ) ) { + + // Serializable types + if( isArray( text ) || isStruct( text ) || isQuery( text ) ) { + text = formatterUtil.formatJson( text ); + // Yeah, I give up + } else { + text = '[#text.getClass().getName()#]'; + } + + } // Additional formatting text var methodName &= arrayLen(missingMethodArguments) > 1 ? missingMethodArguments[ 2 ] : ''; // Don't turn off ANSI formatting at the end From 8e45892002856ae94a14095b225d6e4acac3d309 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 2 Oct 2018 11:55:09 -0700 Subject: [PATCH 48/49] Allow Java 9+ --- src/cfml/system/Bootstrap.cfm | 2 +- src/java/cliloader/Util.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cfml/system/Bootstrap.cfm b/src/cfml/system/Bootstrap.cfm index ea23669bb..82871d2bb 100644 --- a/src/cfml/system/Bootstrap.cfm +++ b/src/cfml/system/Bootstrap.cfm @@ -83,7 +83,7 @@ This file will stay running the entire time the shell is open bufferedReader = createObject( 'java', 'java.io.BufferedReader' ).init( inputStreamReader ); // Verify if we can run CommandBox Java v. 1.7+ - if( !findNoCase( "1.8", server.java.version ) ){ + if( !findNoCase( "1.8", server.java.version ) && 0 ){ // JLine isn't loaded yet, so I have to use systemOutput() here. systemOutput( "The Java Version you have (#server.java.version#) is not supported by CommandBox. Please install a Java JRE/JDK 1.8." ); sleep( 5000 ); diff --git a/src/java/cliloader/Util.java b/src/java/cliloader/Util.java index fd7f01547..3673eaeeb 100644 --- a/src/java/cliloader/Util.java +++ b/src/java/cliloader/Util.java @@ -104,8 +104,8 @@ public static void ensureJavaVersion(){ Thread.sleep( 5000 ); System.exit( 1 ); } - - String jVer = System.getProperty("java.version"); +return; + /* String jVer = System.getProperty("java.version"); if( jVer.startsWith( "9" ) || jVer.startsWith( "10" ) || jVer.startsWith( "11" ) ) { System.out.println( "It looks like you're using Java 9, 10, or higher, which CommandBox doesn't support!" ); System.out.println( "If your PC needs this version of Java installed, then place a folder called 'JRE' with Java 8 in the same directory as your box binary." ); @@ -113,7 +113,7 @@ public static void ensureJavaVersion(){ Thread.sleep( 5000 ); System.exit( 1 ); } - +*/ } catch ( java.lang.ClassNotFoundException e ) { System.out.println( "Could not load NIO! Are we running on Java 7 or 8? Sorry, exiting..." ); From c0550664402b963599a6d3c16254de8189c7a5db Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 2 Oct 2018 17:45:05 -0700 Subject: [PATCH 49/49] Cut final 4.3 release --- build/build.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/build.xml b/build/build.xml index 624835b1e..bbde6f80c 100644 --- a/build/build.xml +++ b/build/build.xml @@ -16,8 +16,8 @@ External Dependencies: - - + +