| @@ -1,7 +1,8 @@ |
| 1 | 1 | import { intro, outro, spinner, log } from "@clack/prompts"; |
| 2 | | import { mkdirSync, writeFileSync, existsSync } from "node:fs"; |
| 2 | import { mkdirSync, writeFileSync, existsSync, chmodSync } from "node:fs"; |
| 3 | 3 | import { join } from "node:path"; |
| 4 | | import { homedir } from "node:os"; |
| 4 | import { homedir, platform, arch } from "node:os"; |
| 5 | import { execSync } from "node:child_process"; |
| 5 | 6 | import { waitForAuthCallback } from "../auth-server.js"; |
| 6 | 7 | import { loadConfig, saveConfig } from "../config.js"; |
| 7 | 8 | |
| @@ -67,6 +68,38 @@ |
| 67 | 68 | } |
| 68 | 69 | } |
| 69 | 70 | |
| 71 | async function installSapling(hub: string) { |
| 72 | // Only install on Linux — macOS users build from source |
| 73 | if (platform() !== "linux") return; |
| 74 | |
| 75 | // Check if sl is already installed and working |
| 76 | try { |
| 77 | const version = execSync("sl --version 2>&1", { stdio: "pipe" }).toString(); |
| 78 | // Our builds are version 4.x+, old official release is 0.2 |
| 79 | if (!version.includes("0.2.")) return; |
| 80 | } catch { |
| 81 | // sl not found — need to install |
| 82 | } |
| 83 | |
| 84 | const s = spinner(); |
| 85 | s.start("Installing Sapling (sl)"); |
| 86 | try { |
| 87 | const binary = `sl-linux-${arch() === "x64" ? "x86_64" : arch()}`; |
| 88 | const res = await fetch(`${hub}/downloads/${binary}`); |
| 89 | if (!res.ok) { |
| 90 | s.stop("Sapling binary not available yet (non-fatal)"); |
| 91 | return; |
| 92 | } |
| 93 | const buf = Buffer.from(await res.arrayBuffer()); |
| 94 | const dest = "/usr/local/bin/sl"; |
| 95 | writeFileSync(dest, buf); |
| 96 | chmodSync(dest, 0o755); |
| 97 | s.stop("Sapling (sl) installed"); |
| 98 | } catch { |
| 99 | s.stop("Could not install Sapling (non-fatal)"); |
| 100 | } |
| 101 | } |
| 102 | |
| 70 | 103 | export async function authLogin(args: string[]) { |
| 71 | 104 | const config = await loadConfig(); |
| 72 | 105 | |
| @@ -85,6 +118,7 @@ |
| 85 | 118 | config.token = token; |
| 86 | 119 | await saveConfig(config); |
| 87 | 120 | await provisionTlsCerts(config.hub, token); |
| 121 | await installSapling(config.hub); |
| 88 | 122 | outro("Authenticated! Token saved to ~/.grove/config.json"); |
| 89 | 123 | } catch (err: any) { |
| 90 | 124 | log.error(err.message); |
| @@ -113,6 +147,7 @@ |
| 113 | 147 | config.token = token; |
| 114 | 148 | await saveConfig(config); |
| 115 | 149 | await provisionTlsCerts(config.hub, token); |
| 150 | await installSapling(config.hub); |
| 116 | 151 | outro("Authenticated! Token saved to ~/.grove/config.json"); |
| 117 | 152 | } catch (err: any) { |
| 118 | 153 | s.stop("Authentication failed"); |
| 119 | 154 | |