@automattic/jetpack-cli 1.0.3 → 1.1.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/bin/jp.js +194 -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,142 @@ 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
|
+
const hooksDir = resolve( monorepoRoot, '.git/hooks' );
|
|
154
|
+
|
|
155
|
+
if ( ! fs.existsSync( hooksDir ) ) {
|
|
156
|
+
throw new Error( 'Git hooks directory not found. Is this a git repository?' );
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log( chalk.blue( 'Setting up jp git hooks...' ) );
|
|
160
|
+
|
|
161
|
+
// Check if git is configured to use a custom hooks path (e.g., husky)
|
|
162
|
+
const hooksPathResult = spawnSync( 'git', [ 'config', 'core.hooksPath' ], {
|
|
163
|
+
cwd: monorepoRoot,
|
|
164
|
+
encoding: 'utf8',
|
|
165
|
+
} );
|
|
166
|
+
|
|
167
|
+
if ( hooksPathResult.stdout && hooksPathResult.stdout.trim() ) {
|
|
168
|
+
const currentHooksPath = hooksPathResult.stdout.trim();
|
|
169
|
+
console.log( chalk.yellow( ` Detected custom git hooks path: ${ currentHooksPath }` ) );
|
|
170
|
+
console.log( chalk.yellow( ' Resetting to use .git/hooks/ for jp hooks' ) );
|
|
171
|
+
|
|
172
|
+
const unsetResult = spawnSync( 'git', [ 'config', '--unset', 'core.hooksPath' ], {
|
|
173
|
+
cwd: monorepoRoot,
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
if ( unsetResult.status !== 0 ) {
|
|
177
|
+
throw new Error( 'Failed to unset core.hooksPath git configuration' );
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const hooks = getHuskyHooks( monorepoRoot );
|
|
182
|
+
if ( hooks.length === 0 ) {
|
|
183
|
+
console.log( chalk.yellow( ' No hooks found in .husky/' ) );
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for ( const hookName of hooks ) {
|
|
188
|
+
const hookPath = resolve( hooksDir, hookName );
|
|
189
|
+
const hookContent = `#!/bin/sh
|
|
190
|
+
# Jetpack CLI git hook
|
|
191
|
+
# Runs the .husky hook in Docker to ensure consistent environment
|
|
192
|
+
|
|
193
|
+
# Exit gracefully if the .husky hook was removed
|
|
194
|
+
if [ ! -f .husky/${ hookName } ]; then
|
|
195
|
+
exit 0
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Check if we're already in the Docker container
|
|
199
|
+
if [ -n "$JETPACK_MONOREPO_ENV" ]; then
|
|
200
|
+
echo "✓ Using jp hooks (running in Docker)"
|
|
201
|
+
sh .husky/${ hookName } "$@"
|
|
202
|
+
exit $?
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# Not in Docker - delegate to jp to run in Docker
|
|
206
|
+
echo "✓ Using jp hooks (delegating to Docker)"
|
|
207
|
+
jp git-hook ${ hookName } "$@"
|
|
208
|
+
exit $?
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
fs.writeFileSync( hookPath, hookContent, { mode: 0o755 } );
|
|
212
|
+
console.log( chalk.green( ` Created ${ hookName } hook` ) );
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log(
|
|
216
|
+
chalk.green( '\n✓ Git hooks installed! Hooks will run automatically in Docker.\n' )
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Run a git hook inside the Docker container.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} monorepoRoot - Path to the monorepo root
|
|
224
|
+
* @param {string} hookName - Name of the hook to run
|
|
225
|
+
* @param {Array} hookArgs - Arguments to pass to the hook
|
|
226
|
+
* @throws {Error} If hook execution fails
|
|
227
|
+
*/
|
|
228
|
+
const runGitHook = ( monorepoRoot, hookName, hookArgs ) => {
|
|
229
|
+
console.log( chalk.blue( `Running ${ hookName } hook in Docker...` ) );
|
|
230
|
+
|
|
231
|
+
if ( ! huskyHookExists( monorepoRoot, hookName ) ) {
|
|
232
|
+
throw new Error( `Unknown git hook: ${ hookName }` );
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Run the .husky hook directly through the monorepo script
|
|
236
|
+
// TTY detection is handled by the monorepo script itself (reconnects to /dev/tty if available)
|
|
237
|
+
const result = spawnSync(
|
|
238
|
+
resolve( monorepoRoot, 'tools/docker/bin/monorepo' ),
|
|
239
|
+
[ 'sh', `.husky/${ hookName }`, ...hookArgs ],
|
|
240
|
+
{
|
|
241
|
+
stdio: 'inherit',
|
|
242
|
+
cwd: monorepoRoot,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if ( result.status !== 0 ) {
|
|
247
|
+
throw new Error( `Git hook ${ hookName } failed with status ${ result.status }` );
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
84
251
|
/**
|
|
85
252
|
* Initialize a new Jetpack development environment.
|
|
86
253
|
*
|
|
@@ -109,6 +276,9 @@ const initJetpack = async () => {
|
|
|
109
276
|
|
|
110
277
|
console.log( chalk.green( '\nJetpack monorepo has been cloned successfully!' ) );
|
|
111
278
|
|
|
279
|
+
// Initialize git hooks
|
|
280
|
+
initHooks( targetDir );
|
|
281
|
+
|
|
112
282
|
console.log( '\nNext steps:' );
|
|
113
283
|
|
|
114
284
|
console.log( '1. cd', response.directory );
|
|
@@ -128,7 +298,7 @@ const main = async () => {
|
|
|
128
298
|
|
|
129
299
|
// Handle version flag
|
|
130
300
|
if ( args[ 0 ] === '--version' || args[ 0 ] === '-v' ) {
|
|
131
|
-
console.log( chalk.green(
|
|
301
|
+
console.log( chalk.green( displayVersion ) );
|
|
132
302
|
return;
|
|
133
303
|
}
|
|
134
304
|
|
|
@@ -154,6 +324,27 @@ const main = async () => {
|
|
|
154
324
|
throw new Error( 'Monorepo not found' );
|
|
155
325
|
}
|
|
156
326
|
|
|
327
|
+
// Handle 'init-hooks' command
|
|
328
|
+
if ( args[ 0 ] === 'init-hooks' ) {
|
|
329
|
+
initHooks( monorepoRoot );
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Handle 'git-hook' command
|
|
334
|
+
if ( args[ 0 ] === 'git-hook' ) {
|
|
335
|
+
const hookName = args[ 1 ];
|
|
336
|
+
const hookArgs = args.slice( 2 );
|
|
337
|
+
|
|
338
|
+
if ( ! hookName ) {
|
|
339
|
+
console.error( chalk.red( 'Error: git-hook command requires a hook name' ) );
|
|
340
|
+
console.log( 'Usage: jp git-hook <hook-name> [args...]' );
|
|
341
|
+
throw new Error( 'Missing hook name' );
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
runGitHook( monorepoRoot, hookName, hookArgs );
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
157
348
|
// Handle docker commands that must run on the host machine
|
|
158
349
|
if ( args[ 0 ] === 'docker' ) {
|
|
159
350
|
const hostCommands = [ 'up', 'down', 'stop', 'clean' ];
|
|
@@ -312,8 +503,7 @@ const main = async () => {
|
|
|
312
503
|
[ 'pnpm', 'jetpack', ...args ],
|
|
313
504
|
{
|
|
314
505
|
stdio: 'inherit',
|
|
315
|
-
|
|
316
|
-
cwd: monorepoRoot, // Ensure we're in the monorepo root when running commands
|
|
506
|
+
cwd: monorepoRoot,
|
|
317
507
|
}
|
|
318
508
|
);
|
|
319
509
|
|
package/package.json
CHANGED