|
| 1 | +import type { OptionValues } from 'commander' |
| 2 | +import inquirer from 'inquirer' |
| 3 | + |
| 4 | +import { chalk, exit, log, logAndThrowError, logJson } from '../../utils/command-helpers.js' |
| 5 | +import { startSpinner, stopSpinner } from '../../lib/spinner.js' |
| 6 | +import type BaseCommand from '../base-command.js' |
| 7 | +import { createAgentsApi, type AgentsApi } from './api.js' |
| 8 | +import type { AgentRunner } from './types.js' |
| 9 | + |
| 10 | +interface AgentSyncOptions extends OptionValues { |
| 11 | + json?: boolean |
| 12 | + yes?: boolean |
| 13 | +} |
| 14 | + |
| 15 | +type SyncStrategy = 'sync_git_origin' | 'merge_target' | 'rebase' |
| 16 | + |
| 17 | +const pickStrategy = (runner: AgentRunner): SyncStrategy | null => { |
| 18 | + if (runner.needs_git_sync) return 'sync_git_origin' |
| 19 | + if (runner.merge_target_available) return 'merge_target' |
| 20 | + if (runner.rebase_available) return 'rebase' |
| 21 | + return null |
| 22 | +} |
| 23 | + |
| 24 | +const describeStrategy = (strategy: SyncStrategy, runner: AgentRunner): string => { |
| 25 | + const target = runner.branch ? ` (target: ${runner.branch})` : '' |
| 26 | + switch (strategy) { |
| 27 | + case 'sync_git_origin': |
| 28 | + return `sync with the remote git origin${target}` |
| 29 | + case 'merge_target': |
| 30 | + return `merge the latest target branch into this agent run${target}` |
| 31 | + case 'rebase': |
| 32 | + return 'reapply changes on top of the latest production deploy' |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +const runStrategy = (api: AgentsApi, strategy: SyncStrategy, id: string): Promise<AgentRunner> => { |
| 37 | + switch (strategy) { |
| 38 | + case 'sync_git_origin': |
| 39 | + return api.syncGitOriginAgentRunner(id) |
| 40 | + case 'merge_target': |
| 41 | + return api.mergeTargetAgentRunner(id) |
| 42 | + case 'rebase': |
| 43 | + return api.rebaseAgentRunner(id) |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +export const agentsSync = async (id: string, options: AgentSyncOptions, command: BaseCommand) => { |
| 48 | + if (!id) return logAndThrowError('Agent run ID is required') |
| 49 | + await command.authenticate() |
| 50 | + const api = createAgentsApi(command.netlify) |
| 51 | + |
| 52 | + const lookupSpinner = startSpinner({ text: 'Checking agent run state...' }) |
| 53 | + let runner: AgentRunner |
| 54 | + try { |
| 55 | + runner = await api.getAgentRunner(id) |
| 56 | + stopSpinner({ spinner: lookupSpinner }) |
| 57 | + } catch (error_) { |
| 58 | + stopSpinner({ spinner: lookupSpinner, error: true }) |
| 59 | + const error = error_ as Error & { status?: number } |
| 60 | + if (error.status === 404) return logAndThrowError(`Agent run not found: ${id}`) |
| 61 | + return logAndThrowError(`Failed to fetch agent run: ${error.message}`) |
| 62 | + } |
| 63 | + |
| 64 | + const strategy = pickStrategy(runner) |
| 65 | + if (!strategy) { |
| 66 | + log(chalk.yellow('Nothing to sync — this agent run is already up to date.')) |
| 67 | + return runner |
| 68 | + } |
| 69 | + |
| 70 | + if (!options.yes && !options.json) { |
| 71 | + if (!process.stdin.isTTY) { |
| 72 | + return logAndThrowError('Refusing to sync without --yes when stdin is not a TTY') |
| 73 | + } |
| 74 | + const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([ |
| 75 | + { |
| 76 | + type: 'confirm', |
| 77 | + name: 'confirmed', |
| 78 | + message: `Sync agent run ${id}? This will ${describeStrategy(strategy, runner)}.`, |
| 79 | + default: false, |
| 80 | + }, |
| 81 | + ]) |
| 82 | + if (!confirmed) return exit() |
| 83 | + } |
| 84 | + |
| 85 | + const spinner = startSpinner({ text: 'Syncing agent run...' }) |
| 86 | + try { |
| 87 | + const updated = await runStrategy(api, strategy, id) |
| 88 | + stopSpinner({ spinner }) |
| 89 | + |
| 90 | + if (options.json) { |
| 91 | + logJson(updated) |
| 92 | + return updated |
| 93 | + } |
| 94 | + |
| 95 | + log(`${chalk.green('✓')} Sync started: ${describeStrategy(strategy, runner)}.`) |
| 96 | + log(` Run ID: ${chalk.cyan(updated.id)}`) |
| 97 | + log() |
| 98 | + log(`Watch progress: ${chalk.cyan(`netlify agents:show ${updated.id} --watch`)}`) |
| 99 | + return updated |
| 100 | + } catch (error_) { |
| 101 | + stopSpinner({ spinner, error: true }) |
| 102 | + const error = error_ as Error |
| 103 | + return logAndThrowError(`Failed to sync: ${error.message}`) |
| 104 | + } |
| 105 | +} |
0 commit comments