at://Press
at main 133 lines 4.1 kB view raw
1#!/usr/bin/env node 2 3/** 4 * at://press setup script 5 * Interactively configures your .env file by connecting to your PDS. 6 * 7 * Usage: npm run setup 8 */ 9 10import { createInterface } from "node:readline/promises"; 11import { writeFileSync, existsSync, readFileSync } from "node:fs"; 12import { stdin, stdout } from "node:process"; 13 14const rl = createInterface({ input: stdin, output: stdout }); 15 16function log(msg) { console.log(`\x1b[36m>\x1b[0m ${msg}`); } 17function success(msg) { console.log(`\x1b[32m✓\x1b[0m ${msg}`); } 18function error(msg) { console.error(`\x1b[31m✗\x1b[0m ${msg}`); } 19 20async function main() { 21 console.log("\n \x1b[1mat://press\x1b[0m setup\n"); 22 23 // 1. PDS URL 24 const pdsUrl = (await rl.question(" PDS URL (e.g., https://bsky.social): ")).trim().replace(/\/+$/, ""); 25 if (!pdsUrl) { error("PDS URL is required."); process.exit(1); } 26 27 // Validate PDS is reachable 28 log("Checking PDS..."); 29 try { 30 const desc = await fetch(`${pdsUrl}/xrpc/com.atproto.server.describeServer`); 31 if (!desc.ok) throw new Error(`HTTP ${desc.status}`); 32 const data = await desc.json(); 33 success(`Connected to PDS (${data.availableUserDomains?.join(", ") || "custom"})`); 34 } catch (e) { 35 error(`Cannot reach PDS at ${pdsUrl}: ${e.message}`); 36 process.exit(1); 37 } 38 39 // 2. Handle 40 const handle = (await rl.question(" Your handle (e.g., you.bsky.social): ")).trim(); 41 if (!handle) { error("Handle is required."); process.exit(1); } 42 43 // Resolve DID from handle 44 log("Resolving DID..."); 45 let did; 46 try { 47 const res = await fetch(`${pdsUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`); 48 if (!res.ok) { 49 // Try public API as fallback 50 const pub = await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`); 51 if (!pub.ok) throw new Error("Handle not found"); 52 did = (await pub.json()).did; 53 } else { 54 did = (await res.json()).did; 55 } 56 success(`Resolved: ${did}`); 57 } catch (e) { 58 error(`Could not resolve handle "${handle}": ${e.message}`); 59 process.exit(1); 60 } 61 62 // 3. App password 63 const appPassword = (await rl.question(" App password (create in PDS Settings > App Passwords): ")).trim(); 64 if (!appPassword) { error("App password is required."); process.exit(1); } 65 66 // Test authentication 67 log("Testing authentication..."); 68 try { 69 const res = await fetch(`${pdsUrl}/xrpc/com.atproto.server.createSession`, { 70 method: "POST", 71 headers: { "Content-Type": "application/json" }, 72 body: JSON.stringify({ identifier: did, password: appPassword }), 73 }); 74 if (!res.ok) { 75 const body = await res.json().catch(() => ({})); 76 throw new Error(body.message || `HTTP ${res.status}`); 77 } 78 success("Authentication successful!"); 79 } catch (e) { 80 error(`Authentication failed: ${e.message}`); 81 process.exit(1); 82 } 83 84 // 4. Blog URL 85 const blogUrl = (await rl.question(" Blog URL (e.g., https://blog.example.com) [http://localhost:4321]: ")).trim() || "http://localhost:4321"; 86 87 // Write .env 88 const envContent = `# Generated by at://press setup 89PDS_URL=${pdsUrl} 90DID=${did} 91HANDLE=${handle} 92PDS_APP_PASSWORD=${appPassword} 93BLOG_URL=${blogUrl} 94 95# Optional: ATAuth gateway for login/editing 96# ATAUTH_GATEWAY_URL= 97# ATAUTH_PUBLIC_URL= 98 99# Optional: Lexicon collections (defaults to WhiteWind) 100# BLOG_COLLECTION=com.whtwnd.blog.entry 101# ABOUT_COLLECTION=xyz.arcnode.blog.about 102`; 103 104 const envPath = ".env"; 105 if (existsSync(envPath)) { 106 const overwrite = (await rl.question(" .env already exists. Overwrite? [y/N]: ")).trim().toLowerCase(); 107 if (overwrite !== "y") { 108 log("Keeping existing .env"); 109 rl.close(); 110 return; 111 } 112 } 113 114 writeFileSync(envPath, envContent); 115 success("Wrote .env file"); 116 117 console.log(` 118 \x1b[1mSetup complete!\x1b[0m 119 120 Start the dev server: 121 \x1b[36mnpm run dev\x1b[0m 122 123 Or deploy with Docker: 124 \x1b[36mdocker compose up -d\x1b[0m 125`); 126 127 rl.close(); 128} 129 130main().catch((e) => { 131 error(e.message); 132 process.exit(1); 133});