@automattic/jetpack-cli 0.1.0-beta.2 → 1.0.0-beta1

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.
Files changed (3) hide show
  1. package/README.md +18 -0
  2. package/bin/jp.js +177 -126
  3. package/package.json +4 -3
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Jetpack
2
+
3
+
4
+ ## How to install Jetpack plugin on your site
5
+ ### Installation From Git Repo
6
+
7
+ ## Contribute
8
+
9
+ ## Get Help
10
+
11
+ ## Security
12
+
13
+ Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic).
14
+
15
+ ## License
16
+
17
+ Licensed under [GNU General Public License v2 (or later)](./LICENSE.txt).
18
+
package/bin/jp.js CHANGED
@@ -1,12 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from 'child_process';
4
- import fs from 'fs';
4
+ import fs, { readFileSync } from 'fs';
5
5
  import { dirname, resolve } from 'path';
6
6
  import process from 'process';
7
+ import { fileURLToPath } from 'url';
7
8
  import chalk from 'chalk';
8
9
  import dotenv from 'dotenv';
9
10
  import prompts from 'prompts';
11
+ import updateNotifier from 'update-notifier';
12
+
13
+ // Get package.json path relative to this file
14
+ const __dirname = dirname( fileURLToPath( import.meta.url ) );
15
+ const packageJson = JSON.parse( readFileSync( resolve( __dirname, '../package.json' ), 'utf8' ) );
16
+
17
+ // Check for updates
18
+ const notifier = updateNotifier( {
19
+ pkg: packageJson,
20
+ updateCheckInterval: 1000 * 60 * 60 * 24, // Check once per day
21
+ } );
22
+
23
+ // Show update notification
24
+ notifier.notify( {
25
+ message:
26
+ 'Update available for Jetpack CLI: {currentVersion} → {latestVersion}\n' +
27
+ 'Run {updateCommand} to update',
28
+ isGlobal: true,
29
+ } );
10
30
 
11
31
  /**
12
32
  * Check if a directory is the monorepo root.
@@ -30,10 +50,13 @@ const isMonorepoRoot = dir => {
30
50
  */
