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