diff --git a/campaigns/cairo-directory.json b/campaigns/cairo-directory.json deleted file mode 100644 index 924839c..0000000 --- a/campaigns/cairo-directory.json +++ /dev/null @@ -1,100 +0,0 @@ -[ - { - "name": "cairo-thinking", - "quests":[ - { - "name": "sparring-sorcerers", - "version": "1.3.0", - "type": "build", - "parts": 3 - }, - { - "name": "puzzling-pyramids", - "version": "1.3.0", - "type": "build", - "parts": 2 - }, - { - "name": "racing-riverboats", - "version": "1.3.0", - "type": "build", - "parts": 2 - } - ] - }, - { - "name": "standalone-quests", - "quests": [ - { - "name": "brainfuck", - "version": "1.3.0", - "type": "build", - "parts": 2 - }, - { - "name": "vanity-address", - "version": "1.2.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "unsafe-math", - "version": "1.1.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "calldata-spoofing", - "version": "1.1.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "starknet-storage-layout", - "version": "1.1.0", - "type": "ctf", - "parts": 4 - } - ] - }, - { - "name": "bad-accounts", - "quests": [ - { - "name": "stealing-souls", - "version": "1.2.0", - "type": "ctf", - "parts": 2 - }, - { - "name": "bendy-signatures", - "version": "1.2.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "orderless-hashing", - "version": "1.2.0", - "type": "ctf", - "parts": 1 - } - ] - }, - { - "name": "starting-cairo", - "quests": [ - { - "name": "build-tutorial-cairo", - "version": "1.3.0", - "type": "build", - "parts": 4 - }, - { - "name": "ctf-tutorial-cairo", - "version": "1.3.0", - "type": "ctf", - "parts": 1 - } - ] - } -] \ No newline at end of file diff --git a/campaigns/directory.json b/campaigns/directory.json deleted file mode 100644 index bc2c943..0000000 --- a/campaigns/directory.json +++ /dev/null @@ -1,317 +0,0 @@ -[ - { - "name": "standalone-quests", - "quests":[ - { - "name": "low-level-calls", - "version": "1.1.1", - "type": "ctf", - "parts": 2 - }, - { - "name": "merkle-proofs", - "version": "2.0.1", - "type": "build", - "parts": 2 - }, - { - "name": "unordered-key-set", - "version": "2.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "unexpected-ether", - "version": "2.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "bypassing-extcodesize", - "version": "1.1.1", - "type": "ctf", - "parts": 1 - }, - { - "name": "clone-factories", - "version": "3.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "using-signatures", - "version": "2.0.1", - "type": "build", - "parts": 2 - }, - { - "name": "multicall", - "version": "2.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "delegate-call-detection", - "version": "2.0.0", - "type": "build", - "parts": 1 - }, - { - "name": "price-oracle-attack", - "version": "1.1.1", - "type": "ctf", - "parts": 1 - }, - { - "name": "hardhat-forking", - "version": "2.0.0", - "type": "build", - "parts": 3 - }, - { - "name": "stealth-addresses", - "version": "2.0.0", - "type": "build", - "parts": 3 - }, - { - "name": "elliptic-curve", - "version": "1.0.2", - "type": "build", - "parts": 3 - }, - { - "name": "emergency-governance", - "version": "1.1.1", - "type": "ctf", - "parts": 1 - }, - { - "name": "flashloan-counterexploit", - "version": "1.1.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "self-hosted-evm", - "version": "1.0.0", - "type": "build", - "parts": 7 - }, - { - "name": "interactive-fraud-proofs", - "version": "1.0.0", - "type": "ctf", - "parts": 2 - }, - { - "name": "finding-ouroboros", - "version": "1.1.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "quadratic-public-goods", - "version": "1.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "static-call-detection", - "version": "1.0.0", - "type": "ctf", - "parts": 1 - }, - { - "name": "state-channels", - "version": "1.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "wtf-imps", - "version": "1.0.0", - "type": "build", - "parts": 1 - }, - { - "name": "setting-up", - "version": "1.0.0", - "type": "build", - "parts": 4 - }, - { - "name": "vanity-theft", - "version": "1.0.0", - "type": "ctf", - "parts": 1 - } - ] - }, - { - "name": "understanding-storage", - "quests": [ - { - "name": "storage-layout", - "version": "1.1.1", - "type": "ctf", - "parts": 3 - }, - { - "name": "storage-efficiency", - "version": "2.0.1", - "type": "build", - "parts": 2 - } - ] - }, - { - "name": "randomness", - "quests": [ - { - "name": "breaking-rng", - "version": "1.1.2", - "type": "ctf", - "parts": 2 - }, - { - "name": "chainlink-vrf", - "version": "1.1.2", - "type": "ctf", - "parts": 2 - } - ] - }, - { - "name": "proxy-contracts", - "quests": [ - { - "name": "basic-proxies", - "version": "2.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "proxy-security", - "version": "2.0.0", - "type": "ctf", - "parts": 3 - }, - { - "name": "openzeppelin-upgrades", - "version": "1.0.2", - "type": "build", - "parts": 2 - } - ] - }, - { - "name": "diamonds", - "quests": [ - { - "name": "stateful-libraries", - "version": "2.0.0", - "type": "build", - "parts": 1 - }, - { - "name": "building-diamonds", - "version": "2.0.0", - "type": "build", - "parts": 2 - }, - { - "name": "using-diamonds", - "version": "1.1.1", - "type": "ctf", - "parts": 4 - } - ] - }, - { - "name": "learning-assembly", - "quests": [ - { - "name": "yul-basics", - "version": "2.0.0", - "type": "build", - "parts": 7 - }, - { - "name": "bits-and-bytes", - "version": "2.0.0", - "type": "build", - "parts": 4 - }, - { - "name": "calldata-manipulation", - "version": "2.0.0", - "type": "build", - "parts": 3 - }, - { - "name": "memory-manipulation", - "version": "2.0.1", - "type": "build", - "parts": 4 - } - ] - }, - { - "name": "gas-optimization", - "quests": [ - { - "name": "solidity-optimization", - "version": "2.0.0", - "type": "build", - "parts": 1 - }, - { - "name": "assembly-optimization", - "version": "2.0.0", - "type": "build", - "parts": 1 - } - ] - }, - { - "name": "get-rekt", - "quests": [ - { - "name": "cream-rekt", - "version": "1.1.1", - "type": "ctf", - "parts": 1 - }, - { - "name": "poly-rekt", - "version": "1.1.1", - "type": "ctf", - "parts": 1 - }, - { - "name": "wintermute-rekt", - "version": "2.0.0", - "type": "ctf", - "parts": 1 - } - ] - }, - { - "name": "token-standards", - "quests": [ - { - "name": "building-erc-20", - "version": "1.0.0", - "type": "build", - "parts": 5 - }, - { - "name": "building-erc-721", - "version": "1.0.0", - "type": "build", - "parts": 4 - } - ] - } -] \ No newline at end of file diff --git a/campaigns/huff-directory.json b/campaigns/huff-directory.json deleted file mode 100644 index 5da672b..0000000 --- a/campaigns/huff-directory.json +++ /dev/null @@ -1,31 +0,0 @@ -[ - { - "name": "unveiling-huff", - "quests": [ - { - "name": "huff-fundamentals", - "version": "0.1.0", - "type": "build", - "parts": 2 - }, - { - "name": "decoding-arrays", - "version": "0.1.0", - "type": "build", - "parts": 2 - }, - { - "name": "optimized-dispatchers", - "version": "0.1.0", - "type": "build", - "parts": 2 - }, - { - "name": "the-final-duel", - "version": "0.1.0", - "type": "build", - "parts": 2 - } - ] - } -] \ No newline at end of file diff --git a/campaigns/noir-directory.json b/campaigns/noir-directory.json deleted file mode 100644 index 9ee64e6..0000000 --- a/campaigns/noir-directory.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "name": "discovering-noir", - "quests": [ - { - "name": "hello-noir", - "version": "1.1.0", - "type": "build", - "parts": 3 - }, - { - "name": "zk-dungeon", - "version": "1.1.0", - "type": "build", - "parts": 3 - }, - { - "name": "daggers-and-decoys", - "version": "1.1.0", - "type": "build", - "parts": 3 - } - ] - } -] \ No newline at end of file diff --git a/package.json b/package.json index 9f58623..49718a6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "ng-questplay", - "version": "1.8.3", + "version": "1.9.0", "description": "", "type": "module", "scripts": { diff --git a/src/findQuest.js b/src/findQuest.js index 33e49f7..ae0d828 100644 --- a/src/findQuest.js +++ b/src/findQuest.js @@ -30,7 +30,7 @@ export async function findQuest(questName) { process.exit(1); } - const quest = getQuest(questName); + const quest = await getQuest(questName); if (quest == null) { // Quest not found console.log(QUEST_NOT_FOUND_MESSAGE); @@ -120,10 +120,7 @@ async function queryAndPullQuest(quest) { // (3) Install Quest console.log(chalk.green("\nInstalling quest...")); - if (quest.lang == "solidity") { - // TODO: Refactor this to be multi-protocol friendly - await authDownloader.installSubpackage(); - } + await authDownloader.installSubpackage(); // (4) Commit new changes (Skip if dev mode) try { diff --git a/src/quest/baseQuest.js b/src/quest/baseQuest.js index e6c52cd..5535f4b 100644 --- a/src/quest/baseQuest.js +++ b/src/quest/baseQuest.js @@ -46,5 +46,4 @@ export class BaseQuest { } } - } diff --git a/src/quest/cairoQuest.js b/src/quest/cairoQuest.js index 756b4ed..ad7541e 100644 --- a/src/quest/cairoQuest.js +++ b/src/quest/cairoQuest.js @@ -4,20 +4,24 @@ import toml from 'toml'; import { BaseQuest } from './baseQuest.js'; import { checkScarbVersion } from '../utils/versions.js'; -import { mainPath } from '../utils/navigation.js'; import { spawnSync } from 'child_process'; +import { QuestDownloader } from '../utils/downloader.js'; export class CairoQuest extends BaseQuest { - static find(questName) { - const directoryPath = path.join(mainPath(), "campaigns/cairo-directory.json"); - const campaigns = JSON.parse(fs.readFileSync(directoryPath)); + static async find(questName) { + const qdown = new QuestDownloader(); + const campaigns = JSON.parse(await qdown.downloadFile( + "Nodeguardians", + "ng-cairo-quests-public", + "campaigns/directory.json", + {tempFile: true})); for (const campaign of campaigns) { for (const quest of campaign.quests) { - if (quest.name != questName) continue; - - return new CairoQuest(campaign.name, quest); + if (quest.name != questName) continue; + + return new CairoQuest(campaign.name, quest); } } @@ -51,7 +55,7 @@ async function runCairoTests(partIndex) { "test", "-f", `test_${partIndex}` ]; - spawnSync("scarb", cairoTestParams, {stdio: "inherit"}); + spawnSync("scarb", cairoTestParams, { stdio: "inherit" }); console.log(); - + } \ No newline at end of file diff --git a/src/quest/huffQuest.js b/src/quest/huffQuest.js index 97650b8..28fc92c 100644 --- a/src/quest/huffQuest.js +++ b/src/quest/huffQuest.js @@ -2,20 +2,24 @@ import fs from 'fs'; import path from 'path'; import { BaseQuest } from './baseQuest.js'; -import { mainPath } from '../utils/navigation.js'; import { checkForgeVersion, checkHuffCVersion } from '../utils/versions.js'; import { spawnSync } from 'child_process'; +import { QuestDownloader } from '../utils/downloader.js'; export class HuffQuest extends BaseQuest { - static find(questName) { - const directoryPath = path.join(mainPath(), "campaigns/huff-directory.json"); - const campaigns = JSON.parse(fs.readFileSync(directoryPath)); + static async find(questName) { + const qdown = new QuestDownloader(); + const campaigns = JSON.parse(await qdown.downloadFile( + "Nodeguardians", + "ng-huff-quests-public", + `campaigns/huff-directory.json`, + {tempFile: true})); for (const campaign of campaigns) { for (const quest of campaign.quests) { if (quest.name != questName) continue; - + return new HuffQuest(campaign.name, quest); } } @@ -43,9 +47,9 @@ export class HuffQuest extends BaseQuest { } } - + async function runFoundryTests(partIndex) { - + console.log(); if (!checkForgeVersion()) process.exit(1); if (!checkHuffCVersion()) process.exit(1); @@ -56,7 +60,7 @@ async function runFoundryTests(partIndex) { `*\.${partIndex}\.t\.sol` ]; - spawnSync("forge", forgeParams, {stdio: "inherit"}); + spawnSync("forge", forgeParams, { stdio: "inherit" }); console.log(); - + } \ No newline at end of file diff --git a/src/quest/index.js b/src/quest/index.js index bc7b1fb..3f5763c 100644 --- a/src/quest/index.js +++ b/src/quest/index.js @@ -6,21 +6,21 @@ import { NoirQuest } from "./noirQuest.js"; import { campaignPath } from "../utils/navigation.js"; import { HuffQuest } from "./huffQuest.js"; -export function getQuest(questName) { +export async function getQuest(questName) { // 1. Find in Solidity directory - const solidityQuest = SolidityQuest.find(questName); + const solidityQuest = await SolidityQuest.find(questName); if (solidityQuest != null) return solidityQuest; // 2. Find in Cairo directory - const cairoQuest = CairoQuest.find(questName); + const cairoQuest = await CairoQuest.find(questName); if (cairoQuest != null) return cairoQuest; // 3. Find in Noir directory - const noirQuest = NoirQuest.find(questName); + const noirQuest = await NoirQuest.find(questName); if (noirQuest != null) return noirQuest; // 3. Find in Huff directory - const huffQuest = HuffQuest.find(questName); + const huffQuest = await HuffQuest.find(questName); if (huffQuest != null) return huffQuest; return null; diff --git a/src/quest/noirQuest.js b/src/quest/noirQuest.js index 3b63927..251d8ed 100644 --- a/src/quest/noirQuest.js +++ b/src/quest/noirQuest.js @@ -2,9 +2,9 @@ import fs from 'fs'; import path from 'path'; import { BaseQuest } from './baseQuest.js'; -import { mainPath } from '../utils/navigation.js'; import { checkNargoVersion } from '../utils/versions.js'; import { execSync } from 'child_process'; +import { QuestDownloader } from '../utils/downloader.js'; let hre; @@ -12,15 +12,19 @@ const MOCHA_TIMEOUT = 10000 export class NoirQuest extends BaseQuest { - static find(questName) { - const directoryPath = path.join(mainPath(), "campaigns/noir-directory.json"); - const campaigns = JSON.parse(fs.readFileSync(directoryPath)); + static async find(questName) { + const qdown = new QuestDownloader(); + const campaigns = JSON.parse(await qdown.downloadFile( + "Nodeguardians", + "ng-noir-quests-public", + `campaigns/noir-directory.json`, + {tempFile: true})); for (const campaign of campaigns) { for (const quest of campaign.quests) { - if (quest.name != questName) continue; + if (quest.name != questName) continue; - return new NoirQuest(campaign.name, quest); + return new NoirQuest(campaign.name, quest); } } @@ -67,10 +71,10 @@ async function runJSTests(partIndex) { } else { - const mochaCommand + const mochaCommand = `npx mocha test --timeout ${MOCHA_TIMEOUT} --recursive --grep "(Part ${partIndex})"`; try { - execSync(mochaCommand, {stdio: 'inherit'}); + execSync(mochaCommand, { stdio: 'inherit' }); } catch (error) { } } diff --git a/src/quest/solQuest.js b/src/quest/solQuest.js index 37dd5c5..2c47030 100644 --- a/src/quest/solQuest.js +++ b/src/quest/solQuest.js @@ -3,23 +3,28 @@ import fs from 'fs'; import path from 'path'; import { BaseQuest } from './baseQuest.js'; -import { mainPath, readSettings } from '../utils/navigation.js'; +import { readSettings } from '../utils/navigation.js'; import { checkForgeVersion } from '../utils/versions.js'; import { spawnSync } from 'child_process'; +import { QuestDownloader } from '../utils/downloader.js'; let hre; export class SolidityQuest extends BaseQuest { - static find(questName) { - const directoryPath = path.join(mainPath(), "campaigns/directory.json"); - const campaigns = JSON.parse(fs.readFileSync(directoryPath)); + static async find(questName) { + const qdown = new QuestDownloader(); + const campaigns = JSON.parse(await qdown.downloadFile( + "Nodeguardians", + "ng-quests-public", + "campaigns/directory.json", + { tempFile: true })); for (const campaign of campaigns) { for (const quest of campaign.quests) { - if (quest.name != questName) continue; - - return new SolidityQuest(campaign.name, quest); + if (quest.name != questName) continue; + + return new SolidityQuest(campaign.name, quest); } } @@ -61,9 +66,9 @@ async function runHardhatTests(partIndex) { await hre.default.run("test", { grep: `Part ${partIndex}` }); } - + async function runFoundryTests(partIndex) { - + console.log(); if (!checkForgeVersion()) process.exit(1); @@ -73,7 +78,7 @@ async function runFoundryTests(partIndex) { `*\.${partIndex}\.t\.sol` ]; - spawnSync("forge", forgeParams, {stdio: "inherit"}); + spawnSync("forge", forgeParams, { stdio: "inherit" }); console.log(); - + } \ No newline at end of file diff --git a/src/submitQuest.js b/src/submitQuest.js index 411e254..f7468e6 100644 --- a/src/submitQuest.js +++ b/src/submitQuest.js @@ -1,6 +1,5 @@ import chalk from "chalk"; import { navigateToQuestDirectory } from "./utils/navigation.js"; -import { ProgressBar } from "./utils/progressbar.js"; import { simpleGit } from "simple-git"; import { currentWorkingQuest } from "./quest/index.js"; import { @@ -8,6 +7,7 @@ import { SUBMISSION_ERROR_BANNER, SUBMISSION_FAILED_BANNER, UNCOMMITTED_FILES_MESSAGE, + BreakingChangeMessage } from "./utils/messages.js"; import { getToken } from "./utils/token.js"; import { @@ -23,6 +23,7 @@ import { stopSpinner, succeedSpinner, } from "./utils/spinner.js"; +import semver from "semver"; const git = simpleGit(); @@ -45,6 +46,13 @@ export async function submitQuest(isSetUpstream, isListening, environment) { process.exit(0); } + const diff = semver.diff(quest.info.version, quest.localVersion()); + + if (diff == "major" || diff == "premajor") { + console.log(BreakingChangeMessage(quest.info.name)); + process.exit(1); + } + const statusSummary = await git.status(); if (statusSummary.files.length) { console.log(UNCOMMITTED_FILES_MESSAGE); diff --git a/src/utils/downloader.js b/src/utils/downloader.js index d4cd65a..07dfd15 100644 --- a/src/utils/downloader.js +++ b/src/utils/downloader.js @@ -18,7 +18,7 @@ export class QuestDownloader { async downloadQuest(owner, repo, directoryPath, options = {}) { const zipFilePath = `${directoryPath}.zip`; - this.progressBar.start(); + const startAnimation = this.progressBar.start(); try { const file = await this._octokit.repos.getContent({ @@ -36,10 +36,12 @@ export class QuestDownloader { await this.unzip(decodedContent); } catch (err) { + await startAnimation; // Wait for initial animation to finish this.progressBar.fail("Download failed"); throw err; } + await startAnimation; // Wait for initial animation to finish await this.progressBar.stop("Download finished"); } @@ -88,7 +90,7 @@ export class QuestDownloader { async downloadFile(owner, repo, filePath, options = {}) { const rootPath = options.rootPath == undefined - ? cwd() + ? process.cwd() : options.rootPath; const file = await this._octokit.repos.getContent({ @@ -99,7 +101,12 @@ export class QuestDownloader { }); const decodedContent = Buffer.from(file.data.content, file.data.encoding); - fs.writeFileSync(path.join(rootPath, filePath), decodedContent); + + if(!options.tempFile) { + fs.writeFileSync(path.join(rootPath, filePath), decodedContent); + } + + return decodedContent; } async installSubpackage() { diff --git a/src/utils/messages.js b/src/utils/messages.js index c7ac173..108fefd 100644 --- a/src/utils/messages.js +++ b/src/utils/messages.js @@ -32,7 +32,12 @@ export const WRONG_DIRECTORY_MESSAGE = chalk.red( export const QUEST_NOT_FOUND_MESSAGE = chalk.yellow( "\nQuest not found. Did you enter the correct name?\n" + - "Check the name again or run `quest update` to fetch the latest directory of released quests.\n" + "Check the name again to be sure.\n" +); + +// TODO: Add link to FAQ page +export const CREDENTIALS_NOT_FOUND_MESSAGE = chalk.red( + "\nGithub token not found." ); export const BAD_TOKEN_MESSAGE = chalk.red( @@ -49,25 +54,33 @@ export const QUEST_ALREADY_EXISTS_MESSAGE = chalk.yellow( export const UNCOMMITTED_FILES_MESSAGE = chalk.yellow( "Uncommitted files detected. Questplay is unable to push to remote.\n" + - "Try committing or stashing all changes with git first.\n" + "Try committing or stashing all changes with git first.\n" ); export const UNCOMMITTED_FILES_BEFORE_UPDATE_MESSAGE = chalk.yellow( "Questplay is unable to update when there are still uncommitted files in the repository.\n" + - "Try committing or stashing all changes with git first.\n" + "Try committing or stashing all changes with git first.\n" ); export const UNCOMMITTED_FILES_BEFORE_DOWNLOAD_MESSAGE = chalk.yellow( "Questplay is unable to download a quest when there are still uncommitted files in the repository.\n" + - "Try committing or stashing all changes with git first.\n" + "Try committing or stashing all changes with git first.\n" ); export const UPDATE_REMINDER_MESSAGE = chalk.yellow( chalk.bold("\nIMPORTANT:"), "New version of Questplay available.\n" + - "Run `quest update` to download update.\n" + "Run `quest update` to download update.\n" ); +export function BreakingChangeMessage(questName) { + return ( + chalk.yellow("Deprecated version of quest detected in local repo. New version introduces breaking changes.\n" + + `Run "quest find ${questName}" to download breaking update.\n`) + + chalk.bgBlackBright("Any of your existing work will be overwritten\n") + ); +} + export function NoUpstreamBranchMessage(branchName) { return ( chalk.red(`Current branch : ${branchName} doesn't exist in remote\n`) + @@ -149,10 +162,10 @@ export const FOUNDRY_NOT_SUPPORTED_MESSAGE = chalk.yellow( ); export function INSTALL_SCARB_MESSAGE(remoteVersion) { - return chalk.yellow( - `Cairo quests require the installation of Scarb ${remoteVersion}. To install Scarb, check out`, - chalk.underline("https://docs.swmansion.com/scarb/\n") - ); + return chalk.yellow( + `Cairo quests require the installation of Scarb ${remoteVersion}. To install Scarb, check out`, + chalk.underline("https://docs.swmansion.com/scarb/\n") + ); } export function MISMATCH_SCARB_MESSAGE(remoteVersion) { @@ -160,13 +173,13 @@ export function MISMATCH_SCARB_MESSAGE(remoteVersion) { } export function UPDATE_CAIRO_MESSAGE(localVersion, remoteVersion) { - let changes = `${chalk.bgRed(chalk.strikethrough(localVersion))} ${chalk.bgGreenBright(remoteVersion)}`; - return chalk.yellow(`We have migrated to cairo ${remoteVersion}! Migrate your quest by making the following changes to Scarb.toml:\n`) + let changes = `${chalk.bgRed(chalk.strikethrough(localVersion))} ${chalk.bgGreenBright(remoteVersion)}`; + return chalk.yellow(`We have migrated to cairo ${remoteVersion}! Migrate your quest by making the following changes to Scarb.toml:\n`) + chalk.grey(`\n cairo-version = ${changes}`) + chalk.grey(`\n starknet-version = ${changes} [if applicable]\n`) } -export const UPDATE_SCARB_VERSION_FAIL = +export const UPDATE_SCARB_VERSION_FAIL = chalk.yellow( "WARNING: Failed to detect Scarb version.\n", "Consider reporting this on Discord!\n", @@ -204,5 +217,5 @@ export const SUBMISSION_ERROR_BANNER = chalk.yellow( export const SUBMISSION_FAILED_BANNER = chalk.red( "\nERROR: We couldn't validate your submission due to the following error\n", "Please report this error on Discord: " + - chalk.underline("https://discord.gg/EyGQEEzmjx\n") + chalk.underline("https://discord.gg/EyGQEEzmjx\n") );