Skip to content

Commit e17d7aa

Browse files
Changeset (#70)
2 parents 35b7b54 + 9f7aa03 commit e17d7aa

18 files changed

+1117
-490
lines changed

.changeset/gorgeous-keys-swim.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eth-tech-tree": patch
3+
---
4+
5+
Major UI overhaul. Everything should still work similarly to last release.

README.md

+15-14
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
1-
> ⚠️ Ethereum Development Tech Tree is currently under heavy construction.
2-
31
# ETH Development Tech Tree
4-
Test your skills and find some new ones by completing challenges.
5-
6-
There are three different types of nodes on the tree:
7-
- [x] Challenges: A repository that poses a problem that you must solve with Solidity. You deploy your contract and submit your contract address so we can test it to ensure your solution works, allowing you to progress.
8-
- [ ] Quizzes: Links to source material that will help you to master a topic that will be encountered in later challenges.
9-
- [ ] Capstone Projects: These are large scale projects that stretch your knowledge about the ecosystem. A description of the project is provided but it is up to you to fulfill the description.
2+
Test your skills and find some new ones by completing medium to hard Solidity challenges.
103

114
## Quick Start
125
Run the following command to use the NPM package
136
```bash
147
npx eth-tech-tree
158
```
16-
The CLI visualizes several categories which contain challenges. Navigate with your arrow keys and hit enter to view options for a challenge. Follow the instructions in your CLI to complete challenges fill out your Ethereum dev tech skills.
9+
The CLI visualizes several categories which contain challenges. Navigate with your arrow keys and hit enter to view the challenge description and see options. Follow the instructions in your CLI to complete challenges and fill out your Ethereum development tech tree.
10+
11+
You can also run individual commands without the tree visualization.
12+
13+
Set up a challenge:
14+
```bash
15+
npx eth-tech-tree setup CHALLENGE_NAME INSTALL_LOCATION
16+
```
17+
18+
Submit a challenge:
19+
```bash
20+
npx eth-tech-tree submit CHALLENGE_NAME CONTRACT_ADDRESS
21+
```
1722

1823
## Development
1924
Clone and `cd` into the repo then run this CLI application with the following commands
2025
- `yarn install`
2126
- `yarn build`
2227
- `yarn cli`
2328

24-
## TODO
25-
- [ ] Show users how many challenges they have completed in a category
26-
- [ ] Show users where they rank on a leaderboard
27-
- [ ] Onchain NFT mint or attestations showing a user has completed certain challenges
28-
- [ ] Enable Gas Efficiency CTF element
29+
Also consider contributing new challenges here: https://github.com/BuidlGuidl/eth-tech-tree-challenges

package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"license": "MIT",
3333
"devDependencies": {
3434
"@rollup/plugin-typescript": "11.1.0",
35-
"@types/inquirer": "9.0.3",
3635
"@types/ncp": "2.0.5",
3736
"@types/node": "18.16.0",
3837
"rollup": "3.21.0",
@@ -42,6 +41,7 @@
4241
},
4342
"dependencies": {
4443
"@changesets/cli": "^2.26.2",
44+
"@inquirer/prompts": "^7.1.0",
4545
"@types/terminal-kit": "^2.5.6",
4646
"ansi-escapes": "^7.0.0",
4747
"arg": "5.0.2",
@@ -50,12 +50,11 @@
5050
"dotenv": "^16.4.5",
5151
"execa": "7.1.1",
5252
"handlebars": "^4.7.7",
53-
"inquirer": "9.2.0",
54-
"inquirer-tree-prompt": "^1.1.2",
5553
"listr2": "^8.2.5",
5654
"merge-packages": "^0.1.6",
5755
"ncp": "^2.0.0",
5856
"pkg-install": "1.0.0",
57+
"semver": "^7.6.3",
5958
"terminal-kit": "^3.1.1"
6059
},
6160
"packageManager": "[email protected]"

rollup.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export default {
99
sourcemap: true,
1010
},
1111
plugins: [autoExternal(), typescript({ exclude: ["challenges/**"] })],
12+
external: ["@inquirer/core"],
1213
};

src/actions/setup-challenge.ts

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import { execa } from "execa";
2+
import semver, { Range } from 'semver';
23
import ncp from "ncp";
34
import path from "path";
45
import fs from "fs";
56
import { createFirstGitCommit } from "../tasks/create-first-git-commit";
67
import { fetchChallenges } from "../modules/api";
7-
import { loadChallenges } from "../utils/stateManager";
8+
import { loadChallenges } from "../utils/state-manager";
89
import { IChallenge } from "../types";
910
import { BASE_REPO, BASE_BRANCH, BASE_COMMIT } from "../config";
1011
import { DefaultRenderer, Listr, ListrTaskWrapper, SimpleRenderer } from "listr2";
1112
import chalk from "chalk";
1213

