OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / lib / cli / commands / deploy.js

286 lines · javascript ref:
/**
 * pgh deploy commands
 *
 * Subcommands: setup, status, trigger, log, disable, registry
 */

import { table, jsonOut, log, error } from '../output.js'

export async function deployCommand (client, sub, args, flags) {
  switch (sub) {
    case 'setup':
      return deploySetup(client, args[0], flags)
    case 'status':
      return deployStatus(client, args[0], flags)
    case 'trigger':
      return deployTrigger(client, args[0], flags)
    case 'log':
      return deployLog(client, args[0], flags)
    case 'disable':
      return deployDisable(client, args[0], flags)
    case 'registry':
      return deployRegistry(client, flags)
    default:
      deployHelp()
  }
}

async function deploySetup (client, repoName, flags) {
  if (!repoName) error('Usage: pgh deploy setup <repo> --channel <name>')

  const channel = flags.channel || repoName
  const mode = flags.mode || 'git'
  const autoRelease = flags['auto-release'] !== 'false' && flags.autoRelease !== 'false'
  const autoSeed = flags['auto-seed'] !== 'false' && flags.autoSeed !== 'false'
  const autoDeploy = flags['auto-deploy'] !== 'false' && flags.autoDeploy !== 'false'

  const data = await client.post(`/api/deploy/config/${encodeURIComponent(repoName)}`, {
    channel,
    mode,
    autoRelease,
    autoSeed,
    autoDeploy,
    enabled: true
  })

  if (flags.json) {
    jsonOut(data)
    return
  }

  if (data.error) {
    error(data.error)
    return
  }

  log(`Deploy configured for ${repoName}`)
  log(`  Channel:      ${channel}`)
  log(`  Mode:         ${mode}`)
  log(`  Auto-release: ${autoRelease}`)
  log(`  Auto-seed:    ${autoSeed}`)
  log(`  Auto-deploy:  ${autoDeploy}`)
}

async function deployStatus (client, repoName, flags) {
  if (!repoName) {
    // List all repos with deploy config
    const repos = await client.get('/api/repos')
    const deployRepos = repos.filter(r => r.deploy && r.deploy.enabled)

    if (flags.json) {
      jsonOut(deployRepos.map(r => ({ name: r.name, ...r.deploy })))
      return
    }

    if (deployRepos.length === 0) {
      log('No repos configured for deploy.')
      log('Run: pgh deploy setup <repo> --channel <name>')
      return
    }

    table(deployRepos.map(r => ({
      name: r.name,
      channel: r.deploy.channel,
      mode: r.deploy.mode || 'git',
      version: r.deploy.lastCommitHash
        ? r.deploy.lastCommitHash.slice(0, 8)
        : (r.deploy.lastVersion || '-'),
      lastDeploy: r.deploy.lastDeploy ? new Date(r.deploy.lastDeploy).toLocaleString() : 'Never'
    })), [
      { key: 'name', label: 'REPO', width: 25 },
      { key: 'channel', label: 'CHANNEL', width: 20 },
      { key: 'mode', label: 'MODE', width: 8 },
      { key: 'version', label: 'VERSION' },
      { key: 'lastDeploy', label: 'LAST DEPLOY', width: 25 }
    ])
    return
  }

  const data = await client.get(`/api/deploy/status/${encodeURIComponent(repoName)}`)

  if (flags.json) {
    jsonOut(data)
    return
  }

  if (!data.configured) {
    log(`Deploy not configured for ${repoName}`)
    log(`Run: pgh deploy setup ${repoName} --channel <name>`)
    return
  }

  log(`Deploy Status: ${repoName}`)
  log(`  Channel:      ${data.channel}`)
  log(`  Mode:         ${data.mode || 'git'}`)

  if (data.mode === 'git' || !data.mode || data.mode !== 'legacy') {
    log(`  Commit:       ${data.lastCommitHash ? data.lastCommitHash.slice(0, 12) : '-'}`)
    log(`  Branch:       ${data.lastBranch || 'release'}`)
    log(`  Last Deploy:  ${data.lastDeploy ? new Date(data.lastDeploy).toLocaleString() : 'Never'}`)

    if (data.registryApp) {
      log(`  Registry:`)
      log(`    Deployed by: ${data.registryApp.deployedBy ? data.registryApp.deployedBy.slice(0, 12) + '...' : '-'}`)
      log(`    Deployed at: ${data.registryApp.deployedAt ? new Date(data.registryApp.deployedAt).toLocaleString() : '-'}`)
    }
  } else {
    log(`  Version:      ${data.lastVersion || '-'}`)
    log(`  Key:          ${data.lastKey ? data.lastKey.slice(0, 16) + '...' : '-'}`)
    log(`  Seeding:      ${data.seeding ? 'Yes' : 'No'}`)
    log(`  Last Deploy:  ${data.lastDeploy ? new Date(data.lastDeploy).toLocaleString() : 'Never'}`)
    if (data.info) {
      log(`  Release:      ${data.info.release || '-'}`)
      log(`  Length:       ${data.info.length || '-'}`)
    }
  }
}

