| @@ -1,5 +1,5 @@ |
| 1 | 1 | import { intro, outro, spinner, log } from "@clack/prompts"; |
| 2 | | import { mkdirSync, writeFileSync, existsSync, chmodSync } from "node:fs"; |
| 2 | import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync } from "node:fs"; |
| 3 | 3 | import { join } from "node:path"; |
| 4 | 4 | import { homedir, platform, arch } from "node:os"; |
| 5 | 5 | import { execSync } from "node:child_process"; |
| @@ -68,9 +68,34 @@ |
| 68 | 68 | } |
| 69 | 69 | } |
| 70 | 70 | |
| 71 | function getSaplingConfigPath(): string { |
| 72 | if (platform() === "darwin") { |
| 73 | return join(homedir(), "Library", "Preferences", "sapling", "sapling.conf"); |
| 74 | } |
| 75 | return join(homedir(), ".config", "sapling", "sapling.conf"); |
| 76 | } |
| 77 | |
| 78 | function configureSaplingUsername(username: string) { |
| 79 | try { |
| 80 | const configPath = getSaplingConfigPath(); |
| 81 | mkdirSync(join(configPath, ".."), { recursive: true }); |
| 82 | |
| 83 | if (existsSync(configPath)) { |
| 84 | const existing = readFileSync(configPath, "utf-8"); |
| 85 | if (existing.includes("username =")) return; |
| 86 | // Prepend [ui] section |
| 87 | writeFileSync(configPath, `[ui]\nusername = ${username}\n\n` + existing); |
| 88 | } else { |
| 89 | writeFileSync(configPath, `[ui]\nusername = ${username}\n`); |
| 90 | } |
| 91 | } catch { |
| 92 | // non-fatal |
| 93 | } |
| 94 | } |
| 95 | |
| 71 | 96 | async function installSapling(hub: string) { |
| 72 | | // Only install on Linux — macOS users build from source |
| 73 | | if (platform() !== "linux") return; |
| 97 | const os = platform(); |
| 98 | if (os !== "linux" && os !== "darwin") return; |
| 74 | 99 | |
| 75 | 100 | // Check if sl is already installed and working |
| 76 | 101 | try { |
| @@ -84,7 +109,8 @@ |
| 84 | 109 | const s = spinner(); |
| 85 | 110 | s.start("Installing Sapling (sl)"); |
| 86 | 111 | try { |
| 87 | | const binary = `sl-linux-${arch() === "x64" ? "x86_64" : arch()}`; |
| 112 | const archName = arch() === "x64" ? "x86_64" : arch(); |
| 113 | const binary = `sl-${os}-${archName}`; |
| 88 | 114 | const res = await fetch(`${hub}/downloads/${binary}`); |
| 89 | 115 | if (!res.ok) { |
| 90 | 116 | s.stop("Sapling binary not available yet (non-fatal)"); |
| @@ -118,6 +144,8 @@ |
| 118 | 144 | config.token = token; |
| 119 | 145 | await saveConfig(config); |
| 120 | 146 | await provisionTlsCerts(config.hub, token); |
| 147 | const payload = JSON.parse(atob(token.split(".")[1])); |
| 148 | configureSaplingUsername(`${payload.display_name || payload.username} <${payload.username}@grove.host>`); |
| 121 | 149 | await installSapling(config.hub); |
| 122 | 150 | outro("Authenticated! Token saved to ~/.grove/config.json"); |
| 123 | 151 | } catch (err: any) { |
| @@ -147,6 +175,8 @@ |
| 147 | 175 | config.token = token; |
| 148 | 176 | await saveConfig(config); |
| 149 | 177 | await provisionTlsCerts(config.hub, token); |
| 178 | const payload = JSON.parse(atob(token.split(".")[1])); |
| 179 | configureSaplingUsername(`${payload.display_name || payload.username} <${payload.username}@grove.host>`); |
| 150 | 180 | await installSapling(config.hub); |
| 151 | 181 | outro("Authenticated! Token saved to ~/.grove/config.json"); |
| 152 | 182 | } catch (err: any) { |
| 153 | 183 | |