@joltdesign/scripts 0.20.0 → 0.22.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.
Files changed (45) hide show
  1. package/README.md +85 -0
  2. package/dist/AWSClient.js +102 -0
  3. package/dist/AWSClient.js.map +1 -0
  4. package/dist/Command/AWS.js +101 -15
  5. package/dist/Command/AWS.js.map +1 -1
  6. package/dist/Command/Build.js +1 -1
  7. package/dist/Command/Build.js.map +1 -1
  8. package/dist/Command/Cmd.js +2 -2
  9. package/dist/Command/Cmd.js.map +1 -1
  10. package/dist/Command/Config.js +161 -3
  11. package/dist/Command/Config.js.map +1 -1
  12. package/dist/Command/DB.js +3 -2
  13. package/dist/Command/DB.js.map +1 -1
  14. package/dist/Command/Docker.js +9 -7
  15. package/dist/Command/Docker.js.map +1 -1
  16. package/dist/Command/JoltCommand.js +1 -1
  17. package/dist/Command/JoltCommand.js.map +1 -1
  18. package/dist/Command/Nexcess.js +58 -3
  19. package/dist/Command/Nexcess.js.map +1 -1
  20. package/dist/Command/Prepare.js +5 -4
  21. package/dist/Command/Prepare.js.map +1 -1
  22. package/dist/Command/SSH.js +3 -3
  23. package/dist/Command/SSH.js.map +1 -1
  24. package/dist/Command/WP.js +801 -4
  25. package/dist/Command/WP.js.map +1 -1
  26. package/dist/Config.js +123 -13
  27. package/dist/Config.js.map +1 -1
  28. package/dist/cli.js +10 -3
  29. package/dist/cli.js.map +1 -1
  30. package/dist/schemas.js +111 -6
  31. package/dist/schemas.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -0
  33. package/dist/types/compose.js +2 -0
  34. package/dist/types/compose.js.map +1 -0
  35. package/dist/types/config.js +2 -0
  36. package/dist/types/config.js.map +1 -0
  37. package/dist/types/index.js +2 -0
  38. package/dist/types/index.js.map +1 -0
  39. package/dist/types/package.js +2 -0
  40. package/dist/types/package.js.map +1 -0
  41. package/dist/utils.js.map +1 -1
  42. package/jolt-config.schema.json +308 -0
  43. package/package.json +17 -10
  44. package/dist/types.js +0 -2
  45. package/dist/types.js.map +0 -1
