Lesson 9 of 13

Mini Project: Typed CLI Tool

Build a small typed command-line tool that queries a public . It brings together , , /await and the Result pattern in one program.

#!/usr/bin/env ts-node
// github-info.ts — query GitHub's public API
// Usage: ts-node github-info.ts <username> [<username2> ...]

interface GHUser {
  login: string
  name: string | null
  bio: string | null
  public_repos: number
  followers: number
  following: number
  location: string | null
  blog: string
  created_at: string
}

type Result<T> =
  | { ok: true;  value: T }
  | { ok: false; error: string }

async function fetchGH<T>(path: string): Promise<Result<T>> {
  try {
    const res = await fetch(`https://api.github.com${path}`, {
      headers: { "Accept": "application/vnd.github.v3+json" }
    })
    if (res.status === 404) return { ok: false, error: "Not found" }
    if (!res.ok)            return { ok: false, error: `HTTP ${res.status}` }
    return { ok: true, value: await res.json() as T }
  } catch (e) {
    return { ok: false, error: e instanceof Error ? e.message : "Network error" }
  }
}

function formatUser(u: GHUser): string {
  const lines: string[] = [
    `── @${u.login} ${u.name ? `(${u.name})` : ""}`,
    u.bio      ? `   ${u.bio}`         : "",
    u.location ? `   📍 ${u.location}` : "",
    u.blog     ? `   🔗 ${u.blog}`     : "",
    `   repos: ${u.public_repos}  followers: ${u.followers}`,
  ]
  return lines.filter(Boolean).join("\n")
}

async function main(): Promise<void> {
  const usernames = process.argv.slice(2)

  if (usernames.length === 0) {
    console.log("Usage: ts-node github-info.ts <username> [username2] ...")
    process.exit(1)
  }

  const results = await Promise.all(
    usernames.map(async (username): Promise<[string, Result<GHUser>]> => [
      username,
      await fetchGH<GHUser>(`/users/${username}`),
    ])
  )

  for (const [username, result] of results) {
    if (result.ok) {
      console.log(formatUser(result.value))
    } else {
      console.error(`✗ ${username}: ${result.error}`)
    }
    console.log()
  }
}

main().catch(err => {
  console.error(err)
  process.exit(1)
})
TypeScript (compiler loads on first run)Ctrl+Enter runs
Bash
ts-node github-info.ts torvalds gvanrossum Rich-Harris
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.