UNPKG

hackages

Version:

CLI tool for learning software development concepts through test-driven development

165 lines (164 loc) 5.5 kB
import open from "open"; import fs from "fs"; import path from "path"; import { printInfo, printSuccess, printError } from "../utils/console.js"; import { apiClient } from "./api.js"; import prompts from "prompts"; import { v4 as uuidv4 } from "uuid"; import { config } from "../config/index.js"; function getConfigDir() { const homeDir = process.env.HOME || process.env.USERPROFILE || ""; const configDir = path.join(homeDir, ".hackages"); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } return configDir; } function getLearningGoalPath() { const learningPath = path.join(getConfigDir(), "learning.json"); if (!fs.existsSync(learningPath)) { fs.writeFileSync(learningPath, JSON.stringify({})); } return learningPath; } export function checkIfLearningJSONIsEmpty() { const learningGoalPath = getLearningGoalPath(); const learningGoal = JSON.parse(fs.readFileSync(learningGoalPath, "utf8")); return Object.keys(learningGoal).length === 0; } export function writeLearningGoal(learningGoal) { const learningJsonPath = getLearningGoalPath(); let learningData = {}; if (fs.existsSync(learningJsonPath)) { try { const fileContent = fs.readFileSync(learningJsonPath, "utf8"); learningData = JSON.parse(fileContent); } catch (err) { // If file is corrupted or unreadable, start fresh learningData = {}; } } // Add or update the new learning goal and set as current learningData.current = learningGoal.id; learningData[learningGoal.id] = learningGoal; fs.writeFileSync(learningJsonPath, JSON.stringify(learningData, null, 2)); } function getUserIdPath() { return path.join(getConfigDir(), "userId.json"); } function getOrCreateUserId() { try { const userIdPath = getUserIdPath(); if (fs.existsSync(userIdPath)) { const data = fs.readFileSync(userIdPath, "utf8"); const parsed = JSON.parse(data); if (parsed.userId) { return parsed.userId; } } // Generate new userId if not found const userId = uuidv4(); fs.writeFileSync(userIdPath, JSON.stringify({ userId }, null, 2)); return userId; } catch (error) { // If there's an error, generate a new userId const userId = uuidv4(); try { const userIdPath = getUserIdPath(); fs.writeFileSync(userIdPath, JSON.stringify({ userId }, null, 2)); } catch (writeError) { // If we can't write, just return the generated ID } return userId; } } function getTokenPath() { return path.join(getConfigDir(), "auth.json"); } export function getStoredAuth() { try { const tokenPath = getTokenPath(); if (fs.existsSync(tokenPath)) { const data = fs.readFileSync(tokenPath, "utf8"); return JSON.parse(data); } } catch (error) { // If there's an error reading the token, treat as not authenticated } return null; } function storeAuth(auth) { try { const tokenPath = getTokenPath(); fs.writeFileSync(tokenPath, JSON.stringify(auth, null, 2)); } catch (error) { printError(`Failed to store authentication: ${error}`); } } export function clearAuth() { try { const tokenPath = getTokenPath(); if (fs.existsSync(tokenPath)) { fs.unlinkSync(tokenPath); } } catch (error) { printError(`Failed to clear authentication: ${error}`); } } export async function loginWithGitHub() { try { // Get or create userId const userId = getOrCreateUserId(); const loginUrl = `${config.apiUrl}/login?userId=${userId}`; printInfo("Opening login page in your browser..."); await open(loginUrl); // Wait for user to complete login flow printInfo("Please complete the login in your browser."); printInfo("After logging in, you'll see a code on the page."); printInfo("Copy that code and paste it here:"); // Get code from user input using prompts for better UX const { code } = await prompts({ type: "text", name: "code", message: "Enter the code from the login page:", validate: (value) => (value.length > 0 ? true : "Please enter the code"), }); if (!code) { throw new Error("No code provided"); } // Exchange code for token via api/check const response = await apiClient.auth.checkCode(code); // The API returns: { token, id, name } if (!response.token) { throw new Error("No token received from server"); } // Store the token and user info storeAuth({ access_token: response.token, user: { id: response.id, name: response.name, login: response.id, // For backward compatibility } }); // Display welcome message printSuccess(`You're connected as "${response.name}"`); } catch (error) { printError(`Login failed: ${error}`); throw error; } } export function isLoggedIn() { return !!getStoredAuth(); } export function getCurrentUser() { const auth = getStoredAuth(); return auth?.user || null; }