| 1 | import { spinner, log } from "@clack/prompts"; |
| 2 | import { execSync } from "node:child_process"; |
| 3 | import { writeFileSync, existsSync } from "node:fs"; |
| 4 | import { join, resolve } from "node:path"; |
| 5 | import { hubRequest } from "../api.js"; |
| 6 | import { getHub } from "../config.js"; |
| 7 | import { buildSlConfig } from "../sl-config.js"; |
| 8 | |
| 9 | interface Repo { |
| 10 | id: number; |
| 11 | owner_name: string; |
| 12 | name: string; |
| 13 | default_branch: string; |
| 14 | } |
| 15 | |
| 16 | export async function clone(args: string[]) { |
| 17 | const slug = args.find((a) => !a.startsWith("--")); |
| 18 | if (!slug) { |
| 19 | log.error("Usage: grove clone <owner/repo> [destination]"); |
| 20 | process.exit(1); |
| 21 | } |
| 22 | |
| 23 | let owner: string; |
| 24 | let repoName: string; |
| 25 | |
| 26 | if (slug.includes("/")) { |
| 27 | [owner, repoName] = slug.split("/", 2); |
| 28 | } else { |
| 29 | // Bare repo name — look up owner from API |
| 30 | const s = spinner(); |
| 31 | s.start("Looking up repository"); |
| 32 | const { repos } = await hubRequest<{ repos: Repo[] }>("/api/repos"); |
| 33 | const match = repos.find((r) => r.name === slug); |
| 34 | if (!match) { |
| 35 | s.stop("Not found"); |
| 36 | log.error(`Repository '${slug}' not found.`); |
| 37 | process.exit(1); |
| 38 | } |
| 39 | owner = match.owner_name; |
| 40 | repoName = match.name; |
| 41 | s.stop(`Found ${owner}/${repoName}`); |
| 42 | } |
| 43 | |
| 44 | // Verify repo exists |
| 45 | const s = spinner(); |
| 46 | s.start(`Fetching ${owner}/${repoName}`); |
| 47 | const { repo } = await hubRequest<{ repo: Repo }>( |
| 48 | `/api/repos/${owner}/${repoName}` |
| 49 | ); |
| 50 | s.stop(`${owner}/${repoName}`); |
| 51 | |
| 52 | const dest = args.find((a, i) => i > args.indexOf(slug) && !a.startsWith("--")) ?? repoName; |
| 53 | const destPath = resolve(dest); |
| 54 | |
| 55 | // Run sl clone |
| 56 | log.step(`Cloning into ${dest}`); |
| 57 | try { |
| 58 | execSync(`sl clone slapi:${repoName} ${dest}`, { stdio: "inherit" }); |
| 59 | } catch { |
| 60 | // sl clone of empty repos exits non-zero — check if .sl was created |
| 61 | if (!existsSync(join(destPath, ".sl"))) { |
| 62 | // Clone truly failed |
| 63 | process.exit(1); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | // Write proper .sl/config |
| 68 | const hub = await getHub(); |
| 69 | writeFileSync(join(destPath, ".sl", "config"), buildSlConfig({ name: repoName, owner_name: owner, default_branch: repo.default_branch }, hub)); |
| 70 | log.success(`Cloned ${owner}/${repoName} into ${dest}`); |
| 71 | } |
| 72 | |