@@ -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,752 @@ 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
+ this.skipLanguages = Option.Boolean('--skip-languages', false, { description: 'Skip language/translation updates' });
117
+ }
118
+ async command() {
119
+ const { config, context: { stdout, stderr }, skipCore, skipPlugins, skipThemes, skipLanguages, logo, } = this;
120
+ stdout.write(ansis.bold(`${logo} WordPress Updates\n\n`));
121
+ // Load configuration
122
+ const wpConfig = await config.loadWordPressConfig();
123
+ if (!wpConfig) {
124
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
125
+ return 1;
126
+ }
127
+ // Use `jolt wp cli ...` via execC with the configured package runner
128
+ let updatedPluginCount = 0;
129
+ let updatedThemeCount = 0;
130
+ let updatedCore = false;
131
+ // Track detailed update information for summary
132
+ const updateSummary = {
133
+ plugins: [],
134
+ themes: [],
135
+ core: null,
136
+ translations: false,
137
+ };
138
+ // Get current status
139
+ if (!skipPlugins) {
140
+ await this.getItems('plugin');
141
+ }
142
+ if (!skipThemes) {
143
+ await this.getItems('theme');
144
+ }
145
+ // We'll create the update branch only when we need to make our first commit
146
+ const branchRef = { branch: undefined, created: false };
147
+ // Disable git hooks temporarily
148
+ const originalHookPath = await this.disableGitHooks();
149
+ try {
150
+ // Update plugins
151
+ const pluginUpdates = await this.processItemUpdates('plugin', skipPlugins, wpConfig, branchRef);
152
+ updatedPluginCount = pluginUpdates.count;
153
+ updateSummary.plugins = pluginUpdates.details;
154
+ // Update themes
155
+ const themeUpdates = await this.processItemUpdates('theme', skipThemes, wpConfig, branchRef);
156
+ updatedThemeCount = themeUpdates.count;
157
+ updateSummary.themes = themeUpdates.details;
158
+ // Update core
159
+ if (!skipCore) {
160
+ stdout.write(ansis.cyan('📦 Checking WordPress core...\n'));
161
+ const coreResult = await this.maybeUpdateCore(wpConfig, branchRef);
162
+ updatedCore = coreResult.updated;
163
+ if (coreResult.updated && coreResult.details) {
164
+ updateSummary.core = coreResult.details;
165
+ }
166
+ }
167
+ }
168
+ finally {
169
+ await this.rollbackGitHooks(originalHookPath);
170
+ }
171
+ // Summary
172
+ stdout.write(ansis.green('\n✅ Update complete!\n'));
173
+ stdout.write(ansis.cyan(`🔌 Updated ${updatedPluginCount} plugins\n`));
174
+ stdout.write(ansis.cyan(`🎨 Updated ${updatedThemeCount} themes\n`));
175
+ if (updatedCore) {
176
+ stdout.write(ansis.cyan('📦 Updated WordPress core\n'));
177
+ }
178
+ const totalUpdates = updatedPluginCount + updatedThemeCount + (updatedCore ? 1 : 0);
179
+ // Update translations if possible
180
+ let updatedTranslations = false;
181
+ if (!skipLanguages && (totalUpdates > 0 || !branchRef.created)) {
182
+ stdout.write(ansis.cyan('🌐 Updating translations...\n'));
183
+ updatedTranslations = await this.maybeUpdateTranslations(branchRef);
184
+ updateSummary.translations = updatedTranslations;
185
+ }
186
+ // Show detailed update summary
187
+ if (totalUpdates > 0 || updatedTranslations) {
188
+ stdout.write(ansis.bold('\n📋 Update Summary:\n'));
189
+ if (updateSummary.plugins.length > 0) {
190
+ stdout.write(ansis.green('🔌 Plugins updated:\n'));
191
+ for (const plugin of updateSummary.plugins) {
192
+ stdout.write(ansis.cyan(` • ${plugin.title} (${plugin.fromVersion} → ${plugin.toVersion})\n`));
193
+ }
194
+ }
195
+ if (updateSummary.themes.length > 0) {
196
+ stdout.write(ansis.green('🎨 Themes updated:\n'));
197
+ for (const theme of updateSummary.themes) {
198
+ stdout.write(ansis.cyan(` • ${theme.title} (${theme.fromVersion} → ${theme.toVersion})\n`));
199
+ }
200
+ }
201
+ if (updateSummary.core) {
202
+ stdout.write(ansis.green('📦 WordPress core updated:\n'));
203
+ stdout.write(ansis.cyan(` • WordPress (${updateSummary.core.fromVersion} → ${updateSummary.core.toVersion})\n`));
204
+ }
205
+ if (updateSummary.translations) {
206
+ stdout.write(ansis.green('🌐 Translations updated\n'));
207
+ }
208
+ }
209
+ if (totalUpdates > 0 && branchRef.created) {
210
+ stdout.write(ansis.yellow('\nNext steps:\n'));
211
+ stdout.write(`• Review updates: ${ansis.dim(await this.getUpdateCommand('modify'))}\n`);
212
+ // Use root config value directly
213
+ stdout.write(`• Merge to ${await config.get('branch')}: ${ansis.dim(await this.getUpdateCommand('merge'))}\n`);
214
+ }
215
+ else if (totalUpdates === 0 && !updatedTranslations) {
216
+ stdout.write(ansis.green('\n✅ No updates available - staying on current branch\n'));
217
+ }
218
+ return 0;
219
+ }
220
+ // Helper method to get the appropriate command format (short or long form)
221
+ async getUpdateCommand(subCommand) {
222
+ var _a;
223
+ const { config } = this;
224
+ const yarnCommand = await config.command('yarn');
225
+ const packageJson = await config.getPackageJson();
226
+ // Check if there's an 'update' script that acts as a shortcut for 'jolt wp update'
227
+ const updateScript = (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.scripts) === null || _a === void 0 ? void 0 : _a.update;
228
+ if (updateScript && (updateScript === 'jolt wp update' || updateScript.startsWith('jolt wp update '))) {
229
+ return `${yarnCommand} update ${subCommand}`;
230
+ }
231
+ // Fall back to the full command
232
+ return `${yarnCommand} jolt wp update ${subCommand}`;
233
+ }
234
+ // Helper method to execute WP CLI commands
235
+ async executeWpCli(args, options) {
236
+ const { config, context } = this;
237
+ const yarnCommand = await config.command('yarn');
238
+ // For silent operations, don't pass context to suppress output
239
+ const execOptions = {
240
+ reject: false,
241
+ ...options,
242
+ ...(!(options === null || options === void 0 ? void 0 : options.silent) ? { context } : {}),
243
+ };
244
+ const result = await execC(yarnCommand, ['jolt', 'wp', 'cli', ...args], execOptions);
245
+ return {
246
+ exitCode: result.exitCode || 0,
247
+ stdout: result.exitCode === 0 ? String(result.stdout || '') : null,
248
+ };
249
+ }
250
+ // Configuration for different item types
251
+ getItemConfig(type) {
252
+ const configs = {
253
+ plugin: {
254
+ type: 'plugin',
255
+ icon: '🔌',
256
+ listCommand: ['plugin', 'list', '--json'],
257
+ updateCommand: (name) => ['plugin', 'update', name],
258
+ getFolder: (wpConfig) => wpConfig.pluginFolder,
259
+ commitPrefix: 'Update',
260
+ },
261
+ theme: {
262
+ type: 'theme',
263
+ icon: '🎨',
264
+ listCommand: ['theme', 'list', '--json'],
265
+ updateCommand: (name) => ['theme', 'update', name],
266
+ getFolder: (wpConfig) => wpConfig.themeFolder,
267
+ commitPrefix: 'Update theme',
268
+ },
269
+ };
270
+ return configs[type];
271
+ }
272
+ // Generic method to get and parse item lists
273
+ async getItems(type) {
274
+ const { context: { stdout }, } = this;
275
+ const itemConfig = this.getItemConfig(type);
276
+ stdout.write(ansis.cyan(`${itemConfig.icon} Checking ${type}s...\n`));
277
+ const result = await this.executeWpCli(itemConfig.listCommand, { silent: true });
278
+ if (!result.stdout) {
279
+ return [];
280
+ }
281
+ const items = this.parseItemJson(result.stdout);
282
+ stdout.write(`Found ${items.length} ${type}s\n`);
283
+ return items;
284
+ }
285
+ // Generic method to parse item JSON (plugins/themes have same structure)
286
+ parseItemJson(itemJson) {
287
+ // Trim off any preceding warnings, try to look for the start of the actual JSON.
288
+ const trimmed = itemJson.substring(itemJson.indexOf('[{'));
289
+ const allItems = JSON.parse(trimmed);
290
+ // For plugins, filter out dropin and must-use plugins
291
+ return allItems.filter((item) => !['dropin', 'must-use'].includes(item.status));
292
+ }
293
+ // Generic method to handle updating items
294
+ async processItemUpdates(type, skip, wpConfig, branchRef) {
295
+ if (skip) {
296
+ return { count: 0, details: [] };
297
+ }
298
+ const itemConfig = this.getItemConfig(type);
299
+ const items = await this.getItems(type);
300
+ const updateDetails = [];
301
+ let count = 0;
302
+ if (items.length > 0) {
303
+ this.context.stdout.write(ansis.cyan(`${itemConfig.icon} Updating ${type}s...\n`));
304
+ for (const item of items) {
305
+ const result = await this.maybeUpdateItem(item, wpConfig, branchRef, itemConfig);
306
+ if (result.updated) {
307
+ count++;
308
+ if (result.details) {
309
+ updateDetails.push(result.details);
310
+ }
311
+ }
312
+ }
313
+ }
314
+ return { count, details: updateDetails };
315
+ }
316
+ // Generic method to check and maybe update an item (plugin/theme)
317
+ async maybeUpdateItem(item, wpConfig, branchRef, itemConfig) {
318
+ const { context: { stdout }, } = this;
319
+ if (wpConfig.doNotUpdate.includes(item.name)) {
320
+ stdout.write(ansis.dim(` Skipping ${item.name} (configured to skip)\n`));
321
+ return { updated: false };
322
+ }
323
+ stdout.write(` Checking ${item.name}...`);
324
+ if (item.update === 'available') {
325
+ stdout.write(ansis.green(' updating\n'));
326
+ return await this.updateItem(item, wpConfig, branchRef, itemConfig);
327
+ }
328
+ if (item.update === 'none') {
329
+ stdout.write(ansis.dim(' up to date\n'));
330
+ }
331
+ else if (item.update === 'version higher than expected') {
332
+ stdout.write(ansis.yellow(` local version ${item.version} is higher than remote\n`));
333
+ }
334
+ else {
335
+ stdout.write(ansis.red(` unknown status: ${item.update}\n`));
336
+ }
337
+ return { updated: false };
338
+ }
339
+ // Generic method to update an item (plugin/theme)
340
+ async updateItem(item, wpConfig, branchRef, itemConfig) {
341
+ const { config, context: { stdout, stderr }, } = this;
342
+ try {
343
+ const gitCommand = await config.command('git');
344
+ const details = await this.getDetails(item.name, itemConfig.type);
345
+ if (!details) {
346
+ return { updated: false };
347
+ }
348
+ const fromVersion = details.version;
349
+ const prettyTitle = this.cleanTitle(details.title);
350
+ const location = `${itemConfig.getFolder(wpConfig)}/${item.name}`;
351
+ const updateResult = await this.executeWpCli(itemConfig.updateCommand(item.name), { silent: true });
352
+ if (updateResult.exitCode !== 0) {
353
+ stderr.write(ansis.red(` Error updating ${item.name}\n`));
354
+ return { updated: false };
355
+ }
356
+ const newDetails = await this.getDetails(item.name, itemConfig.type);
357
+ if (!newDetails || newDetails.version === details.version) {
358
+ stderr.write(ansis.red(' Update failed!\n'));
359
+ return { updated: false };
360
+ }
361
+ // Ensure branch is created before making our first commit
362
+ await this.ensureBranchCreated(wpConfig, branchRef);
363
+ const commitMessage = this.sanitizeCommitMessage(`${itemConfig.commitPrefix} ${prettyTitle} to ${newDetails.version}`);
364
+ await execC(gitCommand, ['add', location]);
365
+ await execC(gitCommand, ['commit', '-m', commitMessage], {
366
+ shell: false,
367
+ env: { SKIP: 'prepare-commit-msg' },
368
+ });
369
+ stdout.write(ansis.green(` Updated ${prettyTitle} from ${fromVersion} to ${newDetails.version}\n`));
370
+ return {
371
+ updated: true,
372
+ details: {
373
+ name: item.name,
374
+ title: prettyTitle,
375
+ fromVersion,
376
+ toVersion: newDetails.version,
377
+ },
378
+ };
379
+ }
380
+ catch (error) {
381
+ stderr.write(ansis.red(` Error updating ${item.name}: ${error}\n`));
382
+ return { updated: false };
383
+ }
384
+ }
385
+ async createBranch() {
386
+ const { config } = this;
387
+ const isoDate = new Date().toISOString().replace(/[:.]/g, '-').replace(/T/, '_').slice(0, -5);
388
+ const branchName = `joltWpUpdate/${isoDate}`;
389
+ const gitCommand = await config.command('git');
390
+ await execC(gitCommand, ['checkout', '-b', branchName]);
391
+ return branchName;
392
+ }
393
+ async ensureBranchCreated(_wpConfig, branchRef) {
394
+ if (!branchRef.created) {
395
+ branchRef.branch = await this.createBranch();
396
+ branchRef.created = true;
397
+ const { context: { stdout }, } = this;
398
+ stdout.write(ansis.green(`📋 Created update branch ${branchRef.branch}\n`));
399
+ }
400
+ return branchRef.branch;
401
+ }
402
+ async disableGitHooks() {
403
+ const { config, context } = this;
404
+ const gitCommand = await config.command('git');
405
+ try {
406
+ const result = await execC(gitCommand, ['config', '--get', 'core.hooksPath'], { context, reject: false });
407
+ const originalHookPath = String(result.stdout || '').trim();
408
+ await execC(gitCommand, ['config', 'core.hooksPath', '/dev/null'], { context });
409
+ return originalHookPath;
410
+ }
411
+ catch (_a) {
412
+ await execC(gitCommand, ['config', 'core.hooksPath', '/dev/null'], { context });
413
+ return '';
414
+ }
415
+ }
416
+ async rollbackGitHooks(originalHookPath) {
417
+ const { config, context } = this;
418
+ const gitCommand = await config.command('git');
419
+ if (originalHookPath) {
420
+ await execC(gitCommand, ['config', 'core.hooksPath', originalHookPath], { context });
421
+ }
422
+ else {
423
+ await execC(gitCommand, ['config', '--unset', 'core.hooksPath'], { context, reject: false });
424
+ }
425
+ }
426
+ async getDetails(name, type) {
427
+ try {
428
+ const result = await this.executeWpCli([type, 'get', '--json', name], { silent: true });
429
+ if (!result.stdout) {
430
+ return null;
431
+ }
432
+ let output = result.stdout;
433
+ if (output.startsWith('$')) {
434
+ // Older Yarn versions include the script name in stdout so we need to trim the first line off
435
+ output = output.substring(1 + output.indexOf('\n'));
436
+ }
437
+ // Look for the start of the JSON to trim off PHP warnings
438
+ const trimmed = output.substring(output.indexOf('{"')).trim();
439
+ return JSON.parse(trimmed);
440
+ }
441
+ catch (_a) {
442
+ return null;
443
+ }
444
+ }
445
+ cleanTitle(name) {
446
+ return name.split(/[|:]/i)[0].trim();
447
+ }
448
+ sanitizeCommitMessage(message) {
449
+ // Replace any problematic characters that might cause issues in commit messages
450
+ return message
451
+ .replace(/[""'']/g, '"') // Normalize quotes
452
+ .replace(/[^\x20-\x7E]/g, '') // Remove non-ASCII characters
453
+ .trim();
454
+ }
455
+ async maybeUpdateCore(wpConfig, branchRef) {
456
+ const { context: { stdout }, } = this;
457
+ const newVersion = await this.hasCoreUpdate();
458
+ if (!newVersion) {
459
+ stdout.write(ansis.dim(' WordPress core is up to date\n'));
460
+ return { updated: false };
461
+ }
462
+ // Get current version before updating
463
+ const currentVersion = await this.getCurrentCoreVersion();
464
+ stdout.write(ansis.green(` Updating WordPress core to ${newVersion}\n`));
465
+ const shouldStash = await this.hasGitChanges(wpConfig.wpRoot);
466
+ if (shouldStash) {
467
+ stdout.write(ansis.yellow(' Stashing changes temporarily...\n'));
468
+ await this.stashChanges();
469
+ }
470
+ try {
471
+ await this.doCoreUpdate(wpConfig.wpRoot, newVersion, wpConfig, branchRef);
472
+ stdout.write(ansis.green(` Updated WordPress core to ${newVersion}\n`));
473
+ return {
474
+ updated: true,
475
+ details: {
476
+ fromVersion: currentVersion || 'unknown',
477
+ toVersion: newVersion,
478
+ },
479
+ };
480
+ }
481
+ finally {
482
+ if (shouldStash) {
483
+ stdout.write(ansis.yellow(' Restoring stashed changes...\n'));
484
+ await this.unstashChanges();
485
+ }
486
+ }
487
+ }
488
+ async getCurrentCoreVersion() {
489
+ try {
490
+ const result = await this.executeWpCli(['core', 'version'], { silent: true });
491
+ return result.stdout ? result.stdout.trim() : null;
492
+ }
493
+ catch (_a) {
494
+ return null;
495
+ }
496
+ }
497
+ async hasCoreUpdate() {
498
+ var _a;
499
+ try {
500
+ const result = await this.executeWpCli(['core', 'check-update', '--json'], { silent: true });
501
+ if (!result.stdout || !result.stdout.trim() || result.stdout.trim() === '[]') {
502
+ return false;
503
+ }
504
+ const trimmed = result.stdout.substring(result.stdout.indexOf('[{'));
505
+ const parsed = JSON.parse(trimmed);
506
+ return ((_a = parsed[0]) === null || _a === void 0 ? void 0 : _a.version) || false;
507
+ }
508
+ catch (_b) {
509
+ return false;
510
+ }
511
+ }
512
+ async hasGitChanges(path) {
513
+ const { config } = this;
514
+ try {
515
+ const gitCommand = await config.command('git');
516
+ const result = await execC(gitCommand, ['status', '--porcelain=v1', '--', path], {
517
+ reject: false,
518
+ });
519
+ return String(result.stdout || '').trim() !== '';
520
+ }
521
+ catch (_a) {
522
+ return false;
523
+ }
524
+ }
525
+ async stashChanges() {
526
+ const { config } = this;
527
+ const date = new Date().toISOString();
528
+ const gitCommand = await config.command('git');
529
+ await execC(gitCommand, ['add', '.']);
530
+ await execC(gitCommand, ['stash', 'save', '--', `Automated stash by Jolt WP Updater at ${date}`], {
531
+ shell: false,
532
+ });
533
+ }
534
+ async unstashChanges() {
535
+ const { config } = this;
536
+ const gitCommand = await config.command('git');
537
+ await execC(gitCommand, ['stash', 'pop']);
538
+ await execC(gitCommand, ['reset', 'HEAD', '--']);
539
+ }
540
+ async doCoreUpdate(path, version, wpConfig, branchRef) {
541
+ const { config } = this;
542
+ // Ensure branch is created before making our first commit
543
+ await this.ensureBranchCreated(wpConfig, branchRef);
544
+ const gitCommand = await config.command('git');
545
+ await this.executeWpCli(['core', 'update'], { silent: true });
546
+ await execC(gitCommand, ['add', path]);
547
+ const commitMessage = this.sanitizeCommitMessage(`Update WordPress to ${version}`);
548
+ await execC(gitCommand, ['commit', '-m', commitMessage], {
549
+ shell: false,
550
+ env: { SKIP: 'prepare-commit-msg' },
551
+ });
552
+ }
553
+ async maybeUpdateTranslations(branchRef) {
554
+ const { config, context: { stdout, stderr }, } = this;
555
+ try {
556
+ let hasUpdates = false;
557
+ // Helper to check and update translations for a specific type
558
+ const updateTranslationType = async (type, command) => {
559
+ stdout.write(` Checking ${type} translations...`);
560
+ const result = await this.executeWpCli(command, { silent: true });
561
+ if (result.exitCode === 0 && result.stdout) {
562
+ if (result.stdout.includes('Updated') || result.stdout.includes('updated')) {
563
+ hasUpdates = true;
564
+ stdout.write(ansis.green(' updated\n'));
565
+ }
566
+ else {
567
+ stdout.write(ansis.dim(' up to date\n'));
568
+ }
569
+ }
570
+ else {
571
+ stdout.write(ansis.dim(' skipped (not available)\n'));
572
+ }
573
+ };
574
+ // Update different translation types
575
+ await updateTranslationType('core', ['language', 'core', 'update']);
576
+ await updateTranslationType('plugin', ['language', 'plugin', 'update', '--all']);
577
+ await updateTranslationType('theme', ['language', 'theme', 'update', '--all']);
578
+ // If we have translation updates, commit them
579
+ if (hasUpdates) {
580
+ const wpConfig = await config.loadWordPressConfig();
581
+ if (!wpConfig) {
582
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
583
+ return false;
584
+ }
585
+ // Create or ensure we're on the update branch
586
+ await this.ensureBranchCreated(wpConfig, branchRef);
587
+ const gitCommand = await config.command('git');
588
+ // Add all language files that might have been updated
589
+ await execC(gitCommand, ['add', wpConfig.wpRoot]);
590
+ // Check if there are any changes to commit
591
+ const statusResult = await execC(gitCommand, ['diff', '--cached', '--exit-code'], {
592
+ reject: false,
593
+ });
594
+ if (statusResult.exitCode !== 0) {
595
+ // There are changes to commit
596
+ await execC(gitCommand, ['commit', '-m', 'Update translations'], {
597
+ shell: false,
598
+ env: { SKIP: 'prepare-commit-msg' },
599
+ });
600
+ stdout.write(ansis.green(' Committed translation updates\n'));
601
+ return true;
602
+ }
603
+ stdout.write(ansis.dim(' No translation files changed\n'));
604
+ }
605
+ return false;
606
+ }
607
+ catch (error) {
608
+ stderr.write(ansis.red(`Error updating translations: ${error}\n`));
609
+ return false;
610
+ }
611
+ }
612
+ }
613
+ WPUpdateCommand.paths = [['wp', 'update']];
614
+ export class WPUpdateMergeCommand extends JoltCommand {
615
+ constructor() {
616
+ super(...arguments);
617
+ this.requiredCommands = ['git'];
618
+ this.rebase = Option.Boolean('--rebase', false, {
619
+ description: 'Use rebase instead of merge',
620
+ });
621
+ this.ffOnly = Option.Boolean('--ff-only', false, {
622
+ description: 'Only allow fast-forward merges',
623
+ });
624
+ this.noFf = Option.Boolean('--no-ff', false, {
625
+ description: 'Create a merge commit even when the merge resolves as a fast-forward',
626
+ });
627
+ }
628
+ async command() {
629
+ const { config, context, context: { stdout, stderr }, logo, } = this;
630
+ const operationTitle = this.rebase ? 'WordPress Update Rebase' : 'WordPress Update Merge';
631
+ stdout.write(`${logo} ${ansis.bold(operationTitle)}\n\n`);
632
+ const wpConfig = await config.loadWordPressConfig();
633
+ if (!wpConfig) {
634
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
635
+ return 1;
636
+ }
637
+ try {
638
+ const gitCommand = await config.command('git');
639
+ // Get current branch
640
+ const currentBranchResult = await execC(gitCommand, ['branch', '--show-current']);
641
+ const currentBranch = String(currentBranchResult.stdout || '').trim();
642
+ if (!currentBranch.startsWith('joltWpUpdate/')) {
643
+ stderr.write(ansis.red('Not currently on a WordPress update branch\n'));
644
+ return 1;
645
+ }
646
+ const targetBranch = (await config.get('branch')) || 'master';
647
+ stdout.write(ansis.cyan(`📋 Switching to ${targetBranch}...\n`));
648
+ await execC(gitCommand, ['switch', targetBranch], { context });
649
+ if (this.rebase) {
650
+ stdout.write(ansis.cyan(`📋 Rebasing ${currentBranch}...\n`));
651
+ await execC(gitCommand, ['rebase', currentBranch], { context });
652
+ }
653
+ else {
654
+ const mergeArgs = ['merge'];
655
+ if (this.ffOnly) {
656
+ mergeArgs.push('--ff-only');
657
+ }
658
+ else if (this.noFf) {
659
+ mergeArgs.push('--no-ff');
660
+ }
661
+ mergeArgs.push(currentBranch);
662
+ const mergeStrategy = this.ffOnly ? ' (fast-forward only)' : this.noFf ? ' (no fast-forward)' : '';
663
+ stdout.write(ansis.cyan(`📋 Merging ${currentBranch}${mergeStrategy}...\n`));
664
+ await execC(gitCommand, mergeArgs, { context });
665
+ }
666
+ const operation = this.rebase ? 'rebased' : 'merged';
667
+ stdout.write(ansis.green(`✅ Successfully ${operation} WordPress updates!\n`));
668
+ return 0;
669
+ }
670
+ catch (error) {
671
+ stderr.write(ansis.red(`Error during merge: ${error}\n`));
672
+ return 1;
673
+ }
674
+ }
675
+ }
676
+ WPUpdateMergeCommand.paths = [['wp', 'update', 'merge']];
677
+ WPUpdateMergeCommand.schema = [
678
+ t.hasMutuallyExclusiveKeys(['rebase', 'ffOnly', 'noFf'], {
679
+ missingIf: 'falsy',
680
+ }),
681
+ ];
682
+ export class WPUpdateCleanCommand extends JoltCommand {
683
+ constructor() {
684
+ super(...arguments);
685
+ this.requiredCommands = ['git'];
686
+ this.dryRun = Option.Boolean('--dry-run', false, { description: 'Show what would be deleted without actually deleting' });
687
+ this.deleteUnmerged = Option.Boolean('--delete-unmerged', false, {
688
+ description: 'Delete unmerged branches (default: only merged branches)',
689
+ });
690
+ }
691
+ async command() {
692
+ const { config, context: { stdout, stderr }, logo, dryRun, deleteUnmerged, } = this;
693
+ const operationTitle = dryRun ? 'WordPress Update Branch Cleanup (Dry Run)' : 'WordPress Update Branch Cleanup';
694
+ stdout.write(`${logo} ${ansis.bold(operationTitle)}\n\n`);
695
+ const wpConfig = await config.loadWordPressConfig();
696
+ if (!wpConfig) {
697
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
698
+ return 1;
699
+ }
700
+ try {
701
+ const gitCommand = await config.command('git');
702
+ // Get current branch to avoid deleting it
703
+ const currentBranchResult = await execC(gitCommand, ['branch', '--show-current'], {
704
+ reject: false,
705
+ });
706
+ const currentBranch = String(currentBranchResult.stdout || '').trim();
707
+ // List all branches with our prefix
708
+ const branchListResult = await execC(gitCommand, ['branch', '--list', 'joltWpUpdate/*'], {
709
+ reject: false,
710
+ });
711
+ if (branchListResult.exitCode !== 0) {
712
+ stderr.write(ansis.red('Failed to list branches\n'));
713
+ return 1;
714
+ }
715
+ const branchOutput = String(branchListResult.stdout || '').trim();
716
+ if (!branchOutput) {
717
+ stdout.write(ansis.green('✅ No WordPress update branches found to clean\n'));
718
+ return 0;
719
+ }
720
+ // Parse branch names (remove leading spaces and asterisks)
721
+ const branches = branchOutput
722
+ .split('\n')
723
+ .map((line) => line.trim().replace(/^\*\s*/, ''))
724
+ .filter((branch) => branch.startsWith('joltWpUpdate/') && branch !== currentBranch);
725
+ if (branches.length === 0) {
726
+ if (currentBranch.startsWith('joltWpUpdate/')) {
727
+ stdout.write(ansis.yellow('⚠️ Currently on a WordPress update branch. Switch to another branch first to clean it.\n'));
728
+ }
729
+ else {
730
+ stdout.write(ansis.green('✅ No WordPress update branches found to clean\n'));
731
+ }
732
+ return 0;
733
+ }
734
+ if (dryRun) {
735
+ // Dry run - show what would be deleted without actually deleting
736
+ const deleteMode = deleteUnmerged ? 'force delete' : 'delete merged';
737
+ for (const branch of branches) {
738
+ stdout.write(ansis.cyan(`🔍 Would ${deleteMode} ${branch}\n`));
739
+ }
740
+ const modeNote = deleteUnmerged
741
+ ? ' (including unmerged branches)'
742
+ : ' (merged branches only, unmerged will be skipped)';
743
+ stdout.write(ansis.cyan(`\n🔍 Dry run complete. Would process ${branches.length} WordPress update branch${branches.length === 1 ? '' : 'es'}${modeNote}\n`));
744
+ }
745
+ else {
746
+ // Actually delete branches
747
+ const deleteFlag = deleteUnmerged ? '-D' : '-d';
748
+ let deletedCount = 0;
749
+ let skippedCount = 0;
750
+ for (const branch of branches) {
751
+ try {
752
+ const deleteResult = await execC(gitCommand, ['branch', deleteFlag, branch], { reject: false });
753
+ if (deleteResult.exitCode === 0) {
754
+ stdout.write(ansis.green(`✅ Deleted ${branch}\n`));
755
+ deletedCount++;
756
+ }
757
+ else {
758
+ if (!deleteUnmerged && String(deleteResult.stderr || '').includes('not fully merged')) {
759
+ stdout.write(ansis.yellow(`⚠️ Skipped ${branch} (unmerged - use --delete-unmerged to force)\n`));
760
+ skippedCount++;
761
+ }
762
+ else {
763
+ stderr.write(ansis.red(`❌ Failed to delete ${branch}\n`));
764
+ }
765
+ }
766
+ }
767
+ catch (error) {
768
+ stderr.write(ansis.red(`❌ Error deleting ${branch}: ${error}\n`));
769
+ }
770
+ }
771
+ if (deletedCount > 0 || skippedCount > 0) {
772
+ const messages = [];
773
+ if (deletedCount > 0) {
774
+ messages.push(`deleted ${deletedCount} branch${deletedCount === 1 ? '' : 'es'}`);
775
+ }
776
+ if (skippedCount > 0) {
777
+ messages.push(`skipped ${skippedCount} unmerged branch${skippedCount === 1 ? '' : 'es'}`);
778
+ }
779
+ stdout.write(ansis.green(`\n🎉 Successfully ${messages.join(', ')}\n`));
780
+ }
781
+ }
782
+ return 0;
783
+ }
784
+ catch (error) {
785
+ stderr.write(ansis.red(`Error during cleanup: ${error}\n`));
786
+ return 1;
787
+ }
788
+ }
789
+ }
790
+ WPUpdateCleanCommand.paths = [['wp', 'update', 'clean']];
791
+ export class WPUpdateModifyCommand extends JoltCommand {
792
+ constructor() {
793
+ super(...arguments);
794
+ this.requiredCommands = ['git'];
795
+ this.autostash = Option.Boolean('--autostash', false, {
796
+ description: 'Automatically stash and unstash changes before and after the rebase',
797
+ });
798
+ }
799
+ async command() {
800
+ const { config, context, context: { stdout, stderr }, logo, } = this;
801
+ stdout.write(`${logo} ${ansis.bold('WordPress Update Interactive Rebase')}\n\n`);
802
+ const wpConfig = await config.loadWordPressConfig();
803
+ if (!wpConfig) {
804
+ stderr.write(ansis.red('Failed to load WordPress configuration\n'));
805
+ return 1;
806
+ }
807
+ try {
808
+ const gitCommand = await config.command('git');
809
+ // Get current branch
810
+ const currentBranchResult = await execC(gitCommand, ['branch', '--show-current']);
811
+ const currentBranch = String(currentBranchResult.stdout || '').trim();
812
+ if (!currentBranch.startsWith('joltWpUpdate/')) {
813
+ stderr.write(ansis.red('Not currently on a WordPress update branch\n'));
814
+ return 1;
815
+ }
816
+ // Count commits since branching from main
817
+ const branchName = (await config.get('branch')) || 'master';
818
+ const commitCountResult = await execC(gitCommand, ['rev-list', '--count', `${branchName}..HEAD`], {
819
+ reject: false,
820
+ });
821
+ if (commitCountResult.exitCode !== 0) {
822
+ stderr.write(ansis.red('Could not determine commit count\n'));
823
+ return 1;
824
+ }
825
+ const commitCount = Number.parseInt(String(commitCountResult.stdout || '').trim(), 10);
826
+ if (commitCount === 0) {
827
+ stdout.write(ansis.yellow('No commits to rebase\n'));
828
+ return 0;
829
+ }
830
+ stdout.write(ansis.cyan(`📋 Starting interactive rebase for ${commitCount} commits...\n`));
831
+ // Run interactive rebase
832
+ const rebaseArgs = ['rebase', '-i', `HEAD~${commitCount}`];
833
+ if (this.autostash) {
834
+ rebaseArgs.push('--autostash');
835
+ }
836
+ const rebaseResult = await execC(gitCommand, rebaseArgs, {
837
+ context,
838
+ reject: false,
839
+ });
840
+ if (rebaseResult.exitCode === 0) {
841
+ stdout.write(ansis.green('✅ Interactive rebase completed!\n'));
842
+ }
843
+ else {
844
+ stderr.write(ansis.red('Interactive rebase was cancelled or failed\n'));
845
+ return rebaseResult.exitCode;
846
+ }
847
+ return 0;
848
+ }
849
+ catch (error) {
850
+ stderr.write(ansis.red(`Error during rebase: ${error}\n`));
851
+ return 1;
852
+ }
853
+ }
854
+ }
855
+ WPUpdateModifyCommand.paths = [['wp', 'update', 'modify']];
59
856
  //# sourceMappingURL=WP.js.map