@caweb/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # caweb-cli
2
+ `caweb-cli` rapidly sets up a local WordPress environment using [wp-env](https://www.npmjs.com/package/@wordpress/env), fully configured to the [CAWebPublishing Service](https://caweb.cdt.ca.gov/). The cli will automatically generate the necessary [.wp-env.json](https://www.npmjs.com/package/@wordpress/env#wp-envjson) file, to override or add additional configuration options use the [.wp-env.override.json](https://www.npmjs.com/package/@wordpress/env#wp-envoverridejson) file.
3
+
4
+ ## Additional Features
5
+ - phpMyAdmin Service
6
+ - Downloads and configures the [CAWeb Theme](https://github.com/CA-cODE-Works/CAWeb)
7
+ - Downloads and configures the [Divi Theme](https://www.elegantthemes.com/gallery/divi/) (*requires valid ElegantThemes Username and API Key*)
8
+
9
+ ## Command Reference
10
+ `caweb-cli` is a wrapper for [wp-env](https://www.npmjs.com/package/@wordpress/env); therefore, all commands from wp-env are readily available, for more information on those commands [see](https://www.npmjs.com/package/@wordpress/env#command-referenced). ***Note:** substitute `wp-env` command with `caweb`.
11
+
12
+ ### `caweb start`
13
+ Starts the CAWebPublishing WordPress Environment.
14
+ ### `caweb shell`
15
+ Open a shell session in the WordPress environment.
package/bin/caweb ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Remove 'node' and the name of the script from the arguments.
5
+ let command = process.argv.slice( 2 );
6
+ // Default to help text when they aren't running any commands.
7
+ if ( ! command.length ) {
8
+ command = [ '--help' ];
9
+ }
10
+
11
+ // Rather than just executing the current CLI we will attempt to find a local version
12
+ // and execute that one instead. This prevents users from accidentally using the
13
+ // global CLI when a potentially different local version is expected.
14
+ const localPath = require.resolve( '@caweb/cli/lib/cli.js', {
15
+ paths: [ process.cwd(), __dirname ],
16
+ } );
17
+ const cli = require( localPath )();
18
+
19
+ // Now we can execute the CLI with the given command.
20
+ cli.parse( command );
package/lib/caweb.js ADDED
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const dockerCompose = require( 'docker-compose' );
6
+ const path = require( 'path' );
7
+ const loadConfig = require( '@wordpress/env/lib/config/load-config' );
8
+
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ const { CAWEB_OPTIONS } = require('./options');
14
+
15
+ /**
16
+ *
17
+ * @param {Object} options
18
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
19
+ * @param {boolean} options.environment Which environment to open terminal in.
20
+ * @param {Array} options.cmds Array of commands to run.
21
+ *
22
+ * @returns {Object}
23
+ */
24
+ async function runDockerCmd({
25
+ spinner,
26
+ environment,
27
+ cmds
28
+ }) {
29
+
30
+ try {
31
+ const config = await loadConfig(path.resolve('.'));
32
+
33
+ // -eo pipefail exits the command as soon as anything fails in bash.
34
+ //const setupCommands = [ 'set -eo pipefail' ];
35
+
36
+ // Execute all setup commands in a batch.
37
+ return await dockerCompose.run(
38
+ environment === 'development' ? 'cli' : 'tests-cli',
39
+ [ 'bash', '-c', cmds.join( ' && ' ) ],
40
+ {
41
+ cwd: config.workDirectoryPath,
42
+ commandOptions: [ '--rm' ],
43
+ log: config.debug,
44
+ }
45
+ ).then(
46
+ (result) => {
47
+ if( '' !== result.out ){
48
+ console.log( result.out )
49
+ }else{
50
+ console.log(result.err)
51
+ }
52
+ return result;
53
+ },
54
+ (err) => {
55
+ return false;
56
+ }
57
+ )
58
+
59
+ } catch(error) {
60
+ console.log(error)
61
+
62
+ process.exit( 1 );
63
+ }
64
+
65
+ };
66
+
67
+ /**
68
+ * Activates the CAWeb Theme for the WordPress Environment if it's installed.
69
+ *
70
+ * @param {Object} options
71
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
72
+ * @param {boolean} options.environment Which environment to activate the theme on.
73
+ */
74
+ async function activateCAWeb({
75
+ spinner,
76
+ environment
77
+ }){
78
+ return await runDockerCmd( {
79
+ spinner,
80
+ environment,
81
+ cmds: ['wp theme is-installed CAWeb && wp theme activate CAWeb']
82
+ }).then(
83
+ (result) => {
84
+ return true;
85
+ },
86
+ (err) => {
87
+ return false;
88
+ }
89
+ )
90
+
91
+ }
92
+
93
+ /**
94
+ * Configures WordPress for the given environment by installing WordPress,
95
+ * activating all plugins, and activating the first theme. These steps are
96
+ * performed sequentially so as to not overload the WordPress instance.
97
+ *
98
+ * @param {WPEnvironment} environment The environment to configure. Either 'development' or 'tests'.
99
+ * @param {WPConfig} config The wp-env config object.
100
+ * @param {Object} spinner A CLI spinner which indicates progress.
101
+ */
102
+ async function configureCAWeb( environment, config, spinner ) {
103
+
104
+
105
+ const isThemeActivated = await activateCAWeb( {
106
+ spinner,
107
+ environment
108
+ })
109
+
110
+ // if our theme is active.
111
+ if( isThemeActivated ){
112
+ let themeOptions = [];
113
+
114
+ // iterate over config options.
115
+ Object.entries(config.env[ environment ].config).forEach(([k,v]) => {
116
+ // if the option is prefixed with CAWEB_ and is a valid CAWeb Option.
117
+ if ( `${k}`.startsWith('CAWEB_') && undefined !== CAWEB_OPTIONS[k] ){
118
+ // set the option.
119
+ themeOptions.push(
120
+ `wp option set '${CAWEB_OPTIONS[k]}' "${v}"`
121
+ );
122
+ }
123
+ })
124
+
125
+
126
+ await runDockerCmd({
127
+ spinner,
128
+ environment,
129
+ cmds: themeOptions
130
+ })
131
+ }
132
+
133
+
134
+ }
135
+
136
+ module.exports = {
137
+ activateCAWeb,
138
+ configureCAWeb,
139
+ runDockerCmd
140
+ };
package/lib/cli.js ADDED
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const wpenv_cli = require('@wordpress/env/lib/cli');
6
+
7
+ const chalk = require( 'chalk' );
8
+ const ora = require( 'ora' );
9
+ let yargs = require( 'yargs' );
10
+ const terminalLink = require( 'terminal-link' );
11
+ const { execSync } = require( 'child_process' );
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ const pkg = require( '../package.json' );
17
+ const env = require( './commands' );
18
+ const parseXdebugMode = require( '@wordpress/env/lib/parse-xdebug-mode' );
19
+
20
+ // Colors.
21
+ const boldWhite = chalk.bold.white;
22
+ const wpPrimary = boldWhite.bgHex( '#00669b' );
23
+ const wpGreen = boldWhite.bgHex( '#4ab866' );
24
+ const wpRed = boldWhite.bgHex( '#d94f4f' );
25
+ const wpYellow = boldWhite.bgHex( '#f0b849' );
26
+
27
+ // Spinner.
28
+ const withSpinner =
29
+ ( command ) =>
30
+ ( ...args ) => {
31
+ const spinner = ora().start();
32
+ args[ 0 ].spinner = spinner;
33
+ let time = process.hrtime();
34
+ return command( ...args ).then(
35
+ ( message ) => {
36
+ time = process.hrtime( time );
37
+ spinner.succeed(
38
+ `${ message || spinner.text } (in ${ time[ 0 ] }s ${ (
39
+ time[ 1 ] / 1e6
40
+ ).toFixed( 0 ) }ms)`
41
+ );
42
+ process.exit( 0 );
43
+ },
44
+ ( error ) => {
45
+ if( error ){
46
+ // Error is an unknown error. That means there was a bug in our code.
47
+ spinner.fail(
48
+ typeof error === 'string' ? error : error.message
49
+ );
50
+ // Disable reason: Using console.error() means we get a stack trace.
51
+ console.error( error );
52
+ process.exit( 1 );
53
+ }else{
54
+ spinner.fail( 'An unknown error occurred.' );
55
+ process.exit( 1 );
56
+ }
57
+ }
58
+ );
59
+ };
60
+
61
+ module.exports = function cli() {
62
+ yargs = wpenv_cli();
63
+
64
+ // we are overwriting the default wp-env start command with our own.
65
+ yargs.command(
66
+ 'start',
67
+ wpGreen(
68
+ chalk`Starts WordPress for development on port {bold.underline ${ terminalLink(
69
+ '8888',
70
+ 'http://localhost:8888'
71
+ ) }} (override with WP_ENV_PORT) and tests on port {bold.underline ${ terminalLink(
72
+ '8889',
73
+ 'http://localhost:8889'
74
+ ) }} (override with WP_ENV_TESTS_PORT). The current working directory must be a WordPress installation, a plugin, a theme, or contain a .wp-env.json file. After first install, use the '--update' flag to download updates to mapped sources and to re-apply WordPress configuration options.`
75
+ ),
76
+ ( args ) => {
77
+ args.option( 'update', {
78
+ type: 'boolean',
79
+ describe:
80
+ 'Download source updates and apply WordPress configuration.',
81
+ default: false,
82
+ } );
83
+ args.option( 'xdebug', {
84
+ describe:
85
+ 'Enables Xdebug. If not passed, Xdebug is turned off. If no modes are set, uses "debug". You may set multiple Xdebug modes by passing them in a comma-separated list: `--xdebug=develop,coverage`. See https://xdebug.org/docs/all_settings#mode for information about Xdebug modes.',
86
+ coerce: parseXdebugMode,
87
+ type: 'string',
88
+ } );
89
+ args.option( 'scripts', {
90
+ type: 'boolean',
91
+ describe: 'Execute any configured lifecycle scripts.',
92
+ default: true,
93
+ } );
94
+ args.option( 'bare', {
95
+ type: 'boolean',
96
+ describe: 'True if excluding any downloads from CAWeb, use this if you want to use a local version of the CAWeb Theme, Configurations will still be applied.',
97
+ default: false,
98
+ } );
99
+
100
+ },
101
+ withSpinner( env.start )
102
+ );
103
+
104
+ // Shell Terminal Command.
105
+ yargs.command(
106
+ 'shell ',
107
+ 'Open shell terminal in WordPress environment.',
108
+ (args) => {
109
+ args.positional( 'environment', {
110
+ type: 'string',
111
+ describe: "Which environment to open terminal in.",
112
+ choices: [ 'development', 'tests' ],
113
+ default: 'development',
114
+ } );
115
+ },
116
+ withSpinner( env.shell )
117
+ )
118
+
119
+ // Test Command.
120
+ yargs.command(
121
+ 'test [environment]',
122
+ 'Test commands on a WordPress environment',
123
+ (args) => {
124
+ args.positional( 'environment', {
125
+ type: 'string',
126
+ describe: "Which environment to test in.",
127
+ choices: [ 'development', 'tests' ],
128
+ default: 'development',
129
+ } );
130
+ },
131
+ withSpinner( env.test )
132
+ )
133
+
134
+ return yargs;
135
+ };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ const shell = require('./shell')
10
+ const start = require('./start')
11
+ const test = require( './test' );
12
+
13
+ module.exports = {
14
+ shell,
15
+ start,
16
+ test
17
+ };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const wpEnvRun = require( '@wordpress/env/lib/commands/run' );
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+
11
+ /**
12
+ * Opens shell terminal in WordPress environment
13
+ *
14
+ * @param {Object} options
15
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
16
+ * @param {boolean} options.environment Which environment to open terminal in.
17
+ * @param {boolean} options.debug True if debug mode is enabled.
18
+ */
19
+ module.exports = async function shell({
20
+ spinner,
21
+ environment,
22
+ debug
23
+ }) {
24
+
25
+ const container = 'tests' === environment ? 'tests-cli' : 'cli';
26
+
27
+ await wpEnvRun({
28
+ container: container,
29
+ command: ['bash'],
30
+ "": "",
31
+ envCwd: '.',
32
+ spinner,
33
+ debug
34
+ });
35
+
36
+ };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Modified from wp-env 8.11.0
3
+ * @see @wordpress/env/lib/commands/start
4
+ */
5
+ 'use strict';
6
+ /**
7
+ * External dependencies
8
+ */
9
+ const path = require( 'path' );
10
+ const fs = require( 'fs-extra' );
11
+ const wpEnvStart = require('@wordpress/env/lib/commands/start');
12
+ const util = require( 'util' );
13
+ const { didCacheChange, getCache } = require( '@wordpress/env/lib/cache' );
14
+ const {
15
+ checkDatabaseConnection,
16
+ canAccessWPORG
17
+ } = require( '@wordpress/env/lib/wordpress' );
18
+ const loadConfig = require( '@wordpress/env/lib/config/load-config' );
19
+ const retry = require( '@wordpress/env/lib/retry' );
20
+ const yaml = require( 'js-yaml' );
21
+ const dockerCompose = require( 'docker-compose' );
22
+
23
+ const CONFIG_CACHE_KEY = 'config_checksum';
24
+
25
+ /**
26
+ * Internal dependencies
27
+ */
28
+ const { configureCAWeb } = require('../caweb');
29
+ const { buildWPEnvConfig, buildDockerComposeConfig } = require('../configs');
30
+ const {downloadSources } = require('../download-sources');
31
+
32
+ /**
33
+ * Promisified dependencies
34
+ */
35
+ const { writeFile } = fs.promises;
36
+ const sleep = util.promisify( setTimeout );
37
+
38
+ /**
39
+ * Starts the development server.
40
+ *
41
+ * @param {Object} options
42
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
43
+ * @param {boolean} options.update If true, update sources.
44
+ * @param {string} options.xdebug The Xdebug mode to set.
45
+ * @param {boolean} options.scripts Indicates whether or not lifecycle scripts should be executed.
46
+ * @param {boolean} options.debug True if debug mode is enabled.
47
+ * @param {boolean} options.bare True if excluding any CAWeb Configurations.
48
+ *
49
+ */
50
+ module.exports = async function start({
51
+ spinner,
52
+ update,
53
+ xdebug,
54
+ scripts,
55
+ debug,
56
+ bare
57
+ }) {
58
+ spinner.text = 'Writing configuration file...';
59
+
60
+ // Write CAWeb .wp-env.json file.
61
+ await writeFile(
62
+ path.join(process.cwd(), '.wp-env.json'),
63
+ JSON.stringify( buildWPEnvConfig({bare}), null, 4 )
64
+ );
65
+
66
+ // Get current wp-env cache key
67
+ const config = await loadConfig(path.resolve('.'));
68
+ const { workDirectoryPath } = config;
69
+ const cacheKey = await getCache(CONFIG_CACHE_KEY, {workDirectoryPath});
70
+
71
+ // wp-env launch.
72
+ await wpEnvStart({
73
+ spinner,
74
+ update,
75
+ xdebug,
76
+ scripts,
77
+ debug,
78
+ })
79
+
80
+ // Check if we should configure settings.
81
+ const shouldConfigureWp = ( update ||
82
+ ( await didCacheChange( CONFIG_CACHE_KEY, cacheKey, {
83
+ workDirectoryPath,
84
+ } ) )) &&
85
+ // Don't reconfigure everything when we can't connect to the internet because
86
+ // the majority of update tasks involve connecting to the internet. (Such
87
+ // as downloading sources and pulling docker images.)
88
+ ( await canAccessWPORG() );
89
+
90
+
91
+ // Only run configurations when config has changed.
92
+ if( shouldConfigureWp ){
93
+ // Save pretext from wp-env if it exists for later.
94
+ let preText = undefined !== spinner.prefixText ? spinner.prefixText.slice(0, -1) : '';
95
+
96
+ try {
97
+ // We aren't done lets clear the default WordPress text.
98
+ spinner.prefixText = '';
99
+ spinner.text = '';
100
+
101
+ await checkDatabaseConnection( config );
102
+ } catch ( error ) {
103
+ // Wait 30 seconds for MySQL to accept connections.
104
+ await retry( () => checkDatabaseConnection( config ), {
105
+ times: 30,
106
+ delay: 1000,
107
+ } );
108
+
109
+ // It takes 3-4 seconds for MySQL to be ready after it starts accepting connections.
110
+ await sleep( 4000 );
111
+ }
112
+
113
+ // Download any resources required for CAWeb.
114
+ if( ! bare ){
115
+ await downloadSources({spinner, config});
116
+ }
117
+
118
+ // Write docker-compose.override.yml file to workDirectoryPath.
119
+ await writeFile(
120
+ path.join(workDirectoryPath, 'docker-compose.override.yml'),
121
+ yaml.dump( buildDockerComposeConfig(workDirectoryPath) )
122
+ );
123
+
124
+ // Start phpMyAdmin Service.
125
+ spinner.text = 'Starting phpMyAdmin Service';
126
+
127
+ // We need to bring the WordPress instances up again so they pick up
128
+ // any config changes that may have been added to the docker-compose.override.yml.
129
+ await dockerCompose.upMany(
130
+ [
131
+ 'phpmyadmin','tests-phpmyadmin',
132
+ 'wordpress', 'tests-wordpress',
133
+ 'cli', 'tests-cli'
134
+ ], {
135
+ cwd: workDirectoryPath,
136
+ commandOptions: ['--build', '--force-recreate'],
137
+ log: debug
138
+ })
139
+
140
+ spinner.text = 'Configuring CAWebPublishing Environments...';
141
+
142
+ // Make CAWeb WordPress Configurations.
143
+ await Promise.all( [
144
+ retry( () => configureCAWeb( 'development', config, spinner ), {
145
+ times: 2,
146
+ } ),
147
+ retry( () => configureCAWeb( 'tests', config, spinner ), {
148
+ times: 2,
149
+ } ),
150
+ ] );
151
+
152
+ spinner.prefixText = preText +
153
+ `phpMyAdmin development site started at http://localhost:8080\n` +
154
+ `phpMyAdmin development site started at http://localhost:9090\n\n`;
155
+
156
+ spinner.text = 'Done!';
157
+
158
+ }
159
+
160
+ };
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+ /**
3
+ * External dependencies
4
+ */
5
+ const path = require( 'path' );
6
+ const fs = require( 'fs-extra' );
7
+ const SimpleGit = require( 'simple-git' );
8
+ const { execSync } = require( 'child_process' );
9
+ const dockerCompose = require( 'docker-compose' );
10
+ const loadConfig = require( '@wordpress/env/lib/config/load-config' );
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ const {
16
+ runDockerCmd,
17
+ } = require('../caweb');
18
+
19
+ const { buildWPEnvConfig, buildDockerComposeConfig } = require('../configs');
20
+
21
+ const pkg = require( '../../package.json' );
22
+ const { CAWEB_OPTIONS } = require('../options');
23
+
24
+
25
+ /**
26
+ * Promisified dependencies
27
+ */
28
+ const { writeFile } = fs.promises;
29
+
30
+ /**
31
+ * Test code.
32
+ *
33
+ * @param {Object} options
34
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
35
+ * @param {boolean} options.environment Which environment to test in.
36
+ */
37
+ module.exports = async function test({
38
+ spinner,
39
+ environment
40
+ }) {
41
+
42
+ try {
43
+ const config = await loadConfig(path.resolve('.'));
44
+ /*
45
+ let themeOptions = [];
46
+
47
+ Object.entries(config.env[ 'development' ].config).forEach(([k,v]) => {
48
+ // if the option is prefixed with CAWEB_ and is a valid CAWeb Option.
49
+ if ( `${k}`.startsWith('CAWEB_') && undefined !== CAWEB_OPTIONS[k] ){
50
+ console.log(CAWEB_OPTIONS[k])
51
+ // set the option.
52
+ themeOptions.push(
53
+ `wp option set '${CAWEB_OPTIONS[k]}' "${v}"`
54
+ );
55
+ }
56
+ })
57
+
58
+ console.log(themeOptions);
59
+
60
+
61
+ // Write CAWeb .wp-env.json file.
62
+ await writeFile(
63
+ path.join(process.cwd(), '.wp-env.json'),
64
+ JSON.stringify( buildWPEnvConfig({bare: false}), null, 4 )
65
+ );
66
+
67
+ let result = await runDockerCmd( {
68
+ spinner,
69
+ environment,
70
+ cmds: [`wp option set 'ca_site_version' "5.5"`]
71
+ })
72
+
73
+
74
+ Object.entries(result).forEach((k,v) => {
75
+ //console.log(`${k}:${v}`)
76
+ })
77
+
78
+ //console.log(`Result: ${result}`)
79
+ */
80
+
81
+ } catch(error) {
82
+ console.log(error)
83
+
84
+ process.exit( 1 );
85
+ }
86
+
87
+ };
package/lib/configs.js ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ const path = require( 'path' );
5
+ const fs = require('fs-extra');
6
+ const pkg = require( '../package.json' );
7
+
8
+
9
+ /**
10
+ * Build .wp-env.json
11
+ *
12
+ * @param {boolean} options.bare True if excluding any CAWeb Configurations.
13
+ * @returns object
14
+ */
15
+ const buildWPEnvConfig = ({bare}) => {
16
+ let config = {
17
+ core: `WordPress/WordPress#${pkg.config.WP_VER}`,
18
+ phpVersion: `${pkg.config.PHP_VER}`,
19
+ config: {
20
+ ...pkg.config.DEFAULTS
21
+ }
22
+ }
23
+
24
+ // if not bare then include our theme.
25
+ if( ! bare ){
26
+ config['themes'] = [`CA-CODE-Works/CAWeb`];
27
+ }
28
+
29
+ return config;
30
+
31
+ }
32
+
33
+ /**
34
+ * Build docker-compose.override.yml
35
+ *
36
+ * @param {string} workDirectoryPath Path to the work directory located in ~/.wp-env.
37
+ * @returns object
38
+ */
39
+ const buildDockerComposeConfig = (workDirectoryPath) => {
40
+ let dockerConfig = {
41
+ version: '3.7',
42
+ services: {
43
+ phpmyadmin: {
44
+ image: `phpmyadmin:latest`,
45
+ restart: 'always',
46
+ ports: ['8080:80'],
47
+ environment: {
48
+ PMA_HOST : 'mysql',
49
+ UPLOAD_LIMIT: '3G',
50
+ MEMORY_LIMIT: '5G',
51
+ MAX_EXECUTION_TIME: 7200
52
+ }
53
+ },
54
+ "tests-phpmyadmin": {
55
+ image: `phpmyadmin:latest`,
56
+ restart: 'always',
57
+ ports: ['9090:80'],
58
+ environment: {
59
+ PMA_HOST : 'tests-mysql',
60
+ UPLOAD_LIMIT: '3G',
61
+ MEMORY_LIMIT: '5G',
62
+ MAX_EXECUTION_TIME: 7200
63
+ }
64
+ }
65
+ }
66
+
67
+ };
68
+
69
+ let extraVolumes = [];
70
+
71
+ // Divi theme to wordpress services volumes.
72
+ if( fs.existsSync(path.join(workDirectoryPath, 'Divi')) ){
73
+ extraVolumes = extraVolumes.concat(path.join(workDirectoryPath, 'Divi') + ':/var/www/html/wp-content/themes/Divi');
74
+ }
75
+
76
+ // Divi plugin to wordpress services volumes.
77
+ if( fs.existsSync(path.join(workDirectoryPath, 'divi-builder')) ){
78
+ extraVolumes = extraVolumes.concat(path.join(workDirectoryPath, 'divi-builder') + ':/var/www/html/wp-content/plugins/divi-builder');
79
+ }
80
+
81
+ // Add extra volumes to WordPress instances.
82
+ if( extraVolumes.length ){
83
+
84
+ dockerConfig.services.wordpress = {
85
+ build: {
86
+ context: '.',
87
+ dockerfile: 'WordPress.Dockerfile'
88
+ },
89
+ volumes: extraVolumes,
90
+ };
91
+ dockerConfig.services.cli = {
92
+ build: {
93
+ context: '.',
94
+ dockerfile: 'CLI.Dockerfile'
95
+ },
96
+ volumes: extraVolumes,
97
+ };
98
+ dockerConfig.services['tests-wordpress'] = {
99
+ build: {
100
+ context: '.',
101
+ dockerfile: 'Tests-WordPress.Dockerfile'
102
+ },
103
+ volumes: extraVolumes,
104
+ };
105
+ dockerConfig.services['tests-cli'] = {
106
+ build: {
107
+ context: '.',
108
+ dockerfile: 'Tests-CLI.Dockerfile'
109
+ },
110
+ volumes: extraVolumes,
111
+ };
112
+ }
113
+
114
+ return dockerConfig;
115
+ }
116
+
117
+ module.exports = {
118
+ buildWPEnvConfig,
119
+ buildDockerComposeConfig
120
+ }
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Modified from wp-env 8.11.0
3
+ * Few modifications made:
4
+ * - Downloads CAWebPublishing Resources, excluding WordPress.
5
+ * - Target directory for downloadZipSource is expected to be absolute.
6
+ * - Ensure parent directory of source.path exists before attempting to extract in downloadZipSource, otherwise it will complain that file/directory doesn't exist.
7
+ * @see @wordpress/env/lib/download-sources.js
8
+ */
9
+ 'use strict';
10
+ /**
11
+ * External dependencies
12
+ */
13
+ const util = require( 'util' );
14
+ const path = require( 'path' );
15
+ const fs = require( 'fs-extra' );
16
+ const SimpleGit = require( 'simple-git' );
17
+ const got = require( 'got' );
18
+
19
+ /**
20
+ * Internal dependencies
21
+ */
22
+
23
+ /**
24
+ * Promisified dependencies
25
+ */
26
+ const pipeline = util.promisify( require( 'stream' ).pipeline );
27
+ const extractZip = util.promisify( require( 'extract-zip' ) );
28
+ const rimraf = util.promisify( require( 'rimraf' ) );
29
+
30
+ /**
31
+ * Download CAWeb Resources.
32
+ *
33
+ * @param {Object} spinner The spinner object to show progress.
34
+ * @return {WPConfig} The config object we've loaded.
35
+ */
36
+ async function downloadSources(
37
+ {
38
+ spinner,
39
+ config
40
+ }
41
+ ) {
42
+ const progresses = {};
43
+ const getProgressSetter = ( id ) => ( progress ) => {
44
+ progresses[ id ] = progress;
45
+ spinner.text =
46
+ `Downloading ${id}.\n` +
47
+ Object.entries( progresses )
48
+ .map(
49
+ ( [ key, value ] ) =>
50
+ ` - ${ key }: ${ ( value * 100 ).toFixed( 0 ) }/100%`
51
+ )
52
+ .join( '\n' );
53
+ };
54
+
55
+ const { workDirectoryPath } = config;
56
+ const { development: dev, tests: test } = config.env;
57
+ let sources = [];
58
+
59
+ // Add Divi Theme and plugin to sources.
60
+ if( (undefined !== dev.config.ET_USERNAME &&
61
+ undefined !== dev.config.ET_API_KEY) ||
62
+ (undefined !== test.config.ET_USERNAME &&
63
+ undefined !== test.config.ET_API_KEY)
64
+ ){
65
+ let url = 'https://www.elegantthemes.com/api/api_downloads.php';
66
+ let user = undefined !== dev.config.ET_USERNAME ? dev.config.ET_USERNAME : test.config.ET_USERNAME;
67
+ let key = undefined !== dev.config.ET_API_KEY ? dev.config.ET_API_KEY : test.config.ET_API_KEY;
68
+
69
+ // Add Divi sources.
70
+ sources = sources.concat( [
71
+ {
72
+ basename: 'Divi',
73
+ url: `${url}?api_update=1&theme=Divi&api_key=${key}&username=${user}`,
74
+ path: path.join(workDirectoryPath, 'Divi'),
75
+ type: 'zip'
76
+ },
77
+ {
78
+ basename: 'Divi Plugin',
79
+ url: `${url}?api_update=1&theme=divi-builder&api_key=${key}&username=${user}`,
80
+ path: path.join(workDirectoryPath, 'divi-builder'),
81
+ type: 'zip'
82
+ }
83
+ ]);
84
+ }
85
+
86
+
87
+ // filter any undefined sources.
88
+ /*sources = sources.filter(function( element ) {
89
+ return element !== undefined;
90
+ });
91
+
92
+ */
93
+ await Promise.all(
94
+ sources.map( ( source ) =>
95
+ downloadSource( source, {
96
+ onProgress: getProgressSetter( source.basename ),
97
+ spinner,
98
+ } )
99
+ )
100
+ );
101
+
102
+ };
103
+
104
+ /**
105
+ * Downloads the given source if necessary. The specific action taken depends
106
+ * on the source type. The source is downloaded to source.path.
107
+ *
108
+ * @param {WPSource} source The source to download.
109
+ * @param {Object} options
110
+ * @param {Function} options.onProgress A function called with download progress. Will be invoked with one argument: a number that ranges from 0 to 1 which indicates current download progress for this source.
111
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
112
+ * @param {boolean} options.debug True if debug mode is enabled.
113
+ */
114
+ async function downloadSource(src, { onProgress, spinner, debug } ){
115
+ if( 'git' == src.type ){
116
+ await downloadGitSource(src, { onProgress, spinner, debug });
117
+ }else if( 'zip' == src.type ) {
118
+ await downloadZipSource(src, { onProgress, spinner, debug });
119
+ }
120
+ }
121
+
122
+
123
+ /**
124
+ * Clones the git repository at `source.url` into `source.path`. If the
125
+ * repository already exists, it is updated instead.
126
+ *
127
+ * @param {WPSource} source The source to download.
128
+ * @param {Object} options
129
+ * @param {Function} options.onProgress A function called with download progress. Will be invoked with one argument: a number that ranges from 0 to 1 which indicates current download progress for this source.
130
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
131
+ * @param {boolean} options.debug True if debug mode is enabled.
132
+ */
133
+ async function downloadGitSource( source, { onProgress, spinner, debug } ) {
134
+ const log = debug
135
+ ? ( message ) => {
136
+ spinner.info( `SimpleGit: ${ message }` );
137
+ spinner.start();
138
+ }
139
+ : () => {};
140
+ onProgress( 0 );
141
+
142
+ const progressHandler = ( { progress } ) => {
143
+ onProgress( progress / 100 );
144
+ };
145
+
146
+ log( 'Cloning or getting the repo.' );
147
+ const git = SimpleGit( { progress: progressHandler } );
148
+
149
+ const isRepo =
150
+ fs.existsSync( source.clonePath ) &&
151
+ ( await git.cwd( source.clonePath ).checkIsRepo( 'root' ) );
152
+
153
+ // repository already exists.
154
+ if ( isRepo ) {
155
+ log( 'Repo already exists, using it.' );
156
+ } else {
157
+ // repository doesn't exists, but the directory does.
158
+ if( fs.existsSync( source.clonePath ) ){
159
+ // reinitialize repo.
160
+ await git.cwd( source.clonePath );
161
+
162
+ await git.init().addRemote('origin', source.url );
163
+ }else{
164
+
165
+ await git.clone( source.url, source.clonePath, {
166
+ '--depth': '1',
167
+ '--no-single-branch': null
168
+ } );
169
+ await git.cwd( source.clonePath );
170
+ }
171
+ }
172
+
173
+ log( 'Fetching the specified ref.' );
174
+ await git.fetch( 'origin', source.ref, {
175
+ '--tags': null,
176
+ } );
177
+
178
+ log( 'Checking out the specified ref.' );
179
+ await git.checkout( source.ref, {
180
+ '--force': null
181
+ });
182
+
183
+ onProgress( 1 );
184
+ }
185
+
186
+ /**
187
+ * Downloads and extracts the zip file at `source.url` into `source.path`.
188
+ *
189
+ * @param {WPSource} source The source to download.
190
+ * @param {Object} options
191
+ * @param {Function} options.onProgress A function called with download progress. Will be invoked with one argument: a number that ranges from 0 to 1 which indicates current download progress for this source.
192
+ * @param {Object} options.spinner A CLI spinner which indicates progress.
193
+ * @param {boolean} options.debug True if debug mode is enabled.
194
+ */
195
+ async function downloadZipSource( source, { onProgress, spinner, debug } ) {
196
+ const log = debug
197
+ ? ( message ) => {
198
+ spinner.info( `NodeGit: ${ message }` );
199
+ spinner.start();
200
+ }
201
+ : () => {};
202
+ onProgress( 0 );
203
+
204
+ log( 'Downloading zip file.' );
205
+ const zipName = `${ source.path }.zip`;
206
+ const zipFile = fs.createWriteStream( zipName );
207
+ const responseStream = got.stream( source.url );
208
+ responseStream.on( 'downloadProgress', ( { percent } ) =>
209
+ onProgress( percent )
210
+ );
211
+
212
+ await pipeline( responseStream, zipFile );
213
+
214
+ log( 'Extracting to temporary directory.' );
215
+ // Target directory is expected to be absolute.
216
+ const tempDir = `${ source.path }.temp`;
217
+ await extractZip( zipName, { dir: tempDir } );
218
+
219
+ const files = (
220
+ await Promise.all( [
221
+ rimraf( zipName ),
222
+ rimraf( source.path ),
223
+ fs.promises.readdir( tempDir ),
224
+ ] )
225
+ )[ 2 ];
226
+ /**
227
+ * The plugin container is the extracted directory which is the direct parent
228
+ * of the contents of the plugin. It seems a zip file can have two fairly
229
+ * common approaches to where the content lives:
230
+ * 1. The .zip is the direct container of the files. So after extraction, the
231
+ * extraction directory contains plugin contents.
232
+ * 2. The .zip contains a directory with the same name which is the container.
233
+ * So after extraction, the extraction directory contains another directory.
234
+ * That subdirectory is the actual container of the plugin contents.
235
+ *
236
+ * We support both situations with the following check.
237
+ */
238
+ let pluginContainer = tempDir;
239
+ const firstSubItem = path.join( tempDir, files[ 0 ] );
240
+ if (
241
+ files.length === 1 &&
242
+ ( await fs.promises.lstat( firstSubItem ) ).isDirectory()
243
+ ) {
244
+ // In this case, only one sub directory exists, so use that as the container.
245
+ pluginContainer = firstSubItem;
246
+ }
247
+ await fs.promises.rename( pluginContainer, source.path );
248
+ await rimraf( tempDir );
249
+
250
+ onProgress( 1 );
251
+ }
252
+
253
+ module.exports = {
254
+ downloadSources
255
+
256
+ }
package/lib/env.js ADDED
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+ /**
3
+ * Internal dependencies
4
+ */
5
+
6
+ //const { ValidationError } = require( './config' );
7
+ //const { LifecycleScriptError } = require( './execute-lifecycle-script' );
8
+ const commands = require( './commands' );
9
+
10
+ module.exports = {
11
+ ...commands
12
+ };
package/lib/options.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+
5
+ const caweb_general_options = {
6
+ CAWEB_TEMPLATE_VER: 'ca_site_version', // State Template Version
7
+ CAWEB_NAV_MENU_STYLE: 'ca_default_navigation_menu', // Header Menu Type
8
+ CAWEB_COLORSCHEME: 'ca_site_color_scheme', // Color Scheme
9
+ CAWEB_TITLE_DISPLAY: 'ca_default_post_title_display', // Title Display Default
10
+ CAWEB_STICKY_NAV: 'ca_sticky_navigation', // Sticky Navigation
11
+ CAWEB_MENU_HOME_LINK: 'ca_home_nav_link', // Menu Home Link
12
+ CAWEB_DISPLAY_POSTS_DATE: 'ca_default_post_date_display', // Display Date for Non-Divi Posts
13
+ CAWEB_X_UA_COMPATIBILITY: 'ca_x_ua_compatibility', // Legacy Browser Support
14
+ CAWEB_FRONTPAGE_SEARCH: 'ca_frontpage_search_enabled', // Show Search on Front Page
15
+ }
16
+
17
+ const CAWEB_OPTIONS = {
18
+ ...caweb_general_options
19
+ }
20
+
21
+ module.exports = {
22
+ CAWEB_OPTIONS
23
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@caweb/cli",
3
+ "version": "1.0.0",
4
+ "description": "CAWebPublishing Command Line Interface.",
5
+ "main": "lib/env.js",
6
+ "files": [
7
+ "bin",
8
+ "lib"
9
+ ],
10
+ "bin": {
11
+ "caweb": "bin/caweb"
12
+ },
13
+ "scripts": {
14
+ "caweb": "caweb",
15
+ "launch": "caweb start",
16
+ "pretest": "npm uninstall @caweb/cli",
17
+ "test": "npm pack && npm i caweb-cli-%npm_package_version%.tgz",
18
+ "posttest": "del caweb-cli-%npm_package_version%.tgz"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/CAWebPublishing/caweb-cli.git"
23
+ },
24
+ "keywords": [
25
+ "caweb",
26
+ "cagov"
27
+ ],
28
+ "author": "CAWebPublishing",
29
+ "license": "ISC",
30
+ "bugs": {
31
+ "url": "https://github.com/CAWebPublishing/caweb-cli/issues"
32
+ },
33
+ "homepage": "https://github.com/CAWebPublishing/caweb-cli#readme",
34
+ "dependencies": {
35
+ "@wordpress/env": "^8.11.0",
36
+ "chalk": "^4.0.0",
37
+ "fs-extra": "^11.1.1"
38
+ },
39
+ "config": {
40
+ "WP_VER": "6.3.1",
41
+ "PHP_VER": "8.1",
42
+ "DEFAULTS": {
43
+ "WP_DEFAULT_THEME": "CAWeb",
44
+ "FS_METHOD": "direct",
45
+ "WP_DEBUG": true,
46
+ "WP_DEBUG_LOG": true,
47
+ "WP_DEBUG_DISPLAY": false,
48
+ "ADMIN_COOKIE_PATH": "/",
49
+ "COOKIE_DOMAIN": "",
50
+ "COOKIEPATH": "",
51
+ "SITECOOKIEPATH": ""
52
+ }
53
+ },
54
+ "directories": {
55
+ "doc": "docs",
56
+ "lib": "lib"
57
+ },
58
+ "devDependencies": {}
59
+ }