diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9ab00c2a5fa1..c54343c347d60 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -943,6 +943,9 @@ importers: chalk: specifier: ^4.1.2 version: 4.1.2 + dotenv: + specifier: ^16.3.1 + version: 16.4.7 prompts: specifier: ^2.4.2 version: 2.4.2 @@ -9588,6 +9591,10 @@ packages: resolution: {integrity: sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==} engines: {node: '>=12'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + dunder-proto@1.0.0: resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} engines: {node: '>= 0.4'} @@ -21746,6 +21753,8 @@ snapshots: dotenv@16.0.2: {} + dotenv@16.4.7: {} + dunder-proto@1.0.0: dependencies: call-bind-apply-helpers: 1.0.1 diff --git a/projects/js-packages/jetpack-cli/bin/jp.js b/projects/js-packages/jetpack-cli/bin/jp.js index b955faeae8120..4c98478ae1975 100755 --- a/projects/js-packages/jetpack-cli/bin/jp.js +++ b/projects/js-packages/jetpack-cli/bin/jp.js @@ -1,12 +1,18 @@ #!/usr/bin/env node import { spawnSync } from 'child_process'; -import fs from 'fs'; +import fs, { readFileSync } from 'fs'; import { dirname, resolve } from 'path'; import process from 'process'; +import { fileURLToPath } from 'url'; import chalk from 'chalk'; +import dotenv from 'dotenv'; import prompts from 'prompts'; +// Get package.json path relative to this file +const __dirname = dirname( fileURLToPath( import.meta.url ) ); +const packageJson = JSON.parse( readFileSync( resolve( __dirname, '../package.json' ), 'utf8' ) ); + /** * Check if a directory is the monorepo root. * @@ -45,7 +51,6 @@ const findMonorepoRoot = startDir => { * @throws {Error} If clone fails */ const cloneMonorepo = async targetDir => { - // eslint-disable-next-line no-console console.log( chalk.blue( 'Cloning Jetpack monorepo...' ) ); const result = spawnSync( 'git', @@ -83,15 +88,15 @@ const initJetpack = async () => { try { await cloneMonorepo( targetDir ); - // eslint-disable-next-line no-console + console.log( chalk.green( '\nJetpack monorepo has been cloned successfully!' ) ); - // eslint-disable-next-line no-console + console.log( '\nNext steps:' ); - // eslint-disable-next-line no-console + console.log( '1. cd', response.directory ); - // eslint-disable-next-line no-console + console.log( '2. jp docker up' ); - // eslint-disable-next-line no-console + console.log( '3. jp docker install' ); } catch ( error ) { throw new Error( `Failed to initialize Jetpack: ${ error.message }` ); @@ -103,6 +108,12 @@ const main = async () => { try { const args = process.argv.slice( 2 ); + // Handle version flag + if ( args[ 0 ] === '--version' || args[ 0 ] === '-v' ) { + console.log( chalk.green( packageJson.version ) ); + return; + } + // Handle 'init' command specially if ( args[ 0 ] === 'init' ) { await initJetpack(); @@ -113,19 +124,145 @@ const main = async () => { const monorepoRoot = findMonorepoRoot( process.cwd() ); if ( ! monorepoRoot ) { - // eslint-disable-next-line no-console console.error( chalk.red( 'Could not find Jetpack monorepo.' ) ); - // eslint-disable-next-line no-console + console.log( '\nTo get started:' ); - // eslint-disable-next-line no-console + console.log( '1. Run', chalk.blue( 'jp init' ), 'to clone the repository' ); - // eslint-disable-next-line no-console + console.log( ' OR' ); - // eslint-disable-next-line no-console + console.log( '2. Navigate to an existing Jetpack monorepo directory' ); throw new Error( 'Monorepo not found' ); } + // Handle docker commands on the host + if ( args[ 0 ] === 'docker' ) { + // Commands that should run in the container + const containerCommands = [ 'build-image', 'install' ]; + if ( containerCommands.includes( args[ 1 ] ) ) { + const result = spawnSync( + resolve( monorepoRoot, 'tools/docker/bin/monorepo' ), + [ 'pnpm', 'jetpack', ...args ], + { + stdio: 'inherit', + shell: true, + cwd: monorepoRoot, + } + ); + + if ( result.status !== 0 ) { + throw new Error( `Command failed with status ${ result.status }` ); + } + return; + } + + // Run config generation first if this is an 'up' command + if ( args[ 1 ] === 'up' ) { + // Create required directories + fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/data/jetpack_dev_mysql' ), { + recursive: true, + } ); + fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/data/ssh.keys' ), { recursive: true } ); + fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/wordpress' ), { recursive: true } ); + + const images = [ + { name: 'mariadb:lts' }, + { name: 'automattic/jetpack-wordpress-dev:latest' }, + { name: 'phpmyadmin/phpmyadmin:latest', platform: 'linux/amd64' }, + { name: 'maildev/maildev', platform: 'linux/amd64' }, + { name: 'atmoz/sftp', platform: 'linux/amd64' }, + ]; + + for ( const image of images ) { + const inspect = spawnSync( 'docker', [ 'image', 'inspect', image.name ], { + stdio: 'ignore', + } ); + if ( inspect.status !== 0 ) { + console.log( chalk.blue( `Pulling ${ image.name }...` ) ); + const pullArgs = [ 'pull', image.name ]; + if ( image.platform ) { + pullArgs.splice( 1, 0, '--platform', image.platform ); + } + const pull = spawnSync( 'docker', pullArgs, { stdio: 'inherit' } ); + if ( pull.status !== 0 ) { + throw new Error( `Failed to pull ${ image.name }` ); + } + } + } + + const configResult = spawnSync( + resolve( monorepoRoot, 'tools/docker/bin/monorepo' ), + [ 'pnpm', 'jetpack', 'docker', 'config' ], + { + stdio: 'inherit', + shell: true, + cwd: monorepoRoot, + } + ); + + if ( configResult.status !== 0 ) { + throw new Error( 'Failed to generate Docker config' ); + } + } + + // Get project name (from docker.js) + const projectName = args.includes( '--type=e2e' ) ? 'jetpack_e2e' : 'jetpack_dev'; + + // Load versions from .github/versions.sh + const versionsPath = resolve( monorepoRoot, '.github/versions.sh' ); + const versions = fs.readFileSync( versionsPath, 'utf8' ); + const versionVars = {}; + versions.split( '\n' ).forEach( line => { + const match = line.match( /^([A-Z_]+)=(.+)$/ ); + if ( match ) { + versionVars[ match[ 1 ] ] = match[ 2 ].replace( /['"]/g, '' ); + } + } ); + + // Build environment variables (from docker.js) + const envVars = { + ...process.env, + // Load from default.env + ...( fs.existsSync( resolve( monorepoRoot, 'tools/docker/default.env' ) ) + ? dotenv.parse( fs.readFileSync( resolve( monorepoRoot, 'tools/docker/default.env' ) ) ) + : {} ), + // Load from .env if it exists + ...( fs.existsSync( resolve( monorepoRoot, 'tools/docker/.env' ) ) + ? dotenv.parse( fs.readFileSync( resolve( monorepoRoot, 'tools/docker/.env' ) ) ) + : {} ), + HOST_CWD: monorepoRoot, + PHP_VERSION: versionVars.PHP_VERSION, + COMPOSER_VERSION: versionVars.COMPOSER_VERSION, + NODE_VERSION: versionVars.NODE_VERSION, + PNPM_VERSION: versionVars.PNPM_VERSION, + COMPOSE_PROJECT_NAME: projectName, + PORT_WORDPRESS: args.includes( '--type=e2e' ) ? '8889' : '80', + }; + + // Build the list of compose files to use + const composeFiles = [ + '-f', + resolve( monorepoRoot, 'tools/docker/docker-compose.yml' ), + '-f', + resolve( monorepoRoot, 'tools/docker/compose-mappings.built.yml' ), + '-f', + resolve( monorepoRoot, 'tools/docker/compose-extras.built.yml' ), + ]; + + const result = spawnSync( 'docker', [ 'compose', ...composeFiles, ...args.slice( 1 ) ], { + stdio: 'inherit', + shell: true, + cwd: resolve( monorepoRoot, 'tools/docker' ), + env: envVars, + } ); + + if ( result.status !== 0 ) { + throw new Error( `Docker command failed with status ${ result.status }` ); + } + return; + } + // Run the monorepo script with the original arguments const result = spawnSync( resolve( monorepoRoot, 'tools/docker/bin/monorepo' ), @@ -141,7 +278,6 @@ const main = async () => { throw new Error( `Command failed with status ${ result.status }` ); } } catch ( error ) { - // eslint-disable-next-line no-console console.error( chalk.red( error.message ) ); process.exitCode = 1; } diff --git a/projects/js-packages/jetpack-cli/eslint.config.mjs b/projects/js-packages/jetpack-cli/eslint.config.mjs new file mode 100644 index 0000000000000..5ad3c9460cc3f --- /dev/null +++ b/projects/js-packages/jetpack-cli/eslint.config.mjs @@ -0,0 +1,11 @@ +import makeBaseConfig from 'jetpack-js-tools/eslintrc/base.mjs'; + +export default [ + ...makeBaseConfig( import.meta.url, { envs: [ 'node' ] } ), + { + rules: { + 'no-console': 'off', + 'n/no-process-exit': 'off', + }, + }, +]; diff --git a/projects/js-packages/jetpack-cli/package.json b/projects/js-packages/jetpack-cli/package.json index aa1e4d78e476e..dd08ef03668d2 100644 --- a/projects/js-packages/jetpack-cli/package.json +++ b/projects/js-packages/jetpack-cli/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-cli", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "description": "Docker-based CLI for Jetpack development", "bin": { "jp": "bin/jp.js" @@ -11,6 +11,7 @@ "type": "module", "dependencies": { "chalk": "^4.1.2", + "dotenv": "^16.3.1", "prompts": "^2.4.2" }, "publishConfig": { diff --git a/tools/cli/commands/docker.js b/tools/cli/commands/docker.js index 9d0291aee259c..8626a47a37427 100644 --- a/tools/cli/commands/docker.js +++ b/tools/cli/commands/docker.js @@ -554,6 +554,15 @@ const execJtCmdHandler = argv => { } }; +/** + * Generate Docker configuration files. + * + * @param {object} argv - The command line arguments + */ +async function generateConfig( argv ) { + await setConfig( argv ); +} + /** * Definition for the Docker commands. * @@ -561,7 +570,7 @@ const execJtCmdHandler = argv => { * @return {object} Yargs with the Docker commands defined. */ export function dockerDefine( yargs ) { - yargs.command( { + return yargs.command( { command: 'docker ', description: 'Docker stuff', builder: yarg => { @@ -832,9 +841,15 @@ export function dockerDefine( yargs ) { const envOpts = buildEnv( argv ); composeExecutor( argv, opts, envOpts ); }, + } ) + .command( { + command: 'config', + description: 'Generate Docker configuration files', + builder: yargCmd => defaultOpts( yargCmd ), + handler: async argv => { + await generateConfig( argv ); + }, } ); }, } ); - - return yargs; } diff --git a/tools/docker/bin/monorepo b/tools/docker/bin/monorepo index e167b19145391..8a6cc39d39c8d 100755 --- a/tools/docker/bin/monorepo +++ b/tools/docker/bin/monorepo @@ -14,30 +14,8 @@ echo "Running command in monorepo container: $*" # Source the versions file source "$MONOREPO_ROOT/.github/versions.sh" -# Pre-pull Docker images if this is a docker command -if [ "$1" = "pnpm" ] && [ "$2" = "jetpack" ] && [ "$3" = "docker" ] && [ "$4" = "up" ]; then - echo "Pre-pulling required Docker images..." - # Ensure MySQL data directory exists - mkdir -p "$MONOREPO_ROOT/tools/docker/data/jetpack_dev_mysql" - # Ensure SSH keys directory exists - mkdir -p "$MONOREPO_ROOT/tools/docker/data/ssh.keys" - # Only pull images that aren't present - if ! docker image inspect mariadb:lts >/dev/null 2>&1; then - docker pull mariadb:lts - fi - if ! docker image inspect automattic/jetpack-wordpress-dev:latest >/dev/null 2>&1; then - docker pull automattic/jetpack-wordpress-dev:latest - fi - if ! docker image inspect phpmyadmin/phpmyadmin:latest >/dev/null 2>&1; then - docker pull --platform linux/arm64 phpmyadmin/phpmyadmin:latest - fi - if ! docker image inspect maildev/maildev >/dev/null 2>&1; then - docker pull --platform linux/arm64 maildev/maildev - fi - if ! docker image inspect atmoz/sftp >/dev/null 2>&1; then - docker pull --platform linux/arm64 atmoz/sftp - fi -fi +# Export variables needed by docker-compose +export HOST_CWD="$MONOREPO_ROOT" # Build the image if it doesn't exist if ! docker image inspect jetpack-monorepo:latest >/dev/null 2>&1; then @@ -67,6 +45,8 @@ docker run --rm -it \ -e TERM=$TERM \ -e COLORTERM=$COLORTERM \ -e DOCKER_ROOT="$MONOREPO_ROOT/tools/docker" \ + -e HOST_CWD="$MONOREPO_ROOT" \ + -e WORKSPACE_PATH="$MONOREPO_ROOT" \ -e NPM_CONFIG_USERCONFIG=/root/.npmrc \ -e NPM_CONFIG_CACHE=/root/.npm \ -e PNPM_HOME=/root/.local/share/pnpm \ diff --git a/tools/docker/docker-compose.yml b/tools/docker/docker-compose.yml index f0eb1ac222808..9e22a06fe6a17 100644 --- a/tools/docker/docker-compose.yml +++ b/tools/docker/docker-compose.yml @@ -52,7 +52,7 @@ services: monorepo: build: - context: . + context: ${HOST_CWD}/tools/docker dockerfile: Dockerfile.monorepo args: PHP_VERSION: ${PHP_VERSION} @@ -60,5 +60,5 @@ services: NODE_VERSION: ${NODE_VERSION} PNPM_VERSION: ${PNPM_VERSION} volumes: - - ../..:${WORKSPACE_PATH:-/workspace} - working_dir: ${WORKSPACE_PATH:-/workspace} + - ${HOST_CWD}:/usr/local/src/jetpack-monorepo + working_dir: /usr/local/src/jetpack-monorepo