31
51
  const findMonorepoRoot = startDir => {
32
52
  let dir = startDir;
33
- while ( dir !== '/' ) {
53
+ let prevDir;
54
+ while ( dir !== prevDir ) {
55
+ // Keep going until dirname() stops changing the path
34
56
  if ( isMonorepoRoot( dir ) ) {
35
57
  return dir;
36
58
  }
59
+ prevDir = dir;
37
60
  dir = dirname( dir );
38
61
  }
39
62
  return null;
@@ -46,7 +69,6 @@ const findMonorepoRoot = startDir => {
46
69
  * @throws {Error} If clone fails
47
70
  */
48
71
  const cloneMonorepo = async targetDir => {
49
- // eslint-disable-next-line no-console
50
72
  console.log( chalk.blue( 'Cloning Jetpack monorepo...' ) );
51
73
  const result = spawnSync(
52
74
  'git',
@@ -84,15 +106,15 @@ const initJetpack = async () => {
84
106
 
85
107
  try {
86
108
  await cloneMonorepo( targetDir );
87
- // eslint-disable-next-line no-console
109
+
88
110
  console.log( chalk.green( '\nJetpack monorepo has been cloned successfully!' ) );
89
- // eslint-disable-next-line no-console
111
+
90
112
  console.log( '\nNext steps:' );
91
- // eslint-disable-next-line no-console
113
+
92
114
  console.log( '1. cd', response.directory );
93
- // eslint-disable-next-line no-console
115
+
94
116
  console.log( '2. jp docker up' );
95
- // eslint-disable-next-line no-console
117
+
96
118
  console.log( '3. jp docker install' );
97
119
  } catch ( error ) {
98
120
  throw new Error( `Failed to initialize Jetpack: ${ error.message }` );
@@ -104,6 +126,12 @@ const main = async () => {
104
126
  try {
105
127
  const args = process.argv.slice( 2 );
106
128
 
129
+ // Handle version flag
130
+ if ( args[ 0 ] === '--version' || args[ 0 ] === '-v' ) {
131
+ console.log( chalk.green( packageJson.version ) );
132
+ return;
133
+ }
134
+
107
135
  // Handle 'init' command specially
108
136
  if ( args[ 0 ] === 'init' ) {
109
137
  await initJetpack();
@@ -114,144 +142,168 @@ const main = async () => {
114
142
  const monorepoRoot = findMonorepoRoot( process.cwd() );
115
143
 
116
144
  if ( ! monorepoRoot ) {
117
- // eslint-disable-next-line no-console
118
145
  console.error( chalk.red( 'Could not find Jetpack monorepo.' ) );
119
- // eslint-disable-next-line no-console
146
+
120
147
  console.log( '\nTo get started:' );
121
- // eslint-disable-next-line no-console
148
+
122
149
  console.log( '1. Run', chalk.blue( 'jp init' ), 'to clone the repository' );
123
- // eslint-disable-next-line no-console
150
+
124
151
  console.log( ' OR' );
125
- // eslint-disable-next-line no-console
152
+
126
153
  console.log( '2. Navigate to an existing Jetpack monorepo directory' );
127
154
  throw new Error( 'Monorepo not found' );
128
155
  }
129
156
 
130
- // Handle docker commands on the host
157
+ // Handle docker commands that must run on the host machine
131
158
  if ( args[ 0 ] === 'docker' ) {
132
- // Commands that should run in the container
133
- const containerCommands = [ 'build-image', 'install' ];
134
- if ( containerCommands.includes( args[ 1 ] ) ) {
135
- const result = spawnSync(
136
- resolve( monorepoRoot, 'tools/docker/bin/monorepo' ),
137
- [ 'pnpm', 'jetpack', ...args ],
138
- {
139
- stdio: 'inherit',
140
- shell: true,
141
- cwd: monorepoRoot,
142
- }
143
- );
159
+ const hostCommands = [ 'up', 'down', 'stop', 'clean' ];
160
+ if ( hostCommands.includes( args[ 1 ] ) ) {
161
+ // Handle command-specific setup/cleanup
162
+ if ( args[ 1 ] === 'up' ) {
163
+ // Create required directories
164
+ fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/data/jetpack_dev_mysql' ), {
165
+ recursive: true,
166
+ } );
167
+ fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/data/ssh.keys' ), {
168
+ recursive: true,
169
+ } );
170
+ fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/wordpress' ), { recursive: true } );
144
171
 
145
- if ( result.status !== 0 ) {
146
- throw new Error( `Command failed with status ${ result.status }` );
147
- }
148
- return;
149
- }
172
+ // Create empty .env file
173
+ fs.writeFileSync( resolve( monorepoRoot, 'tools/docker/.env' ), '' );
150
174
 
151
- // Run config generation first if this is an 'up' command
152
- if ( args[ 1 ] === 'up' ) {
153
- // Create required directories
154
- fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/data/jetpack_dev_mysql' ), {
155
- recursive: true,
156
- } );
157
- fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/data/ssh.keys' ), { recursive: true } );
158
- fs.mkdirSync( resolve( monorepoRoot, 'tools/docker/wordpress' ), { recursive: true } );
159
-
160
- const images = [
161
- { name: 'mariadb:lts' },
162
- { name: 'automattic/jetpack-wordpress-dev:latest' },
163
- { name: 'phpmyadmin/phpmyadmin:latest', platform: 'linux/amd64' },
164
- { name: 'maildev/maildev', platform: 'linux/amd64' },
165
- { name: 'atmoz/sftp', platform: 'linux/amd64' },
166
- ];
167
-
168
- for ( const image of images ) {
169
- const inspect = spawnSync( 'docker', [ 'image', 'inspect', image.name ], {
170
- stdio: 'ignore',
171
- } );
172
- if ( inspect.status !== 0 ) {
173
- console.log( `Pulling ${ image.name }...` );
174
- const args = [ 'pull', image.name ];
175
- if ( image.platform ) {
176
- args.splice( 1, 0, '--platform', image.platform );
177
- }
178
- const pull = spawnSync( 'docker', args, { stdio: 'inherit' } );
179
- if ( pull.status !== 0 ) {
180
- throw new Error( `Failed to pull ${ image.name }` );
175
+ const configResult = spawnSync(
176
+ resolve( monorepoRoot, 'tools/docker/bin/monorepo' ),
177
+ [ 'pnpm', 'jetpack', 'docker', 'config' ],
178
+ {
179
+ stdio: 'inherit',
180
+ shell: true,
181
+ cwd: monorepoRoot,
181
182
  }
183
+ );
184
+
185
+ if ( configResult.status !== 0 ) {
186
+ throw new Error( 'Failed to generate Docker config' );
182
187
  }
188
+ } else if ( args[ 1 ] === 'clean' ) {
189
+ // After docker-compose down -v, also remove local files
190
+ const projectName = args.includes( '--type=e2e' ) ? 'jetpack_e2e' : 'jetpack_dev';
191
+ const cleanupPaths = [
192
+ resolve( monorepoRoot, 'tools/docker/wordpress/' ),
193
+ resolve( monorepoRoot, 'tools/docker/wordpress-develop/*' ),
194
+ resolve( monorepoRoot, 'tools/docker/logs/', projectName ),
195
+ resolve( monorepoRoot, 'tools/docker/data/', `${ projectName }_mysql` ),
196
+ ];
197
+
198
+ // Function to clean up after docker-compose down
199
+ const cleanupFiles = () => {
200
+ for ( const path of cleanupPaths ) {
201
+ try {
202
+ fs.rmSync( path, { recursive: true, force: true } );
203
+ } catch ( error ) {
204
+ console.warn(
205
+ chalk.yellow( `Warning: Could not remove ${ path }: ${ error.message }` )
206
+ );
207
+ }
208
+ }
209
+ };
210
+
211
+ // Add cleanup to process events to ensure it runs after docker-compose
212
+ process.once( 'beforeExit', cleanupFiles );
213
+
214
+ // Replace 'clean' with 'down -v' in the arguments
215
+ args.splice( 1, 1, 'down', '-v' );
183
216
  }
184
217
 
185
- const configResult = spawnSync(
186
- resolve( monorepoRoot, 'tools/docker/bin/monorepo' ),
187
- [ 'pnpm', 'jetpack', 'docker', 'config' ],
188
- {
189
- stdio: 'inherit',
190
- shell: true,
191
- cwd: monorepoRoot,
192
- }
193
- );
218
+ // Get project name (from docker.js)
219
+ const projectName = args.includes( '--type=e2e' ) ? 'jetpack_e2e' : 'jetpack_dev';
220
+
221
+ // Build environment variables (from docker.js)
222
+ const envVars = {
223
+ ...process.env, // Start with process.env
224
+ };
194
225
 
195
- if ( configResult.status !== 0 ) {
196
- throw new Error( 'Failed to generate Docker config' );
226
+ // Add default env vars if they exist
227
+ if ( fs.existsSync( resolve( monorepoRoot, 'tools/docker/default.env' ) ) ) {
228
+ Object.assign(
229
+ envVars,
230
+ dotenv.parse( fs.readFileSync( resolve( monorepoRoot, 'tools/docker/default.env' ) ) )
231
+ );
197
232
  }
198
- }
199
233
 
200
- // Get project name (from docker.js)
201
- const projectName = args.includes( '--type=e2e' ) ? 'jetpack_e2e' : 'jetpack_dev';
202
-
203
- // Load versions from .github/versions.sh
204
- const versionsPath = resolve( monorepoRoot, '.github/versions.sh' );
205
- const versions = fs.readFileSync( versionsPath, 'utf8' );
206
- const versionVars = {};
207
- versions.split( '\n' ).forEach( line => {
208
- const match = line.match( /^([A-Z_]+)=(.+)$/ );
209
- if ( match ) {
210
- versionVars[ match[ 1 ] ] = match[ 2 ].replace( /['"]/g, '' );
234
+ // Add user overrides from .env if they exist
235
+ if ( fs.existsSync( resolve( monorepoRoot, 'tools/docker/.env' ) ) ) {
236
+ Object.assign(
237
+ envVars,
238
+ dotenv.parse( fs.readFileSync( resolve( monorepoRoot, 'tools/docker/.env' ) ) )
239
+ );
211
240
  }
212
- } );
213
-
214
- // Build environment variables (from docker.js)
215
- const envVars = {
216
- ...process.env,
217
- // Load from default.env
218
- ...( fs.existsSync( resolve( monorepoRoot, 'tools/docker/default.env' ) )
219
- ? dotenv.parse( fs.readFileSync( resolve( monorepoRoot, 'tools/docker/default.env' ) ) )
220
- : {} ),
221
- // Load from .env if it exists
222
- ...( fs.existsSync( resolve( monorepoRoot, 'tools/docker/.env' ) )
223
- ? dotenv.parse( fs.readFileSync( resolve( monorepoRoot, 'tools/docker/.env' ) ) )
224
- : {} ),
225
- HOST_CWD: monorepoRoot,
226
- PHP_VERSION: versionVars.PHP_VERSION,
227
- COMPOSER_VERSION: versionVars.COMPOSER_VERSION,
228
- NODE_VERSION: versionVars.NODE_VERSION,
229
- PNPM_VERSION: versionVars.PNPM_VERSION,
230
- COMPOSE_PROJECT_NAME: projectName,
231
- PORT_WORDPRESS: args.includes( '--type=e2e' ) ? '8889' : '80',
232
- };
233
-
234
- // Build the list of compose files to use
235
- const composeFiles = [
236
- '-f',
237
- resolve( monorepoRoot, 'tools/docker/docker-compose.yml' ),
238
- '-f',
239
- resolve( monorepoRoot, 'tools/docker/compose-mappings.built.yml' ),
240
- '-f',
241
- resolve( monorepoRoot, 'tools/docker/compose-extras.built.yml' ),
242
- ];
243
-
244
- const result = spawnSync( 'docker', [ 'compose', ...composeFiles, ...args.slice( 1 ) ], {
245
- stdio: 'inherit',
246
- shell: true,
247
- cwd: resolve( monorepoRoot, 'tools/docker' ),
248
- env: envVars,
249
- } );
250
241
 
251
- if ( result.status !== 0 ) {
252
- throw new Error( `Docker command failed with status ${ result.status }` );
242
+ // Only set these specific vars if they're not already set in .env
243
+ if ( ! envVars.COMPOSE_PROJECT_NAME ) {
244
+ envVars.COMPOSE_PROJECT_NAME = projectName;
245
+ }
246
+ if ( ! envVars.PORT_WORDPRESS ) {
247
+ envVars.PORT_WORDPRESS = args.includes( '--type=e2e' ) ? '8889' : '80';
248
+ }
249
+
250
+ // Load versions from .github/versions.sh if not already set
251
+ if (
252
+ ! (
253
+ envVars.PHP_VERSION &&
254
+ envVars.COMPOSER_VERSION &&
255
+ envVars.NODE_VERSION &&
256
+ envVars.PNPM_VERSION
257
+ )
258
+ ) {
259
+ const versionsPath = resolve( monorepoRoot, '.github/versions.sh' );
260
+ const versions = fs.readFileSync( versionsPath, 'utf8' );
261
+ const versionVars = {};
262
+ versions.split( '\n' ).forEach( line => {
263
+ const match = line.match( /^([A-Z_]+)=(.+)$/ );
264
+ if ( match ) {
265
+ versionVars[ match[ 1 ] ] = match[ 2 ].replace( /['"]/g, '' );
266
+ }
267
+ } );
268
+
269
+ // Only set version vars if they're not already set
270
+ if ( ! envVars.PHP_VERSION ) envVars.PHP_VERSION = versionVars.PHP_VERSION;
271
+ if ( ! envVars.COMPOSER_VERSION ) envVars.COMPOSER_VERSION = versionVars.COMPOSER_VERSION;
272
+ if ( ! envVars.NODE_VERSION ) envVars.NODE_VERSION = versionVars.NODE_VERSION;
273
+ if ( ! envVars.PNPM_VERSION ) envVars.PNPM_VERSION = versionVars.PNPM_VERSION;
274
+ }
275
+
276
+ // Always set HOST_CWD as it's required for Docker context
277
+ envVars.HOST_CWD = monorepoRoot;
278
+
279
+ // Build the list of compose files to use
280
+ const composeFiles =
281
+ args[ 0 ] === 'docker' && [ 'build-image', 'install' ].includes( args[ 1 ] )
282
+ ? [ '-f', resolve( monorepoRoot, 'tools/docker/docker-compose-monorepo.yml' ) ]
283
+ : [
284
+ '-f',
285
+ resolve( monorepoRoot, 'tools/docker/docker-compose.yml' ),
286
+ '-f',
287
+ resolve( monorepoRoot, 'tools/docker/compose-mappings.built.yml' ),
288
+ '-f',
289
+ resolve( monorepoRoot, 'tools/docker/compose-extras.built.yml' ),
290
+ ];
291
+
292
+ // Add dev profile for monorepo service
293
+ const composeArgs = [ 'compose', '--profile', 'dev', ...composeFiles, ...args.slice( 1 ) ];
294
+
295
+ const result = spawnSync( 'docker', composeArgs, {
296
+ stdio: 'inherit',
297
+ shell: true,
298
+ cwd: resolve( monorepoRoot, 'tools/docker' ),
299
+ env: envVars,
300
+ } );
301
+
302
+ if ( result.status !== 0 ) {
303
+ throw new Error( `Docker command failed with status ${ result.status }` );
304
+ }
305
+ return;
253
306
  }
254
- return;
255
307
  }
256
308
 
257
309
  // Run the monorepo script with the original arguments
@@ -269,7 +321,6 @@ const main = async () => {
269
321
  throw new Error( `Command failed with status ${ result.status }` );
270
322
  }
271
323
  } catch ( error ) {
272
- // eslint-disable-next-line no-console
273
324
  console.error( chalk.red( error.message ) );
274
325
  process.exitCode = 1;
275
326
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/jetpack-cli",
3
- "version": "0.1.0-beta.2",
3
+ "version": "1.0.0-beta1",
4
4
  "description": "Docker-based CLI for Jetpack development",
5
5
  "bin": {
6
6
  "jp": "bin/jp.js"
@@ -10,9 +10,10 @@
10
10
  ],
11
11
  "type": "module",
12
12
  "dependencies": {
13
- "chalk": "^4.1.2",
13
+ "chalk": "^5.4.1",
14
14
  "dotenv": "^16.3.1",
15
- "prompts": "^2.4.2"
15
+ "prompts": "^2.4.2",
16
+ "update-notifier": "^7.0.0"
16
17
  },
17
18
  "publishConfig": {
18
19
  "access": "public"