hackages
Version:
CLI tool for learning software development concepts through test-driven development
165 lines (164 loc) • 5.5 kB
JavaScript
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;
}