@joltdesign/scripts 0.20.0 → 0.21.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.
@@ -1,17 +1,67 @@
1
1
  import { userInfo } from 'node:os';
2
2
  import ansis from 'ansis';
3
3
  import { Option } from 'clipanion';
4
- import { execC } from '../utils.js';
4
+ import * as t from 'typanion';
5
+ import { execC, which } from '../utils.js';
5
6
  import JoltCommand from './JoltCommand.js';
7
+ // Possible sub-arguments for the CLI command as of WP-CLI v2.12.0
8
+ const possibleCliArgs = [
9
+ 'alias',
10
+ 'cache',
11
+ 'check-update',
12
+ 'cmd-dump',
13
+ 'completions',
14
+ 'has-command',
15
+ 'info',
16
+ 'param-dump',
17
+ 'update',
18
+ 'version',
19
+ ];
20
+ let maybeAddCliArg = true;
6
21
  export class WPCommand extends JoltCommand {
22
+ constructor() {
23
+ super(...arguments);
24
+ this.wpArgs = Option.Proxy();
25
+ }
26
+ async command() {
27
+ const { cli, wpArgs } = this;
28
+ // Proxy to wp-cli for backwards compatibility
29
+ maybeAddCliArg = false;
30
+ const result = await cli.run(['wp-cli', ...wpArgs]);
31
+ maybeAddCliArg = true;
32
+ return result;
33
+ }
34
+ }
35
+ WPCommand.paths = [['wp']];
36
+ export class WPCLICommand extends JoltCommand {
7
37
  constructor() {
8
38
  super(...arguments);
9
39
  this.requiredCommands = ['docker', 'compose'];
10
40
  this.wpArgs = Option.Proxy();
11
41
  }
12
42
  async command() {
13
- const { config, context, context: { stderr }, } = this;
43
+ var _a;
44
+ const { config, context, context: { stderr }, wpArgs, } = this;
45
+ // Check if wp executable exists and no wp script in package.json
46
+ const wpExecutable = await which('wp');
47
+ const packageJson = await config.getPackageJson();
48
+ const hasWpScript = (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.scripts) === null || _a === void 0 ? void 0 : _a.wp;
49
+ if (wpExecutable && !hasWpScript) {
50
+ // Use wp executable directly
51
+ let realArgs = wpArgs;
52
+ if (maybeAddCliArg && possibleCliArgs.includes(realArgs[0])) {
53
+ realArgs = ['cli', ...realArgs];
54
+ }
55
+ const parsedArgs = await Promise.all(realArgs.map((x) => config.parseArg(x)));
56
+ const result = await execC('wp', parsedArgs, { context, reject: false });
57
+ return result.exitCode;
58
+ }
59
+ // Fall back to container-based approach
14
60
  const containerName = await this.getContainerName();
61
+ let realArgs = wpArgs;
62
+ if (maybeAddCliArg && possibleCliArgs.includes(realArgs[0])) {
63
+ realArgs = ['cli', ...realArgs];
64
+ }
15
65
  if (!containerName) {
16
66
  stderr.write(ansis.red(`Couldn't find a WP CLI container. Set it with the 'wpCliContainer' config key.\n`));
17
67
  return 1;
@@ -21,7 +71,7 @@ export class WPCommand extends JoltCommand {
21
71
  const userArg = uid !== undefined && uid !== -1 && `--user='${uid}:${gid}'`;
22
72
  const profile = await this.getContainerProfile(containerName);
23
73
  const [composeCommand, args] = await config.getComposeCommand();
24
- args.push(profile ? `--profile=${profile}` : '', 'run', '--rm', userArg || '', containerName, 'wp', ...this.wpArgs);
74
+ args.push(profile ? `--profile=${profile}` : '', 'run', '--rm', userArg || '', containerName, 'wp', ...realArgs);
25
75
  const parsedArgs = await Promise.all(args.map((x) => config.parseArg(x)));
26
76
  const result = await execC(composeCommand, parsedArgs, { context, reject: false });
27
77
  return result.exitCode;
@@ -55,5 +105,674 @@ export class WPCommand extends JoltCommand {
55
105
  return service.profiles ? service.profiles[0] : undefined;
56
106
  }
57
107
  }
58
- WPCommand.paths = [['wp'], ['wp-cli']];
108
+ WPCLICommand.paths = [['wp', 'cli'], ['wp-cli']];
109
+ export class WPUpdateCommand extends JoltCommand {
110
+ constructor() {
111
+ super(...arguments);
112
+ this.requiredCommands = ['git'];
113
+ this.skipCore = Option.Boolean('--skip-core', false, { description: 'Skip WordPress core updates' });
114
+ this.skipPlugins = Option.Boolean('--skip-plugins', false, { description: 'Skip plugin updates' });
115
+ this.skipThemes = Option.Boolean('--skip-themes', false, { description: 'Skip theme updates' });
116
+ }
117
+ async command() {
118
+ const { config, context: { stdout, stderr }, skipCore, skipPlugins, skipThemes, logo, } = this;
119
+ stdout.write(ansis.bold(`${logo} WordPress Updates\n\n`));
120
+ // Load configuration
121
+ const wpConfig = await config.loadWordPressConfig();
122
+ if (!wpConfig) {
123
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
124
+ return 1;
125
+ }
126
+ // Use `jolt wp cli ...` via execC with the configured package runner
127
+ let updatedPluginCount = 0;
128
+ let updatedThemeCount = 0;
129
+ let updatedCore = false;
130
+ // Get current status
131
+ if (!skipPlugins) {
132
+ stdout.write(ansis.cyan('🔌 Checking plugins...\n'));
133
+ const yarnCommand = await config.command('yarn');
134
+ const pluginsResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'plugin', 'list', '--json'], {
135
+ reject: false,
136
+ });
137
+ const pluginsJson = pluginsResult.exitCode === 0 ? String(pluginsResult.stdout || '') : null;
138
+ if (pluginsJson) {
139
+ const plugins = this.parsePluginJson(pluginsJson);
140
+ stdout.write(`Found ${plugins.length} plugins\n`);
141
+ }
142
+ }
143
+ if (!skipThemes) {
144
+ stdout.write(ansis.cyan('🎨 Checking themes...\n'));
145
+ const yarnCommand = await config.command('yarn');
146
+ const themesResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'theme', 'list', '--json'], {
147
+ reject: false,
148
+ });
149
+ const themesJson = themesResult.exitCode === 0 ? String(themesResult.stdout || '') : null;
150
+ if (themesJson) {
151
+ const themes = this.parseThemeJson(themesJson);
152
+ stdout.write(`Found ${themes.length} themes\n`);
153
+ }
154
+ }
155
+ // We'll create the update branch only when we need to make our first commit
156
+ const branchRef = { branch: undefined, created: false };
157
+ // Disable git hooks temporarily
158
+ const originalHookPath = await this.disableGitHooks();
159
+ try {
160
+ // Update plugins
161
+ if (!skipPlugins) {
162
+ stdout.write(ansis.cyan('🔌 Updating plugins...\n'));
163
+ const yarnCommand = await config.command('yarn');
164
+ const pluginsResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'plugin', 'list', '--json'], {
165
+ reject: false,
166
+ });
167
+ const pluginsJson = pluginsResult.exitCode === 0 ? String(pluginsResult.stdout || '') : null;
168
+ if (pluginsJson) {
169
+ const plugins = this.parsePluginJson(pluginsJson);
170
+ for (const plugin of plugins) {
171
+ const didUpdate = await this.maybeUpdatePlugin(plugin, wpConfig, branchRef);
172
+ if (didUpdate) {
173
+ updatedPluginCount++;
174
+ }
175
+ }
176
+ }
177
+ }
178
+ // Update themes
179
+ if (!skipThemes) {
180
+ stdout.write(ansis.cyan('🎨 Updating themes...\n'));
181
+ const yarnCommand = await config.command('yarn');
182
+ const themesResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'theme', 'list', '--json'], {
183
+ reject: false,
184
+ });
185
+ const themesJson = themesResult.exitCode === 0 ? String(themesResult.stdout || '') : null;
186
+ if (themesJson) {
187
+ const themes = this.parseThemeJson(themesJson);
188
+ for (const theme of themes) {
189
+ const didUpdate = await this.maybeUpdateTheme(theme, wpConfig, branchRef);
190
+ if (didUpdate) {
191
+ updatedThemeCount++;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ // Update core
197
+ if (!skipCore) {
198
+ stdout.write(ansis.cyan('📦 Checking WordPress core...\n'));
199
+ updatedCore = await this.maybeUpdateCore(wpConfig, branchRef);
200
+ }
201
+ }
202
+ finally {
203
+ await this.rollbackGitHooks(originalHookPath);
204
+ }
205
+ // Summary
206
+ stdout.write(ansis.green('\n✅ Update complete!\n'));
207
+ stdout.write(ansis.cyan(`🔌 Updated ${updatedPluginCount} plugins\n`));
208
+ stdout.write(ansis.cyan(`🎨 Updated ${updatedThemeCount} themes\n`));
209
+ if (updatedCore) {
210
+ stdout.write(ansis.cyan('📦 Updated WordPress core\n'));
211
+ }
212
+ const totalUpdates = updatedPluginCount + updatedThemeCount + (updatedCore ? 1 : 0);
213
+ if (totalUpdates > 0 && branchRef.created) {
214
+ stdout.write(ansis.yellow('\nNext steps:\n'));
215
+ stdout.write(`• Review updates: ${ansis.dim('jolt wp update modify')}\n`);
216
+ // Use root config value directly
217
+ stdout.write(`• Merge to ${await config.get('branch')}: ${ansis.dim('jolt wp update merge')}\n`);
218
+ }
219
+ else if (totalUpdates === 0) {
220
+ stdout.write(ansis.green('\n✅ No updates available - staying on current branch\n'));
221
+ }
222
+ return 0;
223
+ }
224
+ // Note: WP CLI invocations run via `yarn jolt wp cli ...` using execC directly.
225
+ parsePluginJson(pluginsJson) {
226
+ // Trim off any preceding warnings, try to look for the start of the actual JSON.
227
+ const trimmed = pluginsJson.substring(pluginsJson.indexOf('[{'));
228
+ const allPlugins = JSON.parse(trimmed);
229
+ return allPlugins.filter((plugin) => !['dropin', 'must-use'].includes(plugin.status));
230
+ }
231
+ parseThemeJson(themeJson) {
232
+ // Trim off any preceding warnings, try to look for the start of the actual JSON.
233
+ const trimmed = themeJson.substring(themeJson.indexOf('[{'));
234
+ return JSON.parse(trimmed);
235
+ }
236
+ // (No helper) obtain the package runner directly via config.command('yarn') where needed
237
+ async createBranch() {
238
+ const { config, context } = this;
239
+ const isoDate = new Date().toISOString().replace(/[:.]/g, '-').replace(/T/, '_').slice(0, -5);
240
+ const branchName = `joltWpUpdate/${isoDate}`;
241
+ const gitCommand = await config.command('git');
242
+ await execC(gitCommand, ['checkout', '-b', branchName], { context });
243
+ return branchName;
244
+ }
245
+ async ensureBranchCreated(_wpConfig, branchRef) {
246
+ if (!branchRef.created) {
247
+ branchRef.branch = await this.createBranch();
248
+ branchRef.created = true;
249
+ const { context: { stdout }, } = this;
250
+ stdout.write(ansis.green(`📋 Created update branch ${branchRef.branch}\n`));
251
+ }
252
+ return branchRef.branch;
253
+ }
254
+ async disableGitHooks() {
255
+ const { config, context } = this;
256
+ const gitCommand = await config.command('git');
257
+ try {
258
+ const result = await execC(gitCommand, ['config', '--get', 'core.hooksPath'], { context, reject: false });
259
+ const originalHookPath = String(result.stdout || '').trim();
260
+ await execC(gitCommand, ['config', 'core.hooksPath', '/dev/null'], { context });
261
+ return originalHookPath;
262
+ }
263
+ catch (_a) {
264
+ await execC(gitCommand, ['config', 'core.hooksPath', '/dev/null'], { context });
265
+ return '';
266
+ }
267
+ }
268
+ async rollbackGitHooks(originalHookPath) {
269
+ const { config, context } = this;
270
+ const gitCommand = await config.command('git');
271
+ if (originalHookPath) {
272
+ await execC(gitCommand, ['config', 'core.hooksPath', originalHookPath], { context });
273
+ }
274
+ else {
275
+ await execC(gitCommand, ['config', '--unset', 'core.hooksPath'], { context, reject: false });
276
+ }
277
+ }
278
+ async maybeUpdatePlugin(plugin, wpConfig, branchRef) {
279
+ const { context: { stdout }, } = this;
280
+ if (wpConfig.doNotUpdate.includes(plugin.name)) {
281
+ stdout.write(ansis.dim(` Skipping ${plugin.name} (configured to skip)\n`));
282
+ return false;
283
+ }
284
+ stdout.write(` Checking ${plugin.name}...`);
285
+ if (plugin.update === 'available') {
286
+ stdout.write(ansis.green(' updating\n'));
287
+ return await this.updatePlugin(plugin, wpConfig, branchRef);
288
+ }
289
+ if (plugin.update === 'none') {
290
+ stdout.write(ansis.dim(' up to date\n'));
291
+ }
292
+ else if (plugin.update === 'version higher than expected') {
293
+ stdout.write(ansis.yellow(` local version ${plugin.version} is higher than remote\n`));
294
+ }
295
+ else {
296
+ stdout.write(ansis.red(` unknown status: ${plugin.update}\n`));
297
+ }
298
+ return false;
299
+ }
300
+ async maybeUpdateTheme(theme, wpConfig, branchRef) {
301
+ const { context: { stdout }, } = this;
302
+ if (wpConfig.doNotUpdate.includes(theme.name)) {
303
+ stdout.write(ansis.dim(` Skipping ${theme.name} (configured to skip)\n`));
304
+ return false;
305
+ }
306
+ stdout.write(` Checking ${theme.name}...`);
307
+ if (theme.update === 'available') {
308
+ stdout.write(ansis.green(' updating\n'));
309
+ return await this.updateTheme(theme, wpConfig, branchRef);
310
+ }
311
+ if (theme.update === 'none') {
312
+ stdout.write(ansis.dim(' up to date\n'));
313
+ }
314
+ else if (theme.update === 'version higher than expected') {
315
+ stdout.write(ansis.yellow(` local version ${theme.version} is higher than remote\n`));
316
+ }
317
+ else {
318
+ stdout.write(ansis.red(` unknown status: ${theme.update}\n`));
319
+ }
320
+ return false;
321
+ }
322
+ async updatePlugin(plugin, wpConfig, branchRef) {
323
+ const { config, context, context: { stdout, stderr }, } = this;
324
+ try {
325
+ const gitCommand = await config.command('git');
326
+ const details = await this.getPluginDetails(plugin.name);
327
+ if (!details) {
328
+ return false;
329
+ }
330
+ const fromVersion = details.version;
331
+ const prettyTitle = this.cleanTitle(details.title);
332
+ const location = `${wpConfig.pluginFolder}/${plugin.name}`;
333
+ const yarnCommand = await config.command('yarn');
334
+ const updateResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'plugin', 'update', plugin.name], {
335
+ reject: false,
336
+ });
337
+ const updateResultStr = updateResult.exitCode === 0 ? String(updateResult.stdout || '') : null;
338
+ if (!updateResultStr) {
339
+ stderr.write(ansis.red(` Error updating ${plugin.name}\n`));
340
+ return false;
341
+ }
342
+ const newDetails = await this.getPluginDetails(plugin.name);
343
+ if (!newDetails || newDetails.version === details.version) {
344
+ stderr.write(ansis.red(' Update failed!\n'));
345
+ return false;
346
+ }
347
+ // Ensure branch is created before making our first commit
348
+ await this.ensureBranchCreated(wpConfig, branchRef);
349
+ const commitMessage = this.sanitizeCommitMessage(`Update ${prettyTitle} to ${newDetails.version}`);
350
+ await execC(gitCommand, ['add', location], { context });
351
+ await execC(gitCommand, ['commit', '-m', commitMessage], {
352
+ context,
353
+ shell: false,
354
+ env: { SKIP: 'prepare-commit-msg' },
355
+ });
356
+ stdout.write(ansis.green(` Updated ${prettyTitle} from ${fromVersion} to ${newDetails.version}\n`));
357
+ return true;
358
+ }
359
+ catch (error) {
360
+ stderr.write(ansis.red(` Error updating ${plugin.name}: ${error}\n`));
361
+ return false;
362
+ }
363
+ }
364
+ async updateTheme(theme, wpConfig, branchRef) {
365
+ const { config, context, context: { stdout, stderr }, } = this;
366
+ try {
367
+ const gitCommand = await config.command('git');
368
+ const details = await this.getThemeDetails(theme.name);
369
+ if (!details) {
370
+ return false;
371
+ }
372
+ const fromVersion = details.version;
373
+ const prettyTitle = this.cleanTitle(details.title);
374
+ const location = `${wpConfig.themeFolder}/${theme.name}`;
375
+ const yarnCommand = await config.command('yarn');
376
+ const updateResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'theme', 'update', theme.name], {
377
+ reject: false,
378
+ });
379
+ const updateResultStr = updateResult.exitCode === 0 ? String(updateResult.stdout || '') : null;
380
+ if (!updateResultStr) {
381
+ stderr.write(ansis.red(` Error updating ${theme.name}\n`));
382
+ return false;
383
+ }
384
+ const newDetails = await this.getThemeDetails(theme.name);
385
+ if (!newDetails || newDetails.version === details.version) {
386
+ stderr.write(ansis.red(' Update failed!\n'));
387
+ return false;
388
+ }
389
+ // Ensure branch is created before making our first commit
390
+ await this.ensureBranchCreated(wpConfig, branchRef);
391
+ const commitMessage = this.sanitizeCommitMessage(`Update theme ${prettyTitle} to ${newDetails.version}`);
392
+ await execC(gitCommand, ['add', location], { context });
393
+ await execC(gitCommand, ['commit', '-m', commitMessage], {
394
+ context,
395
+ shell: false,
396
+ env: { SKIP: 'prepare-commit-msg' },
397
+ });
398
+ stdout.write(ansis.green(` Updated theme ${prettyTitle} from ${fromVersion} to ${newDetails.version}\n`));
399
+ return true;
400
+ }
401
+ catch (error) {
402
+ stderr.write(ansis.red(` Error updating ${theme.name}: ${error}\n`));
403
+ return false;
404
+ }
405
+ }
406
+ async getPluginDetails(pluginName) {
407
+ return await this.getDetails(pluginName, 'plugin');
408
+ }
409
+ async getThemeDetails(themeName) {
410
+ return await this.getDetails(themeName, 'theme');
411
+ }
412
+ async getDetails(name, type) {
413
+ const { config } = this;
414
+ const cmdType = type === 'plugin' ? 'plugin' : 'theme';
415
+ try {
416
+ const yarnCommand = await config.command('yarn');
417
+ const detailsResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', cmdType, 'get', '--json', name], {
418
+ reject: false,
419
+ });
420
+ const out = detailsResult.exitCode === 0 ? String(detailsResult.stdout || '') : null;
421
+ if (!out) {
422
+ return null;
423
+ }
424
+ let output = String(out || '');
425
+ if (output.startsWith('$')) {
426
+ // Older Yarn versions include the script name in stdout so we need to trim the first line off
427
+ output = output.substring(1 + output.indexOf('\n'));
428
+ }
429
+ // Look for the start of the JSON to trim off PHP warnings
430
+ const trimmed = output.substring(output.indexOf('{"')).trim();
431
+ return JSON.parse(trimmed);
432
+ }
433
+ catch (_a) {
434
+ return null;
435
+ }
436
+ }
437
+ cleanTitle(name) {
438
+ return name.split(/[|:]/i)[0].trim();
439
+ }
440
+ sanitizeCommitMessage(message) {
441
+ // Replace any problematic characters that might cause issues in commit messages
442
+ return message
443
+ .replace(/[""'']/g, '"') // Normalize quotes
444
+ .replace(/[^\x20-\x7E]/g, '') // Remove non-ASCII characters
445
+ .trim();
446
+ }
447
+ async maybeUpdateCore(wpConfig, branchRef) {
448
+ const { context: { stdout }, } = this;
449
+ const newVersion = await this.hasCoreUpdate();
450
+ if (!newVersion) {
451
+ stdout.write(ansis.dim(' WordPress core is up to date\n'));
452
+ return false;
453
+ }
454
+ stdout.write(ansis.green(` Updating WordPress core to ${newVersion}\n`));
455
+ const shouldStash = await this.hasGitChanges(wpConfig.wpRoot);
456
+ if (shouldStash) {
457
+ stdout.write(ansis.yellow(' Stashing changes temporarily...\n'));
458
+ await this.stashChanges();
459
+ }
460
+ try {
461
+ await this.doCoreUpdate(wpConfig.wpRoot, newVersion, wpConfig, branchRef);
462
+ stdout.write(ansis.green(` Updated WordPress core to ${newVersion}\n`));
463
+ return true;
464
+ }
465
+ finally {
466
+ if (shouldStash) {
467
+ stdout.write(ansis.yellow(' Restoring stashed changes...\n'));
468
+ await this.unstashChanges();
469
+ }
470
+ }
471
+ }
472
+ async hasCoreUpdate() {
473
+ var _a;
474
+ const { config } = this;
475
+ try {
476
+ const yarnCommand = await config.command('yarn');
477
+ const coreResult = await execC(yarnCommand, ['jolt', 'wp', 'cli', 'core', 'check-update', '--json'], {
478
+ reject: false,
479
+ });
480
+ if (!coreResult || coreResult.exitCode !== 0) {
481
+ return false;
482
+ }
483
+ const stdoutStr = String(coreResult.stdout || '');
484
+ if (!stdoutStr.trim() || stdoutStr.trim() === '[]') {
485
+ return false;
486
+ }
487
+ const trimmed = stdoutStr.substring(stdoutStr.indexOf('[{'));
488
+ const parsed = JSON.parse(trimmed);
489
+ return ((_a = parsed[0]) === null || _a === void 0 ? void 0 : _a.version) || false;
490
+ }
491
+ catch (_b) {
492
+ return false;
493
+ }
494
+ }
495
+ async hasGitChanges(path) {
496
+ const { config, context } = this;
497
+ try {
498
+ const gitCommand = await config.command('git');
499
+ const result = await execC(gitCommand, ['status', '--porcelain=v1', '--', path], {
500
+ context,
501
+ reject: false,
502
+ });
503
+ return String(result.stdout || '').trim() !== '';
504
+ }
505
+ catch (_a) {
506
+ return false;
507
+ }
508
+ }
509
+ async stashChanges() {
510
+ const { config, context } = this;
511
+ const date = new Date().toISOString();
512
+ const gitCommand = await config.command('git');
513
+ await execC(gitCommand, ['add', '.'], { context });
514
+ await execC(gitCommand, ['stash', 'save', '--', `Automated stash by Jolt WP Updater at ${date}`], {
515
+ context,
516
+ shell: false,
517
+ });
518
+ }
519
+ async unstashChanges() {
520
+ const { config, context } = this;
521
+ const gitCommand = await config.command('git');
522
+ await execC(gitCommand, ['stash', 'pop'], { context });
523
+ await execC(gitCommand, ['reset', 'HEAD', '--'], { context });
524
+ }
525
+ async doCoreUpdate(path, version, wpConfig, branchRef) {
526
+ const { config, context } = this;
527
+ // Ensure branch is created before making our first commit
528
+ await this.ensureBranchCreated(wpConfig, branchRef);
529
+ const gitCommand = await config.command('git');
530
+ const yarnCommand = await config.command('yarn');
531
+ await execC(yarnCommand, ['jolt', 'wp', 'cli', 'core', 'update'], { context, reject: false });
532
+ await execC(gitCommand, ['add', path], { context });
533
+ const commitMessage = this.sanitizeCommitMessage(`Update WordPress to ${version}`);
534
+ await execC(gitCommand, ['commit', '-m', commitMessage], {
535
+ context,
536
+ shell: false,
537
+ env: { SKIP: 'prepare-commit-msg' },
538
+ });
539
+ }
540
+ }
541
+ WPUpdateCommand.paths = [['wp', 'update']];
542
+ export class WPUpdateMergeCommand extends JoltCommand {
543
+ constructor() {
544
+ super(...arguments);
545
+ this.requiredCommands = ['git'];
546
+ this.rebase = Option.Boolean('--rebase', false, {
547
+ description: 'Use rebase instead of merge',
548
+ });
549
+ this.ffOnly = Option.Boolean('--ff-only', false, {
550
+ description: 'Only allow fast-forward merges',
551
+ });
552
+ this.noFf = Option.Boolean('--no-ff', false, {
553
+ description: 'Create a merge commit even when the merge resolves as a fast-forward',
554
+ });
555
+ }
556
+ async command() {
557
+ const { config, context, context: { stdout, stderr }, logo, } = this;
558
+ const operationTitle = this.rebase ? 'WordPress Update Rebase' : 'WordPress Update Merge';
559
+ stdout.write(`${logo} ${ansis.bold(operationTitle)}\n\n`);
560
+ const wpConfig = await config.loadWordPressConfig();
561
+ if (!wpConfig) {
562
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
563
+ return 1;
564
+ }
565
+ try {
566
+ const gitCommand = await config.command('git');
567
+ // Get current branch
568
+ const currentBranchResult = await execC(gitCommand, ['branch', '--show-current']);
569
+ const currentBranch = String(currentBranchResult.stdout || '').trim();
570
+ if (!currentBranch.startsWith('joltWpUpdate/')) {
571
+ stderr.write(ansis.red('Not currently on a WordPress update branch\n'));
572
+ return 1;
573
+ }
574
+ const targetBranch = (await config.get('branch')) || 'master';
575
+ stdout.write(ansis.cyan(`📋 Switching to ${targetBranch}...\n`));
576
+ await execC(gitCommand, ['switch', targetBranch], { context });
577
+ if (this.rebase) {
578
+ stdout.write(ansis.cyan(`📋 Rebasing ${currentBranch}...\n`));
579
+ await execC(gitCommand, ['rebase', currentBranch], { context });
580
+ }
581
+ else {
582
+ const mergeArgs = ['merge'];
583
+ if (this.ffOnly) {
584
+ mergeArgs.push('--ff-only');
585
+ }
586
+ else if (this.noFf) {
587
+ mergeArgs.push('--no-ff');
588
+ }
589
+ mergeArgs.push(currentBranch);
590
+ const mergeStrategy = this.ffOnly ? ' (fast-forward only)' : this.noFf ? ' (no fast-forward)' : '';
591
+ stdout.write(ansis.cyan(`📋 Merging ${currentBranch}${mergeStrategy}...\n`));
592
+ await execC(gitCommand, mergeArgs, { context });
593
+ }
594
+ const operation = this.rebase ? 'rebased' : 'merged';
595
+ stdout.write(ansis.green(`✅ Successfully ${operation} WordPress updates!\n`));
596
+ return 0;
597
+ }
598
+ catch (error) {
599
+ stderr.write(ansis.red(`Error during merge: ${error}\n`));
600
+ return 1;
601
+ }
602
+ }
603
+ }
604
+ WPUpdateMergeCommand.paths = [['wp', 'update', 'merge']];
605
+ WPUpdateMergeCommand.schema = [
606
+ t.hasMutuallyExclusiveKeys(['rebase', 'ffOnly', 'noFf'], {
607
+ missingIf: 'falsy',
608
+ }),
609
+ ];
610
+ export class WPUpdateCleanCommand extends JoltCommand {
611
+ constructor() {
612
+ super(...arguments);
613
+ this.requiredCommands = ['git'];
614
+ this.dryRun = Option.Boolean('--dry-run', false, { description: 'Show what would be deleted without actually deleting' });
615
+ this.deleteUnmerged = Option.Boolean('--delete-unmerged', false, {
616
+ description: 'Delete unmerged branches (default: only merged branches)',
617
+ });
618
+ }
619
+ async command() {
620
+ const { config, context: { stdout, stderr }, logo, dryRun, deleteUnmerged, } = this;
621
+ const operationTitle = dryRun ? 'WordPress Update Branch Cleanup (Dry Run)' : 'WordPress Update Branch Cleanup';
622
+ stdout.write(`${logo} ${ansis.bold(operationTitle)}\n\n`);
623
+ const wpConfig = await config.loadWordPressConfig();
624
+ if (!wpConfig) {
625
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
626
+ return 1;
627
+ }
628
+ try {
629
+ const gitCommand = await config.command('git');
630
+ // Get current branch to avoid deleting it
631
+ const currentBranchResult = await execC(gitCommand, ['branch', '--show-current'], {
632
+ reject: false,
633
+ });
634
+ const currentBranch = String(currentBranchResult.stdout || '').trim();
635
+ // List all branches with our prefix
636
+ const branchListResult = await execC(gitCommand, ['branch', '--list', 'joltWpUpdate/*'], {
637
+ reject: false,
638
+ });
639
+ if (branchListResult.exitCode !== 0) {
640
+ stderr.write(ansis.red('Failed to list branches\n'));
641
+ return 1;
642
+ }
643
+ const branchOutput = String(branchListResult.stdout || '').trim();
644
+ if (!branchOutput) {
645
+ stdout.write(ansis.green('✅ No WordPress update branches found to clean\n'));
646
+ return 0;
647
+ }
648
+ // Parse branch names (remove leading spaces and asterisks)
649
+ const branches = branchOutput
650
+ .split('\n')
651
+ .map((line) => line.trim().replace(/^\*\s*/, ''))
652
+ .filter((branch) => branch.startsWith('joltWpUpdate/') && branch !== currentBranch);
653
+ if (branches.length === 0) {
654
+ if (currentBranch.startsWith('joltWpUpdate/')) {
655
+ stdout.write(ansis.yellow('⚠️ Currently on a WordPress update branch. Switch to another branch first to clean it.\n'));
656
+ }
657
+ else {
658
+ stdout.write(ansis.green('✅ No WordPress update branches found to clean\n'));
659
+ }
660
+ return 0;
661
+ }
662
+ if (dryRun) {
663
+ // Dry run - show what would be deleted without actually deleting
664
+ const deleteMode = deleteUnmerged ? 'force delete' : 'delete merged';
665
+ for (const branch of branches) {
666
+ stdout.write(ansis.cyan(`🔍 Would ${deleteMode} ${branch}\n`));
667
+ }
668
+ const modeNote = deleteUnmerged
669
+ ? ' (including unmerged branches)'
670
+ : ' (merged branches only, unmerged will be skipped)';
671
+ stdout.write(ansis.cyan(`\n🔍 Dry run complete. Would process ${branches.length} WordPress update branch${branches.length === 1 ? '' : 'es'}${modeNote}\n`));
672
+ }
673
+ else {
674
+ // Actually delete branches
675
+ const deleteFlag = deleteUnmerged ? '-D' : '-d';
676
+ let deletedCount = 0;
677
+ let skippedCount = 0;
678
+ for (const branch of branches) {
679
+ try {
680
+ const deleteResult = await execC(gitCommand, ['branch', deleteFlag, branch], { reject: false });
681
+ if (deleteResult.exitCode === 0) {
682
+ stdout.write(ansis.green(`✅ Deleted ${branch}\n`));
683
+ deletedCount++;
684
+ }
685
+ else {
686
+ if (!deleteUnmerged && String(deleteResult.stderr || '').includes('not fully merged')) {
687
+ stdout.write(ansis.yellow(`⚠️ Skipped ${branch} (unmerged - use --delete-unmerged to force)\n`));
688
+ skippedCount++;
689
+ }
690
+ else {
691
+ stderr.write(ansis.red(`❌ Failed to delete ${branch}\n`));
692
+ }
693
+ }
694
+ }
695
+ catch (error) {
696
+ stderr.write(ansis.red(`❌ Error deleting ${branch}: ${error}\n`));
697
+ }
698
+ }
699
+ if (deletedCount > 0 || skippedCount > 0) {
700
+ const messages = [];
701
+ if (deletedCount > 0) {
702
+ messages.push(`deleted ${deletedCount} branch${deletedCount === 1 ? '' : 'es'}`);
703
+ }
704
+ if (skippedCount > 0) {
705
+ messages.push(`skipped ${skippedCount} unmerged branch${skippedCount === 1 ? '' : 'es'}`);
706
+ }
707
+ stdout.write(ansis.green(`\n🎉 Successfully ${messages.join(', ')}\n`));
708
+ }
709
+ }
710
+ return 0;
711
+ }
712
+ catch (error) {
713
+ stderr.write(ansis.red(`Error during cleanup: ${error}\n`));
714
+ return 1;
715
+ }
716
+ }
717
+ }
718
+ WPUpdateCleanCommand.paths = [['wp', 'update', 'clean']];
719
+ export class WPUpdateModifyCommand extends JoltCommand {
720
+ constructor() {
721
+ super(...arguments);
722
+ this.requiredCommands = ['git'];
723
+ }
724
+ async command() {
725
+ const { config, context, context: { stdout, stderr }, logo, } = this;
726
+ stdout.write(`${logo} ${ansis.bold('WordPress Update Interactive Rebase')}\n\n`);
727
+ const wpConfig = await config.loadWordPressConfig();
728
+ if (!wpConfig) {
729
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
730
+ return 1;
731
+ }
732
+ try {
733
+ const gitCommand = await config.command('git');
734
+ // Get current branch
735
+ const currentBranchResult = await execC(gitCommand, ['branch', '--show-current'], { context });
736
+ const currentBranch = String(currentBranchResult.stdout || '').trim();
737
+ if (!currentBranch.startsWith('joltWpUpdate/')) {
738
+ stderr.write(ansis.red('Not currently on a WordPress update branch\n'));
739
+ return 1;
740
+ }
741
+ // Count commits since branching from main
742
+ const branchName = (await config.get('branch')) || 'master';
743
+ const commitCountResult = await execC(gitCommand, ['rev-list', '--count', `${branchName}..HEAD`], {
744
+ context,
745
+ reject: false,
746
+ });
747
+ if (commitCountResult.exitCode !== 0) {
748
+ stderr.write(ansis.red('Could not determine commit count\n'));
749
+ return 1;
750
+ }
751
+ const commitCount = Number.parseInt(String(commitCountResult.stdout || '').trim(), 10);
752
+ if (commitCount === 0) {
753
+ stdout.write(ansis.yellow('No commits to rebase\n'));
754
+ return 0;
755
+ }
756
+ stdout.write(ansis.cyan(`📋 Starting interactive rebase for ${commitCount} commits...\n`));
757
+ // Run interactive rebase
758
+ const rebaseResult = await execC(gitCommand, ['rebase', '-i', `HEAD~${commitCount}`], {
759
+ context,
760
+ reject: false,
761
+ });
762
+ if (rebaseResult.exitCode === 0) {
763
+ stdout.write(ansis.green('✅ Interactive rebase completed!\n'));
764
+ }
765
+ else {
766
+ stderr.write(ansis.red('Interactive rebase was cancelled or failed\n'));
767
+ return rebaseResult.exitCode;
768
+ }
769
+ return 0;
770
+ }
771
+ catch (error) {
772
+ stderr.write(ansis.red(`Error during rebase: ${error}\n`));
773
+ return 1;
774
+ }
775
+ }
776
+ }
777
+ WPUpdateModifyCommand.paths = [['wp', 'update', 'modify']];
59
778
  //# sourceMappingURL=WP.js.map