14+
type RequiredDependency = "node" | "git" | "yarn" | "foundryup";
15+
1316
// Sidestep for ncp issue https://github.com/AvianFlu/ncp/issues/127
1417
const copy = (source: string, destination: string, options?: ncp.Options) => new Promise((resolve, reject) => {
1518
ncp(source, destination, options || {}, (err) => {
@@ -57,6 +60,10 @@ export const setupChallenge = async (name: string, installLocation: string) => {
5760
}
5861

5962
const tasks = new Listr([
63+
{
64+
title: 'Checking for required dependencies',
65+
task: () => checkUserDependencies()
66+
},
6067
{
6168
title: 'Setting up base repository',
6269
task: () => setupBaseRepo(targetDir)
@@ -84,11 +91,39 @@ export const setupChallenge = async (name: string, installLocation: string) => {
8491
console.log(chalk.green("Challenge setup completed successfully."));
8592
console.log("");
8693
console.log(chalk.cyan(`Now open this repository in your favorite code editor and look at the readme for instructions: ${targetDir}`));
87-
} catch (error) {
88-
console.error(chalk.red("An error occurred during challenge setup:"), error);
94+
} catch (error: any) {
95+
console.error(chalk.red("An error occurred during challenge setup:"), error.message);
96+
}
97+
}
98+
99+
const checkDependencyInstalled = async (name: RequiredDependency) => {
100+
try {
101+
await execa(name, ["--help"]);
102+
} catch(_) {
103+
throw new Error(`${name} is required. Please install to continue.`);
89104
}
90105
}
91106

107+
const checkDependencyVersion = async (name: RequiredDependency, requiredVersion: string | Range) => {
108+
try {
109+
const userVersion = (await execa(name, ["--version"])).stdout;
110+
if (!semver.satisfies(userVersion, requiredVersion)) {
111+
throw new Error(`${name} version requirement of ${requiredVersion} not met. Please update to continue.`);
112+
}
113+
} catch(_) {
114+
throw new Error(`${name} ${requiredVersion} is required. Please install to continue.`);
115+
}
116+
}
117+
118+
export const checkUserDependencies = async () => {
119+
await Promise.all([
120+
checkDependencyVersion("node", ">=18.17.0"),
121+
checkDependencyInstalled("git"),
122+
checkDependencyInstalled("yarn"),
123+
checkDependencyInstalled("foundryup"),
124+
])
125+
}
126+
92127
const setupBaseRepo = async (targetDir: string): Promise<void> => {
93128
await execa("git", ["clone", "--branch", BASE_BRANCH, "--single-branch", BASE_REPO, targetDir]);
94129
await execa("git", ["checkout", BASE_COMMIT], { cwd: targetDir });

src/actions/submit-challenge.ts

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
import inquirer from "inquirer";
2-
import { loadUserState } from "../utils/stateManager";
1+
import { loadUserState } from "../utils/state-manager";
32
import { submitChallengeToServer } from "../modules/api";
43
import chalk from "chalk";
4+
import { input } from "@inquirer/prompts";
55

66
export async function submitChallenge(name: string, contractAddress?: string) {
77
const { address: userAddress } = loadUserState();
88
if (!contractAddress) {
99
// Prompt the user for the contract address
10-
const questions = [
11-
{
12-
type: "input",
13-
name: "address",
14-
message: "Completed challenge contract address on Sepolia:",
15-
validate: (value: string) => /^0x[a-fA-F0-9]{40}$/.test(value),
16-
},
17-
];
18-
const answers = await inquirer.prompt(questions);
19-
const { address } = answers;
20-
contractAddress = address;
10+
const question = {
11+
message: "Completed challenge contract address on Sepolia:",
12+
validate: (value: string) => /^0x[a-fA-F0-9]{40}$/.test(value),
13+
};
14+
const answer = await input(question);
15+
contractAddress = answer;
2116
}
2217

2318
console.log("Submitting challenge...");

src/cli.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { promptForMissingUserState } from "./tasks/prompt-for-missing-user-state";
22
import { renderIntroMessage } from "./tasks/render-intro-message";
33
import type { Args, IUser } from "./types";
4-
import { startVisualization } from "./utils/tree";
5-
import { loadUserState, saveChallenges } from "./utils/stateManager";
4+
import { loadUserState, saveChallenges } from "./utils/state-manager";
65
import { fetchChallenges } from "./modules/api";
76
import { parseCommandArgumentsAndOptions, promptForMissingCommandArgs } from "./tasks/parse-command-arguments-and-options";
87
import { handleCommand } from "./tasks/handle-command";
8+
import { TechTree } from ".";
99

1010

1111

@@ -19,7 +19,9 @@ export async function cli(args: Args) {
1919
await renderIntroMessage();
2020
await init(userState);
2121
// Navigate tree
22-
await startVisualization();
22+
const techTree = new TechTree();
23+
24+
await techTree.start();
2325
}
2426
}
2527

0 commit comments

Comments
 (0)