hackages
Version:
CLI tool for learning software development concepts through test-driven development
136 lines (135 loc) • 4.66 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 { randomUUID } from "crypto";
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 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();
// first read the file then merge the auth object with the existing data
const existingData = fs.existsSync(tokenPath) ? JSON.parse(fs.readFileSync(tokenPath, "utf8")) : {};
const mergedData = { ...existingData.user, ...auth.user };
fs.writeFileSync(tokenPath, JSON.stringify({ user: mergedData }, 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 {
// Open GitHub OAuth page
const { authUrl } = await apiClient.auth.getAuthUrl();
printInfo("Opening GitHub login page...");
await open(authUrl);
// Wait for user to complete OAuth flow
printInfo("Please complete the GitHub login in your browser.");
printInfo("After logging in, you'll be redirected to a page with a code.");
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 GitHub:",
validate: (value) => (value.length > 0 ? true : "Please enter the code"),
});
if (!code) {
throw new Error("No code provided");
}
// Exchange code for token
const { authToken: token, user } = await apiClient.auth.login(code);
// Store auth data
storeAuth({ access_token: token, user });
printSuccess(`Successfully logged in as ${user?.name || user?.login}!`);
}
catch (error) {
printError(`Login failed: ${error}`);
throw error;
}
}
export function isLoggedIn() {
const data = getStoredAuth();
if (data && data.user && data.user.login) {
return true;
}
return false;
}
export function getCurrentUser() {
const auth = getStoredAuth();
if (auth && auth.user) {
return auth.user;
}
// write to .hackages/auth.json {user: {login: "uuid"}}
const user = {
anonymousLogin: randomUUID(),
createdAt: new Date().toISOString()
};
storeAuth({ user });
return user;
}