2.5 KB77 lines
Blame
1import { spinner, log } from "@clack/prompts";
2import { execSync } from "node:child_process";
3import { writeFileSync, existsSync, mkdirSync } from "node:fs";
4import { join, resolve } from "node:path";
5import { hubRequest } from "../api.js";
6import { getHub } from "../config.js";
7import { buildSlConfig } from "../sl-config.js";
8
9interface Repo {
10 id: number;
11 owner_name: string;
12 name: string;
13 default_branch: string;
14}
15
16export 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 // Use sl init + pull (not sl clone, which uses getbundle protocol that doesn't work with Mononoke)
56 const hub = await getHub();
57
58 const s2 = spinner();
59 s2.start(`Cloning into ${dest}`);
60 mkdirSync(destPath, { recursive: true });
61 execSync(`sl init --config init.prefer-git=false --config format.use-remotefilelog=true "${destPath}"`, { stdio: "pipe" });
62
63 // Write config with remote and auth settings before pulling
64 writeFileSync(join(destPath, ".sl", "config"), buildSlConfig({ name: repoName, owner_name: owner, default_branch: repo.default_branch }, hub));
65
66 // Pull and checkout
67 try {
68 execSync(`sl pull`, { cwd: destPath, stdio: "pipe" });
69 execSync(`sl goto ${repo.default_branch}`, { cwd: destPath, stdio: "pipe" });
70 s2.stop(`Cloned ${owner}/${repoName}`);
71 } catch {
72 s2.stop(`Cloned ${owner}/${repoName} (empty repository)`);
73 }
74
75 log.success(`Cloned ${owner}/${repoName} into ${dest}`);
76}
77