@automattic/jetpack-cli 1.0.3 → 1.1.1
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/bin/jp.js +206 -4
- package/package.json +1 -1
package/bin/jp.js
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname, resolve } from 'path';
|
|
|
6
6
|
import process from 'process';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
-
import dotenv from 'dotenv';
|
|
9
|
+
import * as dotenv from 'dotenv';
|
|
10
10
|
import prompts from 'prompts';
|
|
11
11
|
import updateNotifier from 'update-notifier';
|
|
12
12
|
|
|
@@ -42,6 +42,37 @@ const isMonorepoRoot = dir => {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Check if the CLI is running from monorepo source (vs npm installed).
|
|
47
|
+
*
|
|
48
|
+
* @return {boolean} True if running from source
|
|
49
|
+
*/
|
|
50
|
+
const isRunningFromSource = () => {
|
|
51
|
+
let dir = __dirname;
|
|
52
|
+
let prevDir;
|
|
53
|
+
while ( dir !== prevDir ) {
|
|
54
|
+
if ( isMonorepoRoot( dir ) ) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
prevDir = dir;
|
|
58
|
+
dir = dirname( dir );
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Compute development version by incrementing patch number.
|
|
65
|
+
*
|
|
66
|
+
* @return {string} Development version string (e.g., "1.0.3-alpha" for released "1.0.2")
|
|
67
|
+
*/
|
|
68
|
+
const computeDevVersion = () => {
|
|
69
|
+
const [ major, minor, patch ] = packageJson.version.split( '.' ).map( Number );
|
|
70
|
+
return `${ major }.${ minor }.${ patch + 1 }-alpha`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Version to display - dev version when running from source, package version otherwise
|
|
74
|
+
const displayVersion = isRunningFromSource() ? computeDevVersion() : packageJson.version;
|
|
75
|
+
|
|
45
76
|
/**
|
|
46
77
|
* Find monorepo root from a starting directory.
|
|
47
78
|
*
|
|
@@ -81,6 +112,154 @@ const cloneMonorepo = async targetDir => {
|
|
|
81
112
|
}
|
|
82
113
|
};
|
|
83
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Get list of git hooks from the .husky directory.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} monorepoRoot - Path to the monorepo root
|
|
119
|
+
* @return {Array<string>} List of hook names
|
|
120
|
+
*/
|
|
121
|
+
const getHuskyHooks = monorepoRoot => {
|
|
122
|
+
const huskyDir = resolve( monorepoRoot, '.husky' );
|
|
123
|
+
if ( ! fs.existsSync( huskyDir ) ) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Filter for valid git hook names (lowercase letters and hyphens)
|
|
128
|
+
const hookPattern = /^[a-z][a-z-]*$/;
|
|
129
|
+
return fs.readdirSync( huskyDir ).filter( name => {
|
|
130
|
+
const fullPath = resolve( huskyDir, name );
|
|
131
|
+
return hookPattern.test( name ) && fs.statSync( fullPath ).isFile();
|
|
132
|
+
} );
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if a husky hook file exists.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} monorepoRoot - Path to the monorepo root
|
|
139
|
+
* @param {string} hookName - Name of the hook
|
|
140
|
+
* @return {boolean} True if the hook exists
|
|
141
|
+
*/
|
|
142
|
+
const huskyHookExists = ( monorepoRoot, hookName ) => {
|
|
143
|
+
return fs.existsSync( resolve( monorepoRoot, '.husky', hookName ) );
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Initialize git hooks that work with Docker.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} monorepoRoot - Path to the monorepo root
|
|
150
|
+
* @throws {Error} If hook installation fails
|
|
151
|
+
*/
|
|
152
|
+
const initHooks = monorepoRoot => {
|
|
153
|
+
// Use git rev-parse --git-common-dir to find the hooks directory.
|
|
154
|
+
// In a regular repo this returns ".git"; in a worktree it returns the
|
|
155
|
+
// main repo's .git path. Hooks are shared across all worktrees.
|
|
156
|
+
const gitCommonDirResult = spawnSync( 'git', [ 'rev-parse', '--git-common-dir' ], {
|
|
157
|
+
cwd: monorepoRoot,
|
|
158
|
+
encoding: 'utf8',
|
|
159
|
+
} );
|
|
160
|
+
|
|
161
|
+
if ( gitCommonDirResult.status !== 0 || ! gitCommonDirResult.stdout.trim() ) {
|
|
162
|
+
throw new Error( 'Could not determine git directory. Is this a git repository?' );
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const hooksDir = resolve( monorepoRoot, gitCommonDirResult.stdout.trim(), 'hooks' );
|
|
166
|
+
|
|
167
|
+
if ( ! fs.existsSync( hooksDir ) ) {
|
|
168
|
+
fs.mkdirSync( hooksDir, { recursive: true } );
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log( chalk.blue( 'Setting up jp git hooks...' ) );
|
|
172
|
+
|
|
173
|
+
// Check if git is configured to use a custom hooks path (e.g., husky)
|
|
174
|
+
const hooksPathResult = spawnSync( 'git', [ 'config', 'core.hooksPath' ], {
|
|
175
|
+
cwd: monorepoRoot,
|
|
176
|
+
encoding: 'utf8',
|
|
177
|
+
} );
|
|
178
|
+
|
|
179
|
+
if ( hooksPathResult.stdout && hooksPathResult.stdout.trim() ) {
|
|
180
|
+
const currentHooksPath = hooksPathResult.stdout.trim();
|
|
181
|
+
console.log( chalk.yellow( ` Detected custom git hooks path: ${ currentHooksPath }` ) );
|
|
182
|
+
console.log( chalk.yellow( ` Resetting to use ${ hooksDir } for jp hooks` ) );
|
|
183
|
+
|
|
184
|
+
const unsetResult = spawnSync( 'git', [ 'config', '--unset', 'core.hooksPath' ], {
|
|
185
|
+
cwd: monorepoRoot,
|
|
186
|
+
} );
|
|
187
|
+
|
|
188
|
+
if ( unsetResult.status !== 0 ) {
|
|
189
|
+
throw new Error( 'Failed to unset core.hooksPath git configuration' );
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const hooks = getHuskyHooks( monorepoRoot );
|
|
194
|
+
if ( hooks.length === 0 ) {
|
|
195
|
+
console.log( chalk.yellow( ' No hooks found in .husky/' ) );
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for ( const hookName of hooks ) {
|
|
200
|
+
const hookPath = resolve( hooksDir, hookName );
|
|
201
|
+
const hookContent = `#!/bin/sh
|
|
202
|
+
# Jetpack CLI git hook
|
|
203
|
+
# Runs the .husky hook in Docker to ensure consistent environment
|
|
204
|
+
|
|
205
|
+
# Exit gracefully if the .husky hook was removed
|
|
206
|
+
if [ ! -f .husky/${ hookName } ]; then
|
|
207
|
+
exit 0
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Check if we're already in the Docker container
|
|
211
|
+
if [ -n "$JETPACK_MONOREPO_ENV" ]; then
|
|
212
|
+
echo "✓ Using jp hooks (running in Docker)"
|
|
213
|
+
sh .husky/${ hookName } "$@"
|
|
214
|
+
exit $?
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# Not in Docker - delegate to jp to run in Docker
|
|
218
|
+
echo "✓ Using jp hooks (delegating to Docker)"
|
|
219
|
+
jp git-hook ${ hookName } "$@"
|
|
220
|
+
exit $?
|
|
221
|
+
`;
|
|
222
|
+
|
|
223
|
+
fs.writeFileSync( hookPath, hookContent, { mode: 0o755 } );
|
|
224
|
+
console.log( chalk.green( ` Created ${ hookName } hook` ) );
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(
|
|
228
|
+
chalk.green( '\n✓ Git hooks installed! Hooks will run automatically in Docker.\n' )
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Run a git hook inside the Docker container.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} monorepoRoot - Path to the monorepo root
|
|
236
|
+
* @param {string} hookName - Name of the hook to run
|
|
237
|
+
* @param {Array} hookArgs - Arguments to pass to the hook
|
|
238
|
+
* @throws {Error} If hook execution fails
|
|
239
|
+
*/
|
|
240
|
+
const runGitHook = ( monorepoRoot, hookName, hookArgs ) => {
|
|
241
|
+
console.log( chalk.blue( `Running ${ hookName } hook in Docker...` ) );
|
|
242
|
+
|
|
243
|
+
if ( ! huskyHookExists( monorepoRoot, hookName ) ) {
|
|
244
|
+
throw new Error( `Unknown git hook: ${ hookName }` );
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Run the .husky hook directly through the monorepo script
|
|
248
|
+
// TTY detection is handled by the monorepo script itself (reconnects to /dev/tty if available)
|
|
249
|
+
const result = spawnSync(
|
|
250
|
+
resolve( monorepoRoot, 'tools/docker/bin/monorepo' ),
|
|
251
|
+
[ 'sh', `.husky/${ hookName }`, ...hookArgs ],
|
|
252
|
+
{
|
|
253
|
+
stdio: 'inherit',
|
|
254
|
+
cwd: monorepoRoot,
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
if ( result.status !== 0 ) {
|
|
259
|
+
throw new Error( `Git hook ${ hookName } failed with status ${ result.status }` );
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
84
263
|
/**
|
|
85
264
|
* Initialize a new Jetpack development environment.
|
|
86
265
|
*
|
|
@@ -109,6 +288,9 @@ const initJetpack = async () => {
|
|
|
109
288
|
|
|
110
289
|
console.log( chalk.green( '\nJetpack monorepo has been cloned successfully!' ) );
|
|
111
290
|
|
|
291
|
+
// Initialize git hooks
|
|
292
|
+
initHooks( targetDir );
|
|
293
|
+
|
|
112
294
|
console.log( '\nNext steps:' );
|
|
113
295
|
|
|
114
296
|
console.log( '1. cd', response.directory );
|
|
@@ -128,7 +310,7 @@ const main = async () => {
|
|
|
128
310
|
|
|
129
311
|
// Handle version flag
|
|
130
312
|
if ( args[ 0 ] === '--version' || args[ 0 ] === '-v' ) {
|
|
131
|
-
console.log( chalk.green(
|
|
313
|
+
console.log( chalk.green( displayVersion ) );
|
|
132
314
|
return;
|
|
133
315
|
}
|
|
134
316
|
|
|
@@ -154,6 +336,27 @@ const main = async () => {
|
|
|
154
336
|
throw new Error( 'Monorepo not found' );
|
|
155
337
|
}
|
|
156
338
|
|
|
339
|
+
// Handle 'init-hooks' command
|
|
340
|
+
if ( args[ 0 ] === 'init-hooks' ) {
|
|
341
|
+
initHooks( monorepoRoot );
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Handle 'git-hook' command
|
|
346
|
+
if ( args[ 0 ] === 'git-hook' ) {
|
|
347
|
+
const hookName = args[ 1 ];
|
|
348
|
+
const hookArgs = args.slice( 2 );
|
|
349
|
+
|
|
350
|
+
if ( ! hookName ) {
|
|
351
|
+
console.error( chalk.red( 'Error: git-hook command requires a hook name' ) );
|
|
352
|
+
console.log( 'Usage: jp git-hook <hook-name> [args...]' );
|
|
353
|
+
throw new Error( 'Missing hook name' );
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
runGitHook( monorepoRoot, hookName, hookArgs );
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
157
360
|
// Handle docker commands that must run on the host machine
|
|
158
361
|
if ( args[ 0 ] === 'docker' ) {
|
|
159
362
|
const hostCommands = [ 'up', 'down', 'stop', 'clean' ];
|
|
@@ -312,8 +515,7 @@ const main = async () => {
|
|
|
312
515
|
[ 'pnpm', 'jetpack', ...args ],
|
|
313
516
|
{
|
|
314
517
|
stdio: 'inherit',
|
|
315
|
-
|
|
316
|
-
cwd: monorepoRoot, // Ensure we're in the monorepo root when running commands
|
|
518
|
+
cwd: monorepoRoot,
|
|
317
519
|
}
|
|
318
520
|
);
|
|
319
521
|
|
package/package.json
CHANGED