OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-sync / examples / cli.js

120 lines · javascript ref:
#!/usr/bin/env node
/**
 * Mini CLI example for hyper-sync
 *
 * Usage:
 *   node examples/cli.js status [--repos <dir>]
 *   node examples/cli.js serve [name] [--repos <dir>]
 *   node examples/cli.js add-remote <name> <serverKey>
 *   node examples/cli.js pull <remote> [repo]
 */

const HyperSync = require('../index')

const args = process.argv.slice(2)
const cmd = args[0]

function getFlag (name) {
  const i = args.indexOf('--' + name)
  return i !== -1 && args[i + 1] ? args[i + 1] : null
}

function fmt (b) {
  if (b < 1024) return b + ' B'
  if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'
  return (b / 1048576).toFixed(1) + ' MB'
}

async function main () {
  const repos = getFlag('repos')
  const sync = new HyperSync(repos ? { repos } : {})
  await sync.ready()

  if (cmd === 'status') {
    const s = sync.status()
    console.log('Server key: %s', s.serverKey)
    console.log('Repos: %d, Serving: %d, Remotes: %d\n', s.repoCount, s.serving, s.remotes)

    if (s.repos.length === 0) {
      console.log('  (no repos found)')
    } else {
      for (const r of s.repos) {
        console.log('  %s %d branches  %s  %s',
          r.name.padEnd(30), r.branches, fmt(r.packSize).padStart(8),
          r.head ? r.head : '(empty)')
      }
    }

    await sync.close()
  } else if (cmd === 'serve') {
    const name = args[1] && !args[1].startsWith('--') ? args[1] : null

    sync.on('serve', (n, topic) => console.log('  %s → %s', n, topic.slice(0, 16) + '...'))
    sync.on('connection', (peer, repo) => console.log('Peer %s connected (repo: %s)', peer, repo))

    await sync.serve(name)
    console.log('Serving %d repo(s). Server key: %s', sync.serving, sync.serverKey)
    console.log('Ctrl+C to stop')
  } else if (cmd === 'add-remote') {
    const name = args[1]
    const key = args[2]
    if (!name || !key) {
      console.error('Usage: cli.js add-remote <name> <serverKey>')
      await sync.close()
      process.exit(1)
    }

    sync.addRemote(name, key)
    console.log('Added remote "%s" → %s', name, key.slice(0, 16) + '...')

    // Note: remotes are in-memory only — a real app would persist them
    console.log('(remote stored in memory — will not persist across restarts)')
    await sync.close()
  } else if (cmd === 'pull') {
    const remoteName = args[1]
    const repoName = args[2] && !args[2].startsWith('--') ? args[2] : null

    if (!remoteName) {
      console.error('Usage: cli.js pull <remote> [repo]')
      await sync.close()
      process.exit(1)
    }

    // For this example, accept serverKey directly as the remote name
    // and auto-register it
    if (remoteName.length === 64) {
      sync.addRemote('remote', remoteName)
      console.log('Auto-registered remote from key: %s...', remoteName.slice(0, 16))
    }

    const remote = remoteName.length === 64 ? 'remote' : remoteName

    sync.on('pull', (r, name, state) => console.log('  %s/%s: %s', r, name, state))

    try {
      const result = await sync.pull(remote, repoName)
      console.log('Synced: %d, Failed: %d, Elapsed: %dms',
        result.synced.length, result.failed.length, result.elapsed)
      if (result.failed.length) {
        for (const f of result.failed) console.log('  FAIL: %s — %s', f.name, f.error)
      }
    } catch (err) {
      console.error('Pull failed: %s', err.message)
    }

    await sync.close()
  } else {
    console.log('hyper-sync example CLI')
    console.log('  status                         List repos')
    console.log('  serve [name]                   Announce repos on DHT')
    console.log('  add-remote <name> <key>        Register a remote server')
    console.log('  pull <remote|key> [repo]       Fetch updates from remote')
    console.log('')
    console.log('Options:')
    console.log('  --repos <dir>                  Git repos directory (default: ~/git-repos)')
    await sync.close()
  }
}

main().catch((err) => { console.error(err.message); process.exit(1) })