async function deployTrigger (client, repoName, flags) {
  if (!repoName) error('Usage: pgh deploy trigger <repo>')

  const legacy = flags.legacy === true || flags.legacy === 'true'

  log(`Deploying ${repoName}${legacy ? ' (legacy mode)' : ''}...`)

  const data = await client.post(`/api/deploy/trigger/${encodeURIComponent(repoName)}`, {
    legacy
  })

  if (flags.json) {
    jsonOut(data)
    return
  }

  if (data.error) {
    error(data.error)
    return
  }

  log(`\u2713 Deployed ${repoName}`)
  if (data.commitHash) {
    log(`  Commit:   ${data.commitHash.slice(0, 12)}`)
    log(`  Branch:   ${data.branch || 'release'}`)
  } else {
    log(`  Version:  ${data.version || '-'}`)
  }
  log(`  Channel:  ${data.channel}`)
  log(`  Mode:     ${data.mode || 'git'}`)
  log(`  Duration: ${data.duration}ms`)
  if (data.key) log(`  Key:      ${data.key.slice(0, 16)}...`)
}

async function deployLog (client, repoName, flags) {
  if (!repoName) error('Usage: pgh deploy log <repo>')

  const entries = await client.get(`/api/deploy/log/${encodeURIComponent(repoName)}`)

  if (flags.json) {
    jsonOut(entries)
    return
  }

  if (entries.length === 0) {
    log(`No deploy history for ${repoName}`)
    return
  }

  table(entries.map(e => ({
    time: new Date(e.timestamp).toLocaleString(),
    version: e.commitHash
      ? e.commitHash.slice(0, 8)
      : (e.version || '-'),
    channel: e.channel,
    mode: e.mode || (e.commitHash ? 'git' : 'legacy'),
    duration: (e.duration || 0) + 'ms'
  })), [
    { key: 'time', label: 'TIME', width: 25 },
    { key: 'version', label: 'VERSION' },
    { key: 'channel', label: 'CHANNEL', width: 20 },
    { key: 'mode', label: 'MODE', width: 8 },
    { key: 'duration', label: 'DURATION' }
  ])
}

async function deployDisable (client, repoName, flags) {
  if (!repoName) error('Usage: pgh deploy disable <repo>')

  const data = await client.post(`/api/deploy/config/${encodeURIComponent(repoName)}`, {
    channel: '_disabled',
    enabled: false
  })

  if (flags.json) {
    jsonOut(data)
    return
  }

  if (data.error) {
    error(data.error)
    return
  }

  log(`Deploy disabled for ${repoName}`)
}

async function deployRegistry (client, flags) {
  const data = await client.get('/api/deploy/registry')

  if (flags.json) {
    jsonOut(data)
    return
  }

  if (!data.available) {
    log('Deploy registry not available')
    if (data.reason) log(`  Reason: ${data.reason}`)
    return
  }

  log('Deploy Registry')
  log(`  Bootstrap Key: ${data.bootstrapKey ? data.bootstrapKey.slice(0, 16) + '...' : '-'}`)
  log(`  Writable:      ${data.writable ? 'Yes' : 'No'}`)
  log(`  Servers:       ${data.servers ? data.servers.length : 0}`)
  log(`  Apps:          ${data.appCount || 0}`)

  if (data.servers && data.servers.length > 0) {
    log('')
    log('Registered Servers:')
    for (const s of data.servers) {
      log(`  ${s.serverKey ? s.serverKey.slice(0, 16) + '...' : '?'} — ${s.hostname || s.instanceUrl || 'unknown'} (${s.status})`)
    }
  }
}

export function deployHelp () {
  log(`Usage: pgh deploy <subcommand> [args] [flags]

Subcommands:
  setup <repo>           Configure deployment for a repo
  status [repo]          Show deploy status (all repos if no name)
  trigger <repo>         Execute deploy now
  log <repo>             Show deploy history
  disable <repo>         Remove deploy config
  registry               Show SharedDeployRegistry status

Setup Flags:
  --channel NAME         Pear channel name (default: repo name)
  --mode git|legacy      Deploy mode (default: git)
  --auto-release false   Disable auto-release after staging (legacy)
  --auto-seed false      Disable auto-seed after release (legacy)
  --auto-deploy false    Disable auto-deploy on git push

Trigger Flags:
  --legacy               Force legacy Hyperdrive deploy

Other Flags:
  --json                 Output as JSON

Examples:
  pgh deploy setup hyper-note --channel hyper-note
  pgh deploy setup wirecore --channel wirecore --mode legacy
  pgh deploy status
  pgh deploy trigger hyper-note
  pgh deploy log hyper-note
  pgh deploy registry`)
}