diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb91dd..bcf46a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: pull_request: jobs: ci: - name: CI for Pirate + name: CI for Preside CommandBox Commands runs-on: ubuntu-latest steps: - name: Setup flow variables @@ -12,7 +12,7 @@ jobs: branch=${GITHUB_REF##*/} publish=false if [[ "{{ env.event.name }}" != "pull_request" ]] ; then - if [[ $branch == v* ]] ; then + if [[ $branch == release-* ]] || [[ $branch == v* ]] ; then publish=true fi fi @@ -39,7 +39,7 @@ jobs: - name: Zip project if: "env.PUBLISH == 'true'" - run: zip -rq $ZIP_FILE * -x *.log + run: zip -rq $ZIP_FILE * -x jmimemagic.log -x .* -x *.sh -x *.log -x tests/**\* shell: bash env: ZIP_FILE: ${{ steps.versiongen.outputs.semver_release_number }}.zip @@ -51,7 +51,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ github.ref }} + tag_name: ${{ steps.versiongen.outputs.semver_release_string }} release_name: Release ${{ steps.versiongen.outputs.semver_release_string }} draft: false prerelease: ${{ steps.versiongen.outputs.semver_release_is_snapshot }} @@ -81,4 +81,4 @@ jobs: uses: pixl8/github-action-box-publish@v3 with: forgebox_user: ${{ secrets.FORGEBOX_USER }} - forgebox_pass: ${{ secrets.FORGEBOX_PASS }} + forgebox_pass: ${{ secrets.FORGEBOX_PASS }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e05c06b..d1fae95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 7.0.0 + +Rewrite of core commands to bring up to date with latest changes and approaches from Commandbox. We now use cfconfig +for `preside server start` and have resolved the issues with datasource prompts overlapping Commandbox text + ## 6.1.6 * Fix for broken paths in Gruntfile generated for new extension command when choosing to manage static assets with grunt diff --git a/_resources/lucee-web.xml.cfm b/_resources/lucee-web.xml.cfm deleted file mode 100644 index 92f8f38..0000000 --- a/_resources/lucee-web.xml.cfm +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - ${datasource} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/commands/preside/new/site.cfc b/commands/preside/new/site.cfc index 24097fc..e3833b0 100644 --- a/commands/preside/new/site.cfc +++ b/commands/preside/new/site.cfc @@ -8,6 +8,7 @@ component { property name="wirebox" inject="wirebox"; property name="forgeBox" inject="ForgeBox"; + /** * @skeleton.hint The name of the app skeleton to use. Options: basic, nocms **/ @@ -15,29 +16,31 @@ component { var directory = shell.pwd(); var templates = _getSkeletonTemplates(); - while( arguments.skeleton == "" ) { + if ( !Len( arguments.skeleton ) ) { print.yellowLine( "Looking up available skeletons from forgebox.io... (hint: register a template by adding a forgebox package matching the pattern, 'preside-skeleton-*')" ); - print.line(); - + print.line().toConsole(); + if ( templates.isEmpty() ) { print.line( "" ); print.redLine( "No preside skeleton templates could be found! Ensure you are online and that https://www.forgebox.io is up and running." ); print.line( "" ); - } + } else { + var options = []; + var i=0; + for( var templateId in templates ) { + var template = templates[ templateId ]; - print.line( "" ); - print.line( "Available skeleton templates from which to build your new site/application:" ); - print.line( "" ); - for( var templateId in templates ) { - print.text( " * " ); - print.yellowText( "#templateId#" ); - print.line( ": #templates[ templateId ].description#" ); - } - print.line( "" ); + ArrayAppend( options, { + value = templateId + , display = template.name & ( template.official ? " (OFFICIAL)" : " (by #template.author#)" ) & ": " & template.description + , accessKey = ++i + } ); + } + + arguments.skeleton = multiselect( "Choose a skeleton template to use: " ).options( options ).required().ask(); + + print.line( "" ); - arguments.skeleton = ask( "Enter the skeleton template to use: " ); - if ( !templates.keyExists( arguments.skeleton ) ) { - arguments.skeleton = ""; } } @@ -102,15 +105,31 @@ component { private struct function _getSkeletonTemplates() { var templates = {}; var forgeboxEntries = forgebox.getEntries( typeSlug = "preside-skeletons" ); + var official = [ "preside-skeleton-basic", "preside-skeleton-webapp" ]; + var ordered = StructNew( "linked" ); for( var entry in forgeboxEntries.results ) { templates[ entry.slug.replace( "preside-skeleton-", "" ) ] = { name = entry.title , description = entry.summary , package = entry.slug + , author = entry.user.fullName ?: "unknown" + , official = ArrayFindNoCase( official, entry.slug ) }; } - return templates; + if ( StructKeyExists( templates, "basic" ) ) { + ordered[ "basic" ] = templates.basic; + } + if ( StructKeyExists( templates, "basic" ) ) { + ordered[ "webapp" ] = templates.webapp; + } + for( var key in templates ) { + if ( !ArrayFind( [ "basic", "webapp" ], key ) ) { + ordered[ key ] = templates[ key ]; + } + } + + return ordered; } } diff --git a/commands/preside/start.cfc b/commands/preside/start.cfc index 47610f9..6e8f921 100644 --- a/commands/preside/start.cfc +++ b/commands/preside/start.cfc @@ -1,12 +1,11 @@ /** - * Start a PresideCMS server + * Start a Preside Application server. This is proxy to server start + * with some specific cfconfig configuration for Preside. **/ component { - property name="serverService" inject="ServerService"; - property name="interceptorService" inject="interceptorService"; - property name="commandboxHomeDirectory" inject="HomeDir@constants"; - property name="interactive" default=true; + property name="interceptorService" inject="interceptorService"; + property name="moduleService" inject="moduleService"; /** * @port.hint port number @@ -22,25 +21,29 @@ component { function run( String directory = "" , Numeric heapSize = 1024 - , Boolean saveSettings = false , Boolean interactive = true - , Numeric port - , Boolean openbrowser - , String name - , Numeric stopPort - , Boolean force - , Boolean debug + , String serverConfigFile = "server.json" , String trayIcon ){ + if ( !moduleService.isModuleActive( "commandbox-cfconfig") ) { + print.redLine( "=================================================================" ); + print.redLine( "CRITICAL ERROR: cfconfig not installed/enabled." ); + print.redLine( "The preside start command relies on cfconfig to persist settings." ); + print.line(); + print.redLine( "Either:" ); + print.redLine( "1. Ensure CommandBox is up to date (comes with cfconfig)" ); + print.redLine( "2. Install cfconfig separately: box install commandbox-cfconfig" ); + print.redLine( "=================================================================" ).toConsole(); + return; + } var serverProps = arguments; - var resourceDir = GetDirectoryFromPath( GetCurrentTemplatePath() ) & "/../../_resources"; var osInfo = CreateObject("java", "java.lang.System").getProperties(); + var resourceDir = GetDirectoryFromPath( GetCurrentTemplatePath() ) & "/../../_resources"; - serverProps.directory = fileSystemUtil.resolvePath( arguments.directory ); - serverProps.name = serverProps.name is "" ? listLast( serverProps.directory, "\/" ) : serverProps.name; - serverProps.rewritesEnable = true; - serverProps.rewritesConfig = serverProps.directory & "/urlrewrite.xml"; - this.interactive = arguments.interactive; + serverProps.directory = fileSystemUtil.resolvePath( arguments.directory ); + serverProps.saveSettings = true; + serverProps.rewritesEnable = true; + serverProps.rewritesConfig = serverProps.rewritesConfig ?: ( serverProps.directory & "/urlrewrite.xml" ); if ( !serverProps.keyExists( "trayIcon" ) ) { if ( osInfo['os.name'].findNoCase( "Mac OS" ) || osInfo['os.name'].findNoCase( "Linux" ) ) { @@ -50,161 +53,133 @@ component { } } - interceptorService.registerInterceptor( this ); - serverService.start( serverProps=serverProps ); - } + _ensureCfConfigSetup( argumentCollection=serverProps ); - function onServerStart( event, interceptData ) { - _prepareDirectories( interceptData.serverInfo ?: {} ); - } - - /** - * Private method to setup the web config directories with Preside specific configuration - * - */ - private void function _prepareDirectories( required struct serverInfo ) { - var webConfigDir = serverInfo.webConfigDir; - - if ( webConfigDir.startsWith( "/WEB-INF" ) ) { - webConfigDir = ( serverInfo.serverHomeDirectory ?: "" ) & webConfigDir; + if ( serverProps.directory == fileSystemUtil.resolvePath( "" ) ) { + StructDelete( serverProps, "directory" ); } - var presideServerDir = webConfigDir & "/preside"; - var resourceDir = GetDirectoryFromPath( GetCurrentTemplatePath() ) & "/../../_resources"; - var presideInitedFile = webConfigDir & "/.presideinitialized"; - - if ( !FileExists( presideInitedFile ) ) { - if ( !DirectoryExists( webConfigDir ) ) { - DirectoryCreate( webConfigDir, false, true ); - - var sourceWebConfigDirectory = commandBoxHomeDirectory & "/engine/cfml/cli/cfml-web"; - if ( !DirectoryExists( sourceWebConfigDirectory ) ) { - print.line(); - print.redLine("*************************************************************************************************************************************************************************"); - print.redLine("Could not find server files. Please ensure you have the latest version of CommandBox. Expected to find files at [#sourceWebConfigDirectory#]"); - print.redLine("*************************************************************************************************************************************************************************"); - print.line(); + interceptorService.registerInterceptor( this ); - return {}; - } + command( "server start" ).params( argumentCollection=serverProps ).run(); + } - DirectoryCopy( sourceWebConfigDirectory, webConfigDir, true ); + public void function onServerStart( interceptData ) { + var path = interceptData.serverInfo.webroot; + var rootAppCfc = path.listAppend( "application/config/Config.cfc", "/" ); + + if ( FileExists( rootAppCfc ) ) { + var regPattern = ".*\bsettings\.preside_admin_path\s*=\s*[""'](.*?)[""'].*"; + var adminPath = ReReplaceNoCase( FileRead( rootAppCfc ), regPattern, "\1" ); + + if ( Len( adminPath ) ) { + interceptData.serverInfo.trayOptions = interceptData.serverInfo.trayOptions ?: []; + ArrayInsertAt( interceptData.serverInfo.trayOptions, ArrayLen( interceptData.serverInfo.trayOptions ), { + "label":"Preside", + "items": [ + { 'label':'Site Home', 'action':'openbrowser', 'url': interceptData.serverInfo.openbrowserURL }, + { 'label':'Site Admin', 'action':'openbrowser', 'url': '#interceptData.serverInfo.openbrowserURL#/#adminPath#/' } + ], + "image" : "" + } ); } - var presideLocation = _setupPresideLocation( webConfigDir, serverInfo.webroot ); - var datasource = this.interactive ? _setupDatasource() : ""; - - var luceeWebXml = FileRead( resourceDir & "/lucee-web.xml.cfm" ); - luceeWebXml = ReplaceNoCase( luceeWebXml, "${presideLocation}", presideLocation ); - luceeWebXml = ReplaceNoCase( luceeWebXml, "${datasource}", datasource ); - FileWrite( webConfigDir & "/lucee-web.xml.cfm", luceeWebXml ); - FileWrite( webConfigDir & "/lucee-web.xml.cfm", luceeWebXml ); - FileWrite( presideInitedFile, "" ); } } - private string function _setupPresideLocation( required string webConfigDir, required string webroot ) { - var presideLocation = arguments.webroot.reReplace( "[\\/]$", "" ) & "/preside"; + private function _ensureCfConfigSetup() { + var cfconfigFilePath = _getCfConfigFilePath( argumentCollection=arguments ); - if ( FileExists( presideLocation & "/system/Bootstrap.cfc" ) ) { - print.line().toConsole(); - print.yellowLine( "Using Preside location [#presideLocation#]..." ).toConsole(); - print.line().toConsole(); + print.line( "Checking/creating your cfconfig file at: [#cfconfigFilePath#]..." ); + print.line( "NOTE: this is used to set Preside specific configuration and save your datasource. You should almost certainly ensure that this file is NOT commited to version control." ).toConsole(); + print.line( " You can also consult the CFConfig documentation to use this file to control other aspects of your Commandbox server." ).toConsole(); - return presideLocation; + if ( !FileExists( cfconfigFilePath ) ) { + FileWrite( cfconfigFilePath, "{}" ); } - if ( !this.interactive ) { - return ""; - } + var cfconfig = DeserializeJson( FileRead( cfconfigFilePath ) ); - print.line().toConsole(); - print.yellowLine( "PresideCMS core installation" ).toConsole(); - print.yellowLine( "============================" ).toConsole(); - print.line().toConsole(); + if ( !Len( Trim( cfconfig.datasources.preside.database ?: "" ) ) ) { + cfconfig[ "datasources" ] = cfconfig.datasources ?: {}; + cfconfig.datasources[ "preside" ] = _setupDatasource( argumentCollection=arguments ); - print.line().toConsole(); - var useLocalVersion = shell.ask( "Install fresh version of Preside [Y/n]? " ) == "n"; - if ( useLocalVersion ) { - print.line().toConsole(); - presideLocation = shell.ask( "Enter the path to Preside: " ); - while( !DirectoryExists( presideLocation ) || !FileExists( presideLocation & "/system/Bootstrap.cfc" ) ) { - print.redLine( "The path you entered is not a valid Preside path!").toConsole(); - presideLocation = shell.ask( "Enter the path to Preside: " ); - } + print.linLine( " " ); + print.greenLine( "Thank you! If you have any issues with your datasource, you can configure in [#cfconfigFilePath#]" ).toConsole(); + } - } else { - var validVersion = false; - var presideVersion = ""; - - while ( !validVersion ) { - validVersion = true; - - print.line().toConsole(); - while( ![ "s", "b" ].findNoCase( presideVersion ) ) { - presideVersion = shell.ask( "Which version of preside do you wish to install, (S)table or (B)leeding edge? [(S)/b]:" ); - if ( !Len( Trim( presideVersion ) ) ) { - presideVersion = "s"; - } - } - presideLocation = "http://downloads.presidecms.com/presidecms/" & ( presideVersion == "b" ? "bleeding-edge.zip" : "release.zip" ); - - var presideZip = GetTempDirectory() & "/PresideCMS.zip"; - try { - print.line() - .yellowLine( "Downloading Preside from [#presideLocation#]... please be patient" ).toConsole(); - http getasBinary=true file=presideZip url=presideLocation throwOnError=true; - } catch ( any e ) { - validVersion = false; - print.redLine( "Invalid preside version [#presideVersion#]. No download found at [#presideLocation#]." ).toConsole(); - } - } + cfconfig[ "templateCharset" ] = cfconfig.templateCharset ?: "UTF-8"; + cfconfig[ "webCharset" ] = cfconfig.webCharset ?: "UTF-8"; + cfconfig[ "resourceCharset" ] = cfconfig.resourceCharset ?: "UTF-8"; + cfconfig[ "resourceCharset" ] = cfconfig.resourceCharset ?: "UTF-8"; + cfconfig[ "dotNotationUpperCase" ] = false; - print.yellowLine( "Download complete. Installing to [#arguments.webConfigDir#/preside]..." ).toConsole(); + FileWrite( cfconfigFilePath, formatterUtil.formatJson( cfconfig ) ); - zip action="unzip" file="#presideZip#" destination=arguments.webConfigDir & "/preside"; + print.line() + print.line( "Checks complete. Starting your server now..." ).toConsole(); + } + + private string function _getCfConfigFilePath() { + var serverConfPath = arguments.directory & arguments.serverConfigFile; + if ( !FileExists( serverConfPath ) ) { + FileWrite( serverConfPath, formatterUtil.formatJson( { "web"={ "webroot"=arguments.directory }} ) ); + } + var serverConf = SerializeJson( FileRead( serverConfPath ) ); + var possibleKeys = [ "file", "server", "web" ]; + var relFilePath = ""; - var subDirs = DirectoryList( arguments.webConfigDir & "/preside", false, "query" ); - var versionDir = ""; - for( var subDir in subDirs ){ - if ( subDir.type == "Dir" && ReFindNoCase( "^presidecms-[0-9\.]+$", subDir.name ) ) { - versionDir = "/#subDir.name#"; - break; - } + for( var key in possibleKeys ) { + if ( Len( serverConfig.cfconfig[ key ] ?: "" ) ) { + relFilePath = serverConfig.cfconfig[ key ]; } + } + + if ( !Len( relFilePath ) && Len( serverConfig.cfconfigFile ?: "" ) ) { + relFilePath = serverConfig.cfconfigFile; + } + + if ( !Len( relFilePath ) ) { + relFilePath = ".cfconfig.json"; + serverConfig[ "cfconfig" ] = serverConfig[ "cfconfig" ] ?: {}; + serverConfig.cfconfig[ "file" ] = relFilePath; - presideLocation = "{lucee-web}/preside#versionDir#"; + FileWrite( serverConfPath, formatterUtil.formatJson( serverConfig ) ); } - return presideLocation; + return arguments.directory & relFilePath; + } - private string function _setupDatasource() { - print.line().toConsole(); - print.yellowLine( "PresideCMS datasource setup (MySQL Only)" ).toConsole(); - print.yellowLine( "========================================" ).toConsole(); + private function _setupDatasource() { + print.line(); + print.greenLine( "PRESIDE DATASOURCE SETUP" ); + print.greenLine( "========================" ); + print.greenLine( "No Preside Datasource found. Starting wizard to create one." ); + print.greenLine( "NOTE: You can configure using your datasource independently in your .cfconfig file if you require a more complex datasource configuration." ); print.line().toConsole(); - if ( shell.ask( "Setup MySQL datasource now [Y/n]? " ) == "n" ) { - return ""; - } + var config = {} - print.line().toConsole(); - print.yellowLine( "If you have not done so already, please create your database and have credentials ready." ).toConsole(); - print.line().toConsole(); + config[ "dbdriver" ] = multiselect( 'Select your database engine: ' ).options( [ + { display='MySQL/MariaDB', value='mysql', selected=true }, + { display='PostgreSQL', value='postgres' }, + { display='Microsoft SQL Server', value='MSSQL' } + ] ).required().ask(); - var db = shell.ask( "Database name: " ); - var usr = shell.ask( "Username: " ); - var pass = shell.ask( "Password: " ); - var host = shell.ask( "Host (localhost): " ); - var port = shell.ask( "Port (3306): " ); - while( Len( Trim( port ) ) && !IsNumeric( port ) ) { - print.redLine( "Invalid port number!" ).toConsole(); - port = shell.ask( "Port (3306): " ); - } + config[ "database" ] = ask( message="Database name: ", required=true ); + config[ "username" ] = ask( message="Username: " ); + config[ "password" ] = ask( message="Password: ", mask='*' ); + config[ "host" ] = ask( message="Host: ", defaultResponse="localhost" ); + config[ "port" ] = ask( message="Port: ", defaultResponse=_getDefaultPortForDb( config.dbdriver ) ); - if( !Len( Trim( host ) ) ) { host = "localhost"; } - if( !Len( Trim( port ) ) ) { port = "3306"; } + return config; + } - return ''; + private function _getDefaultPortForDb( dbdriver ) { + switch( arguments.dbdriver) { + case "mssql": return 1433; + case "postgres": return 5432; + default: return 3306; + } } }