Skip to content

Commit 4fe4de7

Browse files
Release (#125)
2 parents fa4d216 + 71f949b commit 4fe4de7

File tree

6 files changed

+152
-34
lines changed

6 files changed

+152
-34
lines changed

.changeset/early-ads-sneeze.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eth-tech-tree": patch
3+
---
4+
5+
fix ens resolution

.changeset/stale-papayas-wink.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eth-tech-tree": minor
3+
---
4+
5+
Move config files from the `cwd` to the user's home directory `~/.eth-tech-tree` to prevent creating multiple config files when the command is run from different locations

src/actions/remove-storage.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import fs from "fs";
22
import path from "path";
3+
import { getConfigPath } from "../utils/state-manager";
34

45
export function removeStorage() {
56
console.log("Resetting storage...");
6-
const configPath = path.join(process.cwd(), "storage");
7-
if (!fs.existsSync(configPath)) {
7+
8+
const configPath = getConfigPath();
9+
let removedNew = false;
10+
if (fs.existsSync(configPath)) {
11+
fs.rmSync(configPath, { recursive: true });
12+
removedNew = true;
13+
}
14+
15+
// Remove legacy storage location
16+
const legacyPath = path.join(process.cwd(), "storage");
17+
let removedLegacy = false;
18+
if (fs.existsSync(legacyPath)) {
19+
fs.rmSync(legacyPath, { recursive: true });
20+
removedLegacy = true;
21+
}
22+
23+
if (!removedNew && !removedLegacy) {
824
console.log("Storage does not exist. Nothing to reset.");
925
return;
1026
}
11-
fs.rmSync(configPath, { recursive: true });
27+
1228
console.log("Storage reset successfully.");
1329
}

src/modules/api.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,25 @@ export const submitChallengeToServer = async (userAddress: string, challengeName
7373
}
7474
};
7575

76+
/**
77+
* Get address from ENS name
78+
*/
79+
export const getEnsAddress = async (ensName: string) => {
80+
try {
81+
const response = await fetch(`${API_URL}/ens/${ensName}`, {
82+
method: 'GET',
83+
headers: {
84+
'Content-Type': 'application/json',
85+
},
86+
});
87+
const data = await response.json();
88+
return data;
89+
} catch (error) {
90+
console.error('Error:', error);
91+
return { valid: false, error: 'Network error' };
92+
}
93+
};
94+
7695
/**
7796
* Fetch Leaderboard
7897
*/

src/utils/helpers.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import os from "os";
22
import fs from "fs";
33
import { IChallenge } from "../types";
4-
import { loadChallenges } from "./state-manager";
5-
import { fetchChallenges } from "../modules/api";
4+
import { fetchChallenges, getEnsAddress } from "../modules/api";
65
import { Choice } from "../tasks/parse-command-arguments-and-options";
76

87
export function wait(ms: number) {
@@ -29,12 +28,26 @@ export const checkValidPathOrCreate = async (path: string) => {
2928
}
3029
};
3130

31+
export const isValidEns = async (name: string): Promise<boolean> => {
32+
try {
33+
const result = await getEnsAddress(name);
34+
return result.address !== null;
35+
} catch (error) {
36+
console.error('Error validating ENS:', error);
37+
return false;
38+
}
39+
};
40+
3241
export const isValidAddress = (value: string): boolean => {
3342
return /^0x[a-fA-F0-9]{40}$/.test(value)
3443
};
3544

36-
export const isValidAddressOrENS = (value: string): boolean => {
37-
return /^(0x[a-fA-F0-9]{40}|.+\.eth)$/.test(value);
45+
export const isValidAddressOrENS = async (value: string): Promise<string | boolean> => {
46+
if (value.endsWith('.eth')) {
47+
return await isValidEns(value) ? true : "Invalid ENS name";
48+
} else {
49+
return isValidAddress(value) ? true : "Invalid address";
50+
}
3851
};
3952

4053
export const getDevice = (): string => {

src/utils/state-manager.ts

Lines changed: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,111 @@
11
import fs from 'fs';
22
import { promisify } from 'util';
33
import path from 'path';
4+
import os from 'os';
45
import { IChallenge, IUser } from '../types';
56

67
const writeFile = promisify(fs.writeFile);
78

8-
export async function saveUserState(state: IUser) {
9-
const configPath = path.join(process.cwd(), "storage");
9+
export function getConfigPath(): string {
10+
return path.join(os.homedir(), '.eth-tech-tree', 'config');
11+
}
12+
13+
function getLegacyConfigPath(): string {
14+
return path.join(process.cwd(), "storage");
15+
}
16+
17+
function ensureConfigExists(): void {
18+
const configPath = getConfigPath();
1019
if (!fs.existsSync(configPath)) {
11-
fs.mkdirSync(configPath);
20+
fs.mkdirSync(configPath, { recursive: true });
1221
}
13-
const filePath = path.join(configPath, "user.json");
14-
await writeFile(filePath, JSON.stringify(state, null, 2));
1522
}
1623

17-
export function loadUserState(): IUser {
24+
function migrateLegacyConfig(): void {
25+
const legacyPath = getLegacyConfigPath();
26+
const newPath = getConfigPath();
27+
28+
if (fs.existsSync(legacyPath)) {
29+
ensureConfigExists();
30+
31+
let migratedAnyFiles = false;
32+
33+
// Migrate user.json if it exists
34+
const legacyUserFile = path.join(legacyPath, "user.json");
35+
const newUserFile = path.join(newPath, "user.json");
36+
if (fs.existsSync(legacyUserFile) && !fs.existsSync(newUserFile)) {
37+
fs.copyFileSync(legacyUserFile, newUserFile);
38+
fs.unlinkSync(legacyUserFile);
39+
console.log("Migrated user config from local storage to home directory");
40+
migratedAnyFiles = true;
41+
}
42+
43+
// Migrate challenges.json if it exists
44+
const legacyChallengesFile = path.join(legacyPath, "challenges.json");
45+
const newChallengesFile = path.join(newPath, "challenges.json");
46+
if (fs.existsSync(legacyChallengesFile) && !fs.existsSync(newChallengesFile)) {
47+
fs.copyFileSync(legacyChallengesFile, newChallengesFile);
48+
fs.unlinkSync(legacyChallengesFile);
49+
console.log("Migrated challenges config from local storage to home directory");
50+
migratedAnyFiles = true;
51+
}
52+
53+
// Remove the legacy directory if it's empty after migration
54+
if (migratedAnyFiles) {
55+
try {
56+
const remainingFiles = fs.readdirSync(legacyPath);
57+
if (remainingFiles.length === 0) {
58+
fs.rmdirSync(legacyPath);
59+
console.log("Removed legacy storage directory");
60+
}
61+
} catch (error) {
62+
// Ignore errors when trying to remove the directory
63+
// (it might not be empty or we might not have permissions)
64+
}
65+
}
66+
}
67+
}
68+
69+
function loadConfigWithMigration<T>(filename: string, defaultValue: T): T {
1870
try {
19-
const configPath = path.join(process.cwd(), "storage", `user.json`);
20-
const data = fs.readFileSync(configPath, 'utf8');
21-
return JSON.parse(data) as IUser;
71+
const filePath = path.join(getConfigPath(), filename);
72+
const data = fs.readFileSync(filePath, 'utf8');
73+
return JSON.parse(data);
2274
} catch (error: any) {
2375
if (error.code === 'ENOENT') {
24-
return {} as IUser; // Return empty object if file doesn't exist
76+
migrateLegacyConfig();
77+
78+
try {
79+
const filePath = path.join(getConfigPath(), filename);
80+
const data = fs.readFileSync(filePath, 'utf8');
81+
return JSON.parse(data);
82+
} catch (migrationError: any) {
83+
if (migrationError.code === 'ENOENT') {
84+
return defaultValue; // Return default value if file doesn't exist even after migration
85+
}
86+
throw migrationError;
87+
}
2588
}
2689
throw error;
2790
}
2891
}
2992

30-
export async function saveChallenges(challenges: IChallenge[]) {
31-
const configPath = path.join(process.cwd(), "storage");
93+
export async function saveUserState(state: IUser) {
94+
ensureConfigExists();
95+
const filePath = path.join(getConfigPath(), "user.json");
96+
await writeFile(filePath, JSON.stringify(state, null, 2));
97+
}
3298

33-
if (!fs.existsSync(configPath)) {
34-
fs.mkdirSync(configPath);
35-
}
36-
const filePath = path.join(configPath, "challenges.json");
99+
export function loadUserState(): IUser {
100+
return loadConfigWithMigration("user.json", {} as IUser);
101+
}
102+
103+
export async function saveChallenges(challenges: IChallenge[]) {
104+
ensureConfigExists();
105+
const filePath = path.join(getConfigPath(), "challenges.json");
37106
await writeFile(filePath, JSON.stringify(challenges, null, 2));
38107
}
39108

40109
export function loadChallenges(): IChallenge[] {
41-
try {
42-
const configPath = path.join(process.cwd(), "storage", `challenges.json`);
43-
const data = fs.readFileSync(configPath, 'utf8');
44-
return JSON.parse(data);
45-
} catch (error: any) {
46-
if (error.code === 'ENOENT') {
47-
return []; // Return empty array if file doesn't exist
48-
}
49-
throw error;
50-
}
51-
}
110+
return loadConfigWithMigration("challenges.json", []);
111+
}

0 commit comments

Comments
 (0)