Skip to content

Commit

Permalink
Add new release (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
escottalexander authored Jan 8, 2025
2 parents cdfe806 + 6214dd9 commit 7e1b771
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-apples-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eth-tech-tree": patch
---

Update validation messages when submitting a completed CA for a challenge, Add searchable list when setting up or submitting with command, few extra bug-fixes/tweaks
5 changes: 3 additions & 2 deletions src/actions/submit-challenge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { loadUserState } from "../utils/state-manager";
import { submitChallengeToServer } from "../modules/api";
import chalk from "chalk";
import { input } from "@inquirer/prompts";
import { isValidAddress } from "../utils/helpers";

export async function submitChallenge(name: string, contractAddress?: string) {
const { address: userAddress } = loadUserState();
if (!contractAddress) {
// Prompt the user for the contract address
const question = {
message: "Completed challenge contract address on Sepolia:",
validate: (value: string) => /^0x[a-fA-F0-9]{40}$/.test(value),
message: "What is the contract address of your completed challenge?:",
validate: (value: string) => isValidAddress(value) ? true : "Please enter a valid contract address",
};
const answer = await input(question);
contractAddress = answer;
Expand Down
30 changes: 19 additions & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,26 @@ import { TechTree } from ".";


export async function cli(args: Args) {
const commands = await parseCommandArgumentsAndOptions(args);
const userState = loadUserState();
if (commands.command || commands.help) {
const parsedCommands = await promptForMissingCommandArgs(commands, userState);
await handleCommand(parsedCommands);
} else {
await renderIntroMessage();
await init(userState);
// Navigate tree
const techTree = new TechTree();
try {
const commands = await parseCommandArgumentsAndOptions(args);
const userState = loadUserState();
if (commands.command || commands.help) {
const parsedCommands = await promptForMissingCommandArgs(commands, userState);
await handleCommand(parsedCommands);
} else {
await renderIntroMessage();
await init(userState);
// Navigate tree
const techTree = new TechTree();

await techTree.start();
await techTree.start();
}
} catch (error) {
if (error instanceof Error && error.name === 'ExitPromptError') {
// Because canceling the promise (e.g. ctrl+c) can cause the inquirer prompt to throw we need to silence this error
} else {
throw error;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class TechTree {
}
} catch (error) {
if (error instanceof Error && error.name === 'ExitPromptError') {
// Because canceling the promise can cause the inquirer prompt to throw we need to silence this error
// Because canceling the promise (e.g. ctrl+c) can cause the inquirer prompt to throw we need to silence this error
} else {
throw error;
}
Expand Down
52 changes: 39 additions & 13 deletions src/tasks/parse-command-arguments-and-options.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import arg from "arg";
import { IUser } from "../types";
import fs from "fs";
import { select, input } from "@inquirer/prompts";
import { isValidAddress } from "../utils/helpers";
import { search, input } from "@inquirer/prompts";
import { isValidAddress, searchChallenges } from "../utils/helpers";
import { promptForMissingUserState } from "./prompt-for-missing-user-state";

type Commands = {
Expand All @@ -27,6 +27,28 @@ type SubmitCommand = {

export type CommandOptions = BaseOptions & { command: string | null } & SetupCommand & SubmitCommand;

export type Choice<Value> = {
value: Value;
name?: string;
description?: string;
short?: string;
disabled?: boolean | string;
};

type SearchOptions = {
type: "search";
name: string;
message: string;
source: (term: string | undefined) => Promise<Choice<string>[]>;
}

type InputOptions = {
type: "input";
name: string;
message: string;
validate: (value: string) => string | true;
}

const commandArguments = {
setup: {
1: "challenge",
Expand Down Expand Up @@ -76,9 +98,6 @@ export async function parseCommandArgumentsAndOptions(
}

export async function promptForMissingCommandArgs(commands: CommandOptions, userState: IUser): Promise<CommandOptions> {
const cliAnswers = Object.fromEntries(
Object.entries(commands).filter(([key, value]) => value !== null)
);
const questions = [];

const { command, challenge, contractAddress } = commands;
Expand All @@ -90,9 +109,10 @@ export async function promptForMissingCommandArgs(commands: CommandOptions, user
if (command === "setup") {
if (!challenge) {
questions.push({
type: "input",
type: "search",
name: "challenge",
message: "Which challenge would you like to setup?",
source: searchChallenges
});
}
if (!installLocation) {
Expand All @@ -108,28 +128,34 @@ export async function promptForMissingCommandArgs(commands: CommandOptions, user

if (command === "submit") {
// Need user state so direct to promptForMissingUserState
await promptForMissingUserState(userState);
await promptForMissingUserState(userState, true);

if (!challenge) {
questions.push({
type: "input",
type: "search",
name: "challenge",
message: "Which challenge would you like to submit?",
source: searchChallenges
});
}
if (!contractAddress) {
questions.push({
type: "input",
name: "contractAddress",
message: "What is the deployed contract address?",
validate: isValidAddress,
message: "What is the contract address of your completed challenge?",
validate: (value: string) => isValidAddress(value) ? true : "Please enter a valid contract address",
});
}
}
const answers = [];
const answers: Record<string, string> = {};
for (const question of questions) {
const answer = await input(question);
answers.push(answer);
if (question.type === "search") {
const answer = await search(question as unknown as SearchOptions);
answers[question.name] = answer;
} else if (question.type === "input") {
const answer = await input(question as InputOptions);
answers[question.name] = answer;
}
}

return {
Expand Down
9 changes: 5 additions & 4 deletions src/tasks/prompt-for-missing-user-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const defaultOptions: Partial<IUser> = {
};

export async function promptForMissingUserState(
userState: IUser
userState: IUser,
skipInstallLocation: boolean = false
): Promise<IUser> {
const userDevice = getDevice();
let identifier = userState.address;
Expand Down Expand Up @@ -39,7 +40,7 @@ export async function promptForMissingUserState(
}

// Prompt for install location if it doesn't exist on device
if (!existingInstallLocation) {
if (!existingInstallLocation && !skipInstallLocation) {
const answer = await input({
message: "Where would you like to download the challenges?",
default: defaultOptions.installLocation,
Expand All @@ -53,8 +54,8 @@ export async function promptForMissingUserState(
}

const { address, ens, installLocations, challenges, creationTimestamp } = user;
const thisDeviceLocation = installLocations.find((loc: {location: string, device: string}) => loc.device === userDevice);
const newState = { address, ens, installLocation: thisDeviceLocation.location, challenges, creationTimestamp };
const thisDeviceLocation = installLocations?.find((loc: {location: string, device: string}) => loc.device === userDevice);
const newState = { address, ens, installLocation: thisDeviceLocation?.location, challenges, creationTimestamp };
if (JSON.stringify(userState) !== JSON.stringify(newState)) {
// Save the new state locally
await saveUserState(newState);
Expand Down
13 changes: 13 additions & 0 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os from "os";
import fs from "fs";
import { IChallenge } from "../types";
import { loadChallenges } from "./state-manager";
import { fetchChallenges } from "../modules/api";
import { Choice } from "../tasks/parse-command-arguments-and-options";

export function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
Expand Down Expand Up @@ -53,4 +56,14 @@ export const calculatePoints = (completedChallenges: Array<{ challenge: IChallen
const points = pointsPerLevel[challenge!.level - 1] || 100;
return total + points;
}, 0);
}

export const searchChallenges = async (term: string = "") => {
const challenges = (await fetchChallenges()).filter((challenge: IChallenge) => challenge.enabled);
const choices = challenges.map((challenge: IChallenge) => ({
value: challenge.name,
name: challenge.label,
description: ""
}));
return choices.filter((choice: Choice<string>) => choice.name?.toLowerCase().includes(term.toLowerCase()));
}

0 comments on commit 7e1b771

Please sign in to comment.