diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 2c6f376d..00000000 --- a/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[package.json] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 00000000..ef084380 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_SERVER_URL=http://localhost:8080 \ No newline at end of file diff --git a/.freeCodeCamp/client/components/block.tsx b/.freeCodeCamp/client/components/block.tsx deleted file mode 100644 index e13f4f02..00000000 --- a/.freeCodeCamp/client/components/block.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { SelectionProps } from './selection'; -import { ProjectI, Events } from '../types/index'; -import { Tag } from './tag'; -import { Checkmark } from './checkmark'; - -type BlockProps = { - sock: SelectionProps['sock']; -} & ProjectI; - -export const Block = ({ - id, - title, - description, - isIntegrated, - isPublic, - numberOfLessons, - currentLesson, - completedDate, - tags, - sock -}: BlockProps) => { - function selectProject() { - sock(Events.SELECT_PROJECT, { id }); - } - - let lessonsCompleted = 0; - if (completedDate) { - lessonsCompleted = numberOfLessons; - } else { - lessonsCompleted = - !isIntegrated && currentLesson === numberOfLessons - 1 - ? currentLesson + 1 - : currentLesson; - } - return ( -
  • - -
  • - ); -}; diff --git a/.freeCodeCamp/client/components/console.tsx b/.freeCodeCamp/client/components/console.tsx deleted file mode 100644 index 8c72a318..00000000 --- a/.freeCodeCamp/client/components/console.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { ConsoleError } from '../types'; - -export const Console = ({ cons }: { cons: ConsoleError[] }) => { - return ( - - ); -}; - -const ConsoleElement = ({ testText, testId, error }: ConsoleError) => { - const details = `${testId + 1} ${testText} - - ${error}`; - return ( -
    - ); -}; diff --git a/.freeCodeCamp/client/components/controls.tsx b/.freeCodeCamp/client/components/controls.tsx deleted file mode 100644 index 2d2242c5..00000000 --- a/.freeCodeCamp/client/components/controls.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useEffect, useState } from 'react'; -import { F, LoaderT, ProjectI, TestType } from '../types'; - -interface ControlsProps { - cancelTests: F; - runTests: F; - resetProject?: F; - isResetEnabled?: ProjectI['isResetEnabled']; - tests: TestType[]; - loader?: LoaderT; -} - -// Changes the Reset button background to a filling progress bar when the seed is running -function progressStyle(loader?: LoaderT) { - if (!loader) { - return {}; - } - - const { - isLoading, - progress: { total, count } - } = loader; - if (isLoading) { - return { - background: `linear-gradient(to right, #0065A9 ${ - (count / total) * 100 - }%, rgba(0,0,0,0) 0%)` - }; - } -} - -export const Controls = ({ - cancelTests, - runTests, - resetProject, - isResetEnabled, - tests, - loader -}: ControlsProps) => { - const [isTestsRunning, setIsTestsRunning] = useState(false); - - useEffect(() => { - if (tests.some(t => t.isLoading)) { - setIsTestsRunning(true); - } else { - setIsTestsRunning(false); - } - }, [tests]); - - function handleTests() { - if (isTestsRunning) { - cancelTests(); - } else { - runTests(); - } - } - - const resetDisabled = !isResetEnabled || loader?.isLoading; - - return ( -
    - - {resetProject && ( - - )} -
    - ); -}; diff --git a/.freeCodeCamp/client/components/header.tsx b/.freeCodeCamp/client/components/header.tsx deleted file mode 100644 index 12e9e933..00000000 --- a/.freeCodeCamp/client/components/header.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Events, FreeCodeCampConfigI, ProjectI } from '../types'; -import FreeCodeCampLogo from '../assets/fcc_primary_large'; -import { LanguageList } from './language-list'; - -interface HeaderProps { - updateProject: (project: ProjectI | null) => void; - freeCodeCampConfig: FreeCodeCampConfigI; - sock: (type: Events, data: {}) => void; -} -export const Header = ({ - sock, - updateProject, - freeCodeCampConfig -}: HeaderProps) => { - function returnToLanding() { - updateProject(null); - } - - const locales = freeCodeCampConfig?.curriculum?.locales - ? Object.keys(freeCodeCampConfig?.curriculum?.locales) - : []; - return ( -
    - - {locales.length > 1 ? : null} -
    - ); -}; diff --git a/.freeCodeCamp/client/components/heading.tsx b/.freeCodeCamp/client/components/heading.tsx deleted file mode 100644 index 45877a59..00000000 --- a/.freeCodeCamp/client/components/heading.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect, useState } from 'react'; -import { F } from '../types'; - -interface HeadingProps { - title: string; - lessonNumber?: number; - numberOfLessons?: number; - goToNextLesson?: F; - goToPreviousLesson?: F; -} - -export const Heading = ({ - title, - lessonNumber, - numberOfLessons, - goToNextLesson, - goToPreviousLesson -}: HeadingProps) => { - const [anim, setAnim] = useState(''); - - useEffect(() => { - setAnim('fade-in'); - setTimeout(() => setAnim(''), 1000); - }, [lessonNumber]); - - const lessonNumberExists = typeof lessonNumber !== 'undefined'; - const canGoBack = lessonNumberExists && lessonNumber > 0; - const canGoForward = - lessonNumberExists && numberOfLessons && lessonNumber < numberOfLessons - 1; - - const h1 = title + (lessonNumberExists ? ' - Lesson ' + lessonNumber : ''); - return ( - - ); -}; diff --git a/.freeCodeCamp/client/components/output.tsx b/.freeCodeCamp/client/components/output.tsx deleted file mode 100644 index 9b49cd7c..00000000 --- a/.freeCodeCamp/client/components/output.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { useState } from 'react'; -import { ConsoleError, TestType } from '../types'; -import { Tests } from './tests'; -import { Console } from './console'; -import { Hints } from './hints'; - -interface OutputProps { - hints: string[]; - tests: TestType[]; - cons: ConsoleError[]; -} - -export const Output = ({ hints, tests, cons }: OutputProps) => { - const [selectedBtn, setSelectedBtn] = useState('tests'); - - return ( -
    -
      -
    • - -
    • -
    • - -
    • - {hints.length ? ( -
    • - -
    • - ) : null} -
    - -
    - {(() => { - switch (selectedBtn) { - case 'tests': - return ; - case 'console': - return ; - case 'hints': - return ; - default: - return
    No content
    ; - } - })()} -
    -
    - ); -}; diff --git a/.freeCodeCamp/client/components/selection.tsx b/.freeCodeCamp/client/components/selection.tsx deleted file mode 100644 index be8f76de..00000000 --- a/.freeCodeCamp/client/components/selection.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Events, ProjectI } from '../types'; -import { Block } from './block'; - -export interface SelectionProps { - sock: (type: Events, data: {}) => void; - projects: ProjectI[]; -} -export const Selection = ({ sock, projects }: SelectionProps) => { - return ( -
      - {projects.map((p, i) => { - return ; - })} -
    - ); -}; diff --git a/.freeCodeCamp/client/components/test.tsx b/.freeCodeCamp/client/components/test.tsx deleted file mode 100644 index b8b3ecf9..00000000 --- a/.freeCodeCamp/client/components/test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Loader } from './loader'; -import { TestType } from '../types'; - -export const Test = ({ testText, passed, isLoading, testId }: TestType) => { - return ( -
  • - - {testId + 1}) {isLoading ? : passed ? '✓' : '✗'}{' '} - -
    -
  • - ); -}; diff --git a/.freeCodeCamp/client/components/tests.tsx b/.freeCodeCamp/client/components/tests.tsx deleted file mode 100644 index 99b1b01f..00000000 --- a/.freeCodeCamp/client/components/tests.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { TestType } from '../types'; -import { Test } from './test'; - -interface TestsProps { - tests: TestType[]; -} - -export const Tests = ({ tests }: TestsProps) => { - return ( -
      - {tests.map(({ testText, passed, isLoading, testId }, i) => ( - - ))} -
    - ); -}; diff --git a/.freeCodeCamp/client/index.tsx b/.freeCodeCamp/client/index.tsx deleted file mode 100644 index 801146eb..00000000 --- a/.freeCodeCamp/client/index.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { createRoot } from 'react-dom/client'; -import { Suspense, useState, useEffect } from 'react'; -import { - ConsoleError, - Events, - FreeCodeCampConfigI, - LoaderT, - ProjectI, - TestType -} from './types/index'; -import { Loader } from './components/loader'; -import { Landing } from './templates/landing'; -import { Project } from './templates/project'; -import { parse } from './utils/index'; -import { Header } from './components/header'; -import './styles.css'; -import { E44o5 } from './components/error'; - -// Dynamically construct the socket url based on `window.location` -let socket = new WebSocket( - `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${ - window.location.host - }` -); - -const App = () => { - const [projects, setProjects] = useState([]); - const [freeCodeCampConfig, setFreeCodeCampConfig] = - useState({}); - const [project, setProject] = useState(null); - - const [lessonNumber, setLessonNumber] = useState(1); - const [description, setDescription] = useState(''); - const [locale, setLocale] = useState('english'); - const [tests, setTests] = useState([]); - const [hints, setHints] = useState([]); - const [cons, setCons] = useState([]); - const [loader, setLoader] = useState({ - isLoading: false, - progress: { count: 0, total: 1 } - }); - const [alertCamper, setAlertCamper] = useState(null); - const [error, setError] = useState(null); - - const [debouncers, setDebouncers] = useState([]); - const [connected, setConnected] = useState(false); - - function connectToWebSocket() { - socket.onopen = function (_event) { - setConnected(true); - setAlertCamper(null); - sock(Events.CONNECT); - }; - socket.onmessage = function (event) { - const parsedData: { event: keyof typeof handle; data: any } = parse( - event.data - ); - handle[parsedData.event]?.(parsedData.data); - }; - socket.onclose = function (_event) { - setAlertCamper('Client has disconnected from local server'); - setConnected(false); - // Try to reconnect - setTimeout(() => { - socket = new WebSocket( - `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${ - window.location.host - }` - ); - connectToWebSocket(); - }, 1000); - }; - - return () => { - console.log('socket closing'); - socket.close(); - }; - } - - useEffect(connectToWebSocket, []); - - const handle = { - 'handle-project-finish': handleProjectFinish, - 'update-loader': updateLoader, - 'update-test': updateTest, - 'update-tests': updateTests, - 'update-hints': updateHints, - 'update-console': updateConsole, - 'update-description': updateDescription, - 'update-project-heading': updateProjectHeading, - 'update-project': setProject, - 'update-projects': setProjects, - 'update-freeCodeCamp-config': setFreeCodeCampConfig, - 'update-error': updateError, - 'reset-tests': resetTests, - 'update-locale': setLocale, - RESPONSE: debounce - }; - - function handleProjectFinish() { - // Send Camper to landing page - updateProject(null); - } - - useEffect(() => { - if (connected) { - sock(Events.REQUEST_DATA, { request: 'projects' }); - } - }, [project]); - - function debounce({ event }: { event: string }) { - const debouncerRemoved = debouncers.filter(d => d !== event); - setDebouncers(() => debouncerRemoved); - } - - function sock(type: Events, data = {}) { - if (debouncers.includes(type)) { - return; - } - const newDebouncers = [...debouncers, type]; - setDebouncers(() => newDebouncers); - socket.send(parse({ event: type, data })); - } - - function updateProject(project: ProjectI | null) { - sock(Events.SELECT_PROJECT, { id: project?.id }); - resetState(); - setProject(project); - } - - function updateProjectHeading({ lessonNumber }: { lessonNumber: number }) { - setLessonNumber(lessonNumber); - } - - function updateDescription({ description }: { description: string }) { - setDescription(description); - } - - function updateTests({ tests }: { tests: TestType[] }) { - setTests(tests); - } - function updateTest({ test }: { test: TestType }) { - setTests(ts => ts.map(t => (t.testId === test.testId ? test : t))); - } - function updateHints({ hints }: { hints: string[] }) { - setHints(hints); - } - - function updateConsole({ cons }: { cons: ConsoleError }) { - if (!Object.keys(cons).length) { - return setCons([]); - } - // Insert cons in array at index `id` - setCons(prev => { - const sorted = [ - ...prev.slice(0, cons.testId), - cons, - ...prev.slice(cons.testId) - ].filter(Boolean); - return sorted; - }); - } - - function updateError({ error }: { error: Error }) { - setError(error); - } - - function updateLoader({ loader }: { loader: LoaderT }) { - setLoader(loader); - } - - function resetTests() { - setTests([]); - } - - function resetState() { - setTests([]); - setHints([]); - setCons([]); - } - - function toggleLoaderAnimation({ loader }: { loader: LoaderT }) { - setLoader(loader); - } - - function runTests() { - setCons([]); - sock(Events.RUN_TESTS); - } - function resetProject() { - sock(Events.RESET_PROJECT); - } - function goToNextLesson() { - sock(Events.GO_TO_NEXT_LESSON); - } - function goToPreviousLesson() { - sock(Events.GO_TO_PREVIOUS_LESSON); - } - - function cancelTests() { - sock(Events.CANCEL_TESTS); - } - - if (alertCamper) { - return ( - <> -
    - - - ); - } - - return ( - <> - }> -
    - {project ? ( - - ) : ( - - )} - - - ); -}; - -const container = document.getElementById('root'); -if (!container) throw Error('Element #root not found to mount to'); -const root = createRoot(container); -root.render(); diff --git a/.freeCodeCamp/client/templates/landing.tsx b/.freeCodeCamp/client/templates/landing.tsx deleted file mode 100644 index 78c25591..00000000 --- a/.freeCodeCamp/client/templates/landing.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Selection } from '../components/selection'; -import { Events, FreeCodeCampConfigI, ProjectI } from '../types'; -import './landing.css'; - -interface LandingProps { - sock: (type: Events, data: {}) => void; - projects: ProjectI[]; - freeCodeCampConfig: FreeCodeCampConfigI; - locale: string; -} - -export const Landing = ({ - sock, - projects, - freeCodeCampConfig, - locale -}: LandingProps) => { - const title = freeCodeCampConfig.client?.landing?.[locale]?.title; - return ( - <> - {title &&

    {title}

    } -

    - {freeCodeCampConfig.client?.landing?.[locale]?.description} -

    - - {freeCodeCampConfig.client?.landing?.[locale]?.['faq-text']} - - - - ); -}; diff --git a/.freeCodeCamp/client/templates/project.tsx b/.freeCodeCamp/client/templates/project.tsx deleted file mode 100644 index 331fae6d..00000000 --- a/.freeCodeCamp/client/templates/project.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Description } from '../components/description'; -import { Heading } from '../components/heading'; -import { ConsoleError, F, LoaderT, ProjectI, TestType } from '../types'; -import { Controls } from '../components/controls'; -import { Output } from '../components/output'; -import './project.css'; - -export interface ProjectProps { - cancelTests: F; - goToNextLesson: F; - goToPreviousLesson: F; - resetProject: F; - runTests: F; - cons: ConsoleError[]; - description: string; - hints: string[]; - loader: LoaderT; - lessonNumber: number; - project: ProjectI; - tests: TestType[]; -} - -export const Project = ({ - cancelTests, - runTests, - resetProject, - goToNextLesson, - goToPreviousLesson, - loader, - project, - lessonNumber, - description, - tests, - hints, - cons -}: ProjectProps) => { - return ( - <> -
    - - - - - - - -
    - - ); -}; diff --git a/.freeCodeCamp/client/types/index.ts b/.freeCodeCamp/client/types/index.ts deleted file mode 100644 index 2ad7728a..00000000 --- a/.freeCodeCamp/client/types/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -export type F = (arg: A) => R; - -export enum Events { - CONNECT = 'connect', - DISCONNECT = 'disconnect', - TOGGLE_LOADER_ANIMATION = 'toggle-loader-animation', - UPDATE_TESTS = 'update-tests', - UPDATE_TEST = 'update-test', - UPDATE_DESCRIPTION = 'update-description', - UPDATE_PROJECT_HEADING = 'update-project-heading', - UPDATE_PROJECTS = 'update-projects', - RESET_TESTS = 'reset-tests', - RUN_TESTS = 'run-tests', - RESET_PROJECT = 'reset-project', - REQUEST_DATA = 'request-data', - GO_TO_NEXT_LESSON = 'go-to-next-lesson', - GO_TO_PREVIOUS_LESSON = 'go-to-previous-lesson', - SELECT_PROJECT = 'select-project', - CANCEL_TESTS = 'cancel-tests', - CHANGE_LANGUAGE = 'change-language' -} - -export type TestType = { - testText: string; - passed: boolean; - isLoading: boolean; - testId: number; -}; - -export type LoaderT = { - isLoading: boolean; - progress: { - total: number; - count: number; - }; -}; - -export interface ProjectI { - id: number; - title: string; - description: string; - isIntegrated: boolean; - isPublic: boolean; - currentLesson: number; - numberOfLessons: number; - isResetEnabled?: boolean; - completedDate: null | number; - tags: string[]; -} - -export type ConsoleError = { - error: string; -} & TestType; - -export type FreeCodeCampConfigI = { - [key: string]: any; -}; diff --git a/.freeCodeCamp/client/utils/index.ts b/.freeCodeCamp/client/utils/index.ts deleted file mode 100644 index e0032f49..00000000 --- a/.freeCodeCamp/client/utils/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { marked } from 'marked'; -import { markedHighlight } from 'marked-highlight'; -import Prism from 'prismjs'; - -marked.use( - markedHighlight({ - highlight: (code, lang: keyof (typeof Prism)['languages']) => { - if (Prism.languages[lang]) { - return Prism.highlight(code, Prism.languages[lang], String(lang)); - } else { - return code; - } - } - }) -); - -function parseMarkdown(markdown: string) { - return marked.parse(markdown, { gfm: true }); -} - -export function parse(objOrString: any) { - if (typeof objOrString === 'string') { - return JSON.parse(objOrString); - } else { - return JSON.stringify(objOrString); - } -} diff --git a/.freeCodeCamp/plugin/index.js b/.freeCodeCamp/plugin/index.js deleted file mode 100644 index 4f08ad30..00000000 --- a/.freeCodeCamp/plugin/index.js +++ /dev/null @@ -1,162 +0,0 @@ -import { readFile } from 'fs/promises'; -import { freeCodeCampConfig, getState, ROOT } from '../tooling/env.js'; -import { CoffeeDown, parseMarkdown } from '../tooling/parser.js'; -import { join } from 'path'; -import { logover } from '../tooling/logger.js'; - -/** - * Project config from `config/projects.json` - * @typedef {Object} Project - * @property {string} id - * @property {string} title - * @property {string} dashedName - * @property {string} description - * @property {boolean} isIntegrated - * @property {boolean} isPublic - * @property {number} currentLesson - * @property {boolean} runTestsOnWatch - * @property {boolean} isResetEnabled - * @property {number} numberOfLessons - * @property {boolean} seedEveryLesson - * @property {boolean} blockingTests - * @property {boolean} breakOnFailure - */ - -/** - * @typedef {Object} TestsState - * @property {boolean} passed - * @property {string} testText - * @property {number} testId - * @property {boolean} isLoading - */ - -/** - * @typedef {Object} Lesson - * @property {{watch?: string[]; ignore?: string[]} | undefined} meta - * @property {string} description - * @property {[[string, string]]} tests - * @property {string[]} hints - * @property {[{filePath: string; fileSeed: string} | string]} seed - * @property {boolean?} isForce - * @property {string?} beforeAll - * @property {string?} afterAll - * @property {string?} beforeEach - * @property {string?} afterEach - */ - -export const pluginEvents = { - /** - * @param {Project} project - * @param {TestsState[]} testsState - */ - onTestsStart: async (project, testsState) => {}, - - /** - * @param {Project} project - * @param {TestsState[]} testsState - */ - onTestsEnd: async (project, testsState) => {}, - - /** - * @param {Project} project - */ - onProjectStart: async project => {}, - - /** - * @param {Project} project - */ - onProjectFinished: async project => {}, - - /** - * @param {Project} project - */ - onLessonPassed: async project => {}, - - /** - * @param {Project} project - */ - onLessonFailed: async project => {}, - - /** - * @param {Project} project - */ - onLessonLoad: async project => {}, - - /** - * @param {string} projectDashedName - * @returns {Promise<{title: string; description: string; numberOfLessons: number; tags: string[]}>} - */ - getProjectMeta: async projectDashedName => { - const { locale } = await getState(); - const projectFilePath = join( - ROOT, - freeCodeCampConfig.curriculum.locales[locale], - projectDashedName + '.md' - ); - const projectFile = await readFile(projectFilePath, 'utf8'); - const coffeeDown = new CoffeeDown(projectFile); - const projectMeta = coffeeDown.getProjectMeta(); - // Remove `

    ` tags if present - const title = parseMarkdown(projectMeta.title) - .replace(/

    |<\/p>/g, '') - .trim(); - const description = parseMarkdown(projectMeta.description).trim(); - const tags = projectMeta.tags; - const numberOfLessons = projectMeta.numberOfLessons; - return { title, description, numberOfLessons, tags }; - }, - - /** - * @param {string} projectDashedName - * @param {number} lessonNumber - * @returns {Promise} lesson - */ - getLesson: async (projectDashedName, lessonNumber) => { - const { locale } = await getState(); - const projectFilePath = join( - ROOT, - freeCodeCampConfig.curriculum.locales[locale], - projectDashedName + '.md' - ); - const projectFile = await readFile(projectFilePath, 'utf8'); - const coffeeDown = new CoffeeDown(projectFile); - const lesson = coffeeDown.getLesson(lessonNumber); - let seed = lesson.seed; - if (!seed.length) { - // Check for external seed file - const seedFilePath = projectFilePath.replace(/.md$/, '-seed.md'); - try { - const seedContent = await readFile(seedFilePath, 'utf-8'); - const coffeeDown = new CoffeeDown(seedContent); - seed = coffeeDown.getLesson(lessonNumber).seed; - } catch (e) { - if (e?.code !== 'ENOENT') { - logover.debug(e); - throw new Error( - `Error reading external seed for lesson ${lessonNumber}` - ); - } - } - } - const { afterAll, afterEach, beforeAll, beforeEach, isForce, meta } = - lesson; - const description = parseMarkdown(lesson.description).trim(); - const tests = lesson.tests.map(([testText, test]) => [ - parseMarkdown(testText).trim(), - test - ]); - const hints = lesson.hints.map(h => parseMarkdown(h).trim()); - return { - meta, - description, - tests, - hints, - seed, - beforeAll, - afterAll, - beforeEach, - afterEach, - isForce - }; - } -}; diff --git a/.freeCodeCamp/tests/parser.test.js b/.freeCodeCamp/tests/parser.test.js deleted file mode 100644 index e674465b..00000000 --- a/.freeCodeCamp/tests/parser.test.js +++ /dev/null @@ -1,122 +0,0 @@ -/// Tests can be run from `self/` -/// node ../.freeCodeCamp/tests/parser.test.js -import { assert } from 'chai'; -import { Logger } from 'logover'; -import { pluginEvents } from '../plugin/index.js'; - -const logover = new Logger({ - debug: '\x1b[33m[parser.test]\x1b[0m', - error: '\x1b[31m[parser.test]\x1b[0m', - level: 'debug', - timestamp: null -}); - -try { - const { - title, - description: projectDescription, - numberOfLessons - } = await pluginEvents.getProjectMeta('build-x-using-y'); - const { - meta, - description: lessonDescription, - tests, - hints, - seed, - isForce, - beforeAll, - beforeEach, - afterAll, - afterEach - } = await pluginEvents.getLesson('build-x-using-y', 0); - - assert.deepEqual(title, 'Build X Using Y'); - assert.deepEqual(meta, { - watch: ['some/file.js'], - ignore: ['another/file.js'] - }); - assert.deepEqual( - projectDescription, - '

    In this course, you will build x using y.

    ' - ); - assert.deepEqual(numberOfLessons, 1); - - assert.deepEqual( - lessonDescription, - `

    Some description here.

    -
    fn main() {
    -    println!("Hello, world!");
    -}
    -

    Here is an image:

    -` - ); - - const expectedTests = [ - [ - '

    First test using Chai.js assert.

    ', - '// 0\n// Timeout for 3 seconds\nawait new Promise(resolve => setTimeout(resolve, 3000));\nassert.equal(true, true);' - ], - [ - '

    Second test using global variables passed from before hook.

    ', - "// 1\nawait new Promise(resolve => setTimeout(resolve, 4000));\nassert.equal(__projectLoc, 'example global variable for tests');" - ], - [ - '

    Dynamic helpers should be imported.

    ', - "// 2\nawait new Promise(resolve => setTimeout(resolve, 1000));\nassert.equal(__helpers.testDynamicHelper(), 'Helper success!');" - ] - ]; - - for (const [i, [testText, testCode]] of tests.entries()) { - assert.deepEqual(testText, expectedTests[i][0]); - assert.deepEqual(testCode, expectedTests[i][1]); - } - - const expectedHints = [ - '

    Inline hint with some code blocks.

    ', - `

    Multi-line hint with:

    -
    const code_block = true;
    -
    ` - ]; - - for (const [i, hint] of hints.entries()) { - assert.deepEqual(hint, expectedHints[i]); - } - - const expectedSeed = [ - { - filePath: 'build-x-using-y/readme.md', - fileSeed: '# Build X Using Y\n\nIn this course\n\n## 0\n\nHello' - }, - 'npm install' - ]; - - let i = 0; - for (const s of seed) { - assert.deepEqual(s, expectedSeed[i]); - i++; - } - assert.deepEqual(i, 2); - - assert.deepEqual(isForce, true); - - assert.deepEqual( - beforeEach, - "await new Promise(resolve => setTimeout(resolve, 1000));\nconst __projectLoc = 'example global variable for tests';" - ); - assert.deepEqual( - afterEach, - "await new Promise(resolve => setTimeout(resolve, 1000));\nlogover.info('after each');" - ); - assert.deepEqual( - beforeAll, - "await new Promise(resolve => setTimeout(resolve, 1000));\nlogover.info('before all');" - ); - assert.deepEqual( - afterAll, - "await new Promise(resolve => setTimeout(resolve, 1000));\nlogover.info('after all');" - ); -} catch (e) { - throw logover.error(e); -} - -logover.debug('All tests passed! 🎉'); diff --git a/.freeCodeCamp/tooling/client-socks.js b/.freeCodeCamp/tooling/client-socks.js deleted file mode 100644 index e44025fc..00000000 --- a/.freeCodeCamp/tooling/client-socks.js +++ /dev/null @@ -1,153 +0,0 @@ -import { parseMarkdown } from './parser.js'; - -export function updateLoader(ws, loader) { - ws.send(parse({ event: 'update-loader', data: { loader } })); -} - -/** - * Update all tests in the tests state - * @param {WebSocket} ws WebSocket connection to the client - * @param {Test[]} tests Array of Test objects - */ -export function updateTests(ws, tests) { - ws.send(parse({ event: 'update-tests', data: { tests } })); -} -/** - * Update single test in the tests state - * @param {WebSocket} ws WebSocket connection to the client - * @param {Test} test Test object - */ -export function updateTest(ws, test) { - ws.send(parse({ event: 'update-test', data: { test } })); -} -/** - * Update the lesson description - * @param {WebSocket} ws WebSocket connection to the client - * @param {string} description Lesson description - */ -export function updateDescription(ws, description) { - ws.send( - parse({ - event: 'update-description', - data: { description } - }) - ); -} -/** - * Update the heading of the lesson - * @param {WebSocket} ws WebSocket connection to the client - * @param {{lessonNumber: number; title: string;}} projectHeading Project heading - */ -export function updateProjectHeading(ws, projectHeading) { - ws.send( - parse({ - event: 'update-project-heading', - data: projectHeading - }) - ); -} -/** - * Update the project state - * @param {WebSocket} ws WebSocket connection to the client - * @param {Project} project Project object - */ -export function updateProject(ws, project) { - ws.send( - parse({ - event: 'update-project', - data: project - }) - ); -} -/** - * Update the projects state - * @param {WebSocket} ws WebSocket connection to the client - * @param {Project[]} projects Array of Project objects - */ -export function updateProjects(ws, projects) { - ws.send( - parse({ - event: 'update-projects', - data: projects - }) - ); -} -/** - * Update the projects state - * @param {WebSocket} ws WebSocket connection to the client - * @param {any} config config object - */ -export function updateFreeCodeCampConfig(ws, config) { - ws.send( - parse({ - event: 'update-freeCodeCamp-config', - data: config - }) - ); -} -/** - * Update hints - * @param {WebSocket} ws WebSocket connection to the client - * @param {string[]} hints Markdown strings - */ -export function updateHints(ws, hints) { - ws.send(parse({ event: 'update-hints', data: { hints } })); -} -/** - * - * @param {WebSocket} ws WebSocket connection to the client - * @param {{error: string; testText: string; passed: boolean;isLoading: boolean;testId: number;}} cons - */ -export function updateConsole(ws, cons) { - if (Object.keys(cons).length) { - if (cons.error) { - const error = `\`\`\`json\n${JSON.stringify( - cons.error, - null, - 2 - )}\n\`\`\``; - cons.error = parseMarkdown(error); - } - } - ws.send(parse({ event: 'update-console', data: { cons } })); -} - -/** - * Update error - * @param {WebSocket} ws WebSocket connection to the client - * @param {Error} error Error object - */ -export function updateError(ws, error) { - ws.send(parse({ event: 'update-error', data: { error } })); -} - -/** - * Update the current locale - * @param {WebSocket} ws WebSocket connection to the client - * @param {string} locale Locale string - */ -export function updateLocale(ws, locale) { - ws.send(parse({ event: 'update-locale', data: locale })); -} - -/** - * Handles the case when a project is finished - * @param {WebSocket} ws WebSocket connection to the client - */ -export function handleProjectFinish(ws) { - ws.send(parse({ event: 'handle-project-finish' })); -} - -export function parse(obj) { - return JSON.stringify(obj); -} - -/** - * Resets the bottom panel (Tests, Console, Hints) of the client to empty state - * @param {WebSocket} ws WebSocket connection to the client - */ -export function resetBottomPanel(ws) { - updateHints(ws, []); - updateTests(ws, []); - updateConsole(ws, {}); -} diff --git a/.freeCodeCamp/tooling/env.js b/.freeCodeCamp/tooling/env.js deleted file mode 100644 index c71aaf89..00000000 --- a/.freeCodeCamp/tooling/env.js +++ /dev/null @@ -1,131 +0,0 @@ -import { readFile, writeFile } from 'fs/promises'; -import { join } from 'path'; -import { logover } from './logger.js'; -import { pluginEvents } from '../plugin/index.js'; - -export const ROOT = process.env.INIT_CWD || process.cwd(); - -export async function getConfig() { - const config = await readFile(join(ROOT, 'freecodecamp.conf.json'), 'utf-8'); - const conf = JSON.parse(config); - const defaultConfig = { - curriculum: { - locales: { - english: 'curriculum/locales/english' - } - }, - config: { - 'projects.json': 'config/projects.json', - 'state.json': 'config/state.json' - } - }; - return { ...defaultConfig, ...conf }; -} - -export const freeCodeCampConfig = await getConfig(); - -export async function getState() { - let defaultState = { - currentProject: null, - locale: 'english', - lastSeed: { - projectDashedName: null, - // All lessons start at 0, but the logic for whether to seed a lesson - // or not is based on the current lesson matching the last seeded lesson - // So, to ensure the first lesson is seeded, this is -1 - lessonNumber: -1 - }, - // All lessons start at 0, but the logic for whether to run certain effects - // is based on the current lesson matching the last lesson - lastWatchChange: -1 - }; - try { - const state = JSON.parse( - await readFile( - join(ROOT, freeCodeCampConfig.config['state.json']), - 'utf-8' - ) - ); - return { ...defaultState, ...state }; - } catch (err) { - logover.error(err); - } - return defaultState; -} - -export async function setState(obj) { - const state = await getState(); - const updatedState = { - ...state, - ...obj - }; - - await writeFile( - join(ROOT, freeCodeCampConfig.config['state.json']), - JSON.stringify(updatedState, null, 2) - ); -} - -/** - * @param {string} projectDashedName Project dashed name - */ -export async function getProjectConfig(projectDashedName) { - const projects = JSON.parse( - await readFile( - join(ROOT, freeCodeCampConfig.config['projects.json']), - 'utf-8' - ) - ); - - const project = projects.find(p => p.dashedName === projectDashedName); - - // Add title and description to project - const { title, description } = await pluginEvents.getProjectMeta( - projectDashedName - ); - project.title = title; - project.description = description; - - const defaultConfig = { - testPollingRate: 333, - currentLesson: 0, - runTestsOnWatch: false, - lastKnownLessonWithHash: 0, - seedEveryLesson: false, - blockingTests: false, - breakOnFailure: false, - useGitBuildOnProduction: false // TODO: Necessary? - }; - if (!project) { - return defaultConfig; - } - return { ...defaultConfig, ...project }; -} - -/** - * - * @param {string} projectDashedName Project dashed name - * @param {object} config Config properties to set - */ -export async function setProjectConfig(projectDashedName, config = {}) { - const projects = JSON.parse( - await readFile( - join(ROOT, freeCodeCampConfig.config['projects.json']), - 'utf-8' - ) - ); - - const updatedProject = { - ...projects.find(p => p.dashedName === projectDashedName), - ...config - }; - - const updatedProjects = projects.map(p => - p.dashedName === projectDashedName ? updatedProject : p - ); - - await writeFile( - join(ROOT, freeCodeCampConfig.config['projects.json']), - JSON.stringify(updatedProjects, null, 2) - ); -} diff --git a/.freeCodeCamp/tooling/logger.js b/.freeCodeCamp/tooling/logger.js deleted file mode 100644 index f0670da0..00000000 --- a/.freeCodeCamp/tooling/logger.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Logger } from 'logover'; - -export const logover = new Logger({ - level: process.env.NODE_ENV === 'development' ? 'debug' : 'info' -}); diff --git a/.freeCodeCamp/tooling/parser.js b/.freeCodeCamp/tooling/parser.js deleted file mode 100644 index 3501599e..00000000 --- a/.freeCodeCamp/tooling/parser.js +++ /dev/null @@ -1,391 +0,0 @@ -import { lexer } from 'marked'; - -/** - * A class that takes a Markdown string, uses the markedjs package to tokenize it, and provides convenience methods to access different tokens in the token tree - */ -export class CoffeeDown { - constructor(tokensOrMarkdown, caller = null) { - this.caller = caller; - if (typeof tokensOrMarkdown == 'string') { - this.tokens = lexer(tokensOrMarkdown); - } else if (Array.isArray(tokensOrMarkdown)) { - this.tokens = tokensOrMarkdown; - } else { - this.tokens = [tokensOrMarkdown]; - } - } - - getProjectMeta() { - // There should only be one H1 in the project which is the title - const title = this.tokens.find( - t => t.type === 'heading' && t.depth === 1 - ).text; - - const firstLessonMarker = this.tokens.findIndex(t => { - return ( - t.type === 'heading' && - t.depth === 2 && - Number.isInteger(parseFloat(t.text)) - ); - }); - const tokensBeforeFirstLesson = this.tokens.slice(0, firstLessonMarker); - - // The first paragraph before the lesson marker should be the description - const description = tokensBeforeFirstLesson.find( - t => t.type === 'paragraph' - ).text; - - // First codeblock before the lesson marker is extra meta within a JSON codeblock - const jsonMeta = - tokensBeforeFirstLesson.find(t => t.type === 'code' && t.lang === 'json') - ?.text ?? '{}'; - const meta = JSON.parse(jsonMeta); - - // All H2 elements with an integer for text are lesson headings - const numberOfLessons = this.tokens.filter( - t => - t.type === 'heading' && - t.depth === 2 && - Number.isInteger(parseFloat(t.text)) - ).length; - return { title, description, numberOfLessons, ...meta }; - } - - getHeading(depth, text, caller) { - if (this.caller !== 'getLesson') { - throw new Error( - `${caller} must be called on getLesson. Called on ${this.caller}` - ); - } - const tokens = []; - let take = false; - for (const token of this.tokens) { - if ( - token.type === 'heading' && - token.depth <= depth && - TOKENS.some(t => t.marker === token.text) - ) { - take = false; - } - if (take) { - tokens.push(token); - } - if ( - token.type === 'heading' && - token.depth === depth && - token.text === text - ) { - take = true; - } - } - return new CoffeeDown(tokens, caller); - } - - getLesson(lessonNumber) { - const lesson = this.#getLesson(lessonNumber); - const description = lesson.getDescription().markdown; - const tests = lesson.getTests().tests; - const seed = lesson.getSeed().seed; - const isForce = lesson - .getSeed() - .tokens.some( - t => t.type === 'heading' && t.depth === 4 && t.text === '--force--' - ); - const hints = lesson.getHints().hints; - const beforeAll = lesson.getBeforeAll().code; - const afterAll = lesson.getAfterAll().code; - const beforeEach = lesson.getBeforeEach().code; - const afterEach = lesson.getAfterEach().code; - - const meta = lesson.getMeta(); - return { - meta, - description, - tests, - seed, - hints, - beforeAll, - afterAll, - beforeEach, - afterEach, - isForce - }; - } - - #getLesson(lessonNumber) { - const tokens = []; - let take = false; - for (const token of this.tokens) { - if ( - token.type === 'heading' && - token.depth === 2 && - (parseInt(token.text, 10) === lessonNumber + 1 || - token.text === '--fcc-end--') - ) { - take = false; - } - if (take) { - tokens.push(token); - } - if (token.type === 'heading' && token.depth === 2) { - if (parseInt(token.text, 10) === lessonNumber) { - take = true; - } - } - } - return new CoffeeDown(tokens, 'getLesson'); - } - - getDescription() { - return this.getHeading(3, '--description--', 'getDescription'); - } - - getTests() { - return this.getHeading(3, '--tests--', 'getTests'); - } - - getSeed() { - return this.getHeading(3, '--seed--', 'getSeed'); - } - - getHints() { - return this.getHeading(3, '--hints--', 'getHints'); - } - - getBeforeAll() { - return this.getHeading(3, '--before-all--', 'getBeforeAll'); - } - - getAfterAll() { - return this.getHeading(3, '--after-all--', 'getAfterAll'); - } - - getBeforeEach() { - return this.getHeading(3, '--before-each--', 'getBeforeEach'); - } - - getAfterEach() { - return this.getHeading(3, '--after-each--', 'getAfterEach'); - } - - getMeta() { - const firstHeadingMarker = this.tokens.findIndex(t => { - return t.type === 'heading' && t.depth === 3; - }); - const tokensBeforeFirstHeading = this.tokens.slice(0, firstHeadingMarker); - const jsonMeta = - tokensBeforeFirstHeading.find(t => t.type === 'code' && t.lang === 'json') - ?.text ?? '{}'; - return JSON.parse(jsonMeta); - } - - /** - * Get first code block text from tokens - * - * Meant to be used with `getBeforeAll`, `getAfterAll`, `getBeforeEach`, and `getAfterEach` - */ - get code() { - const callers = [ - 'getBeforeAll', - 'getAfterAll', - 'getBeforeEach', - 'getAfterEach' - ]; - if (!callers.includes(this.caller)) { - throw new Error( - `code must be called on "${callers.join(', ')}". Called on ${ - this.caller - }` - ); - } - return this.tokens.find(t => t.type === 'code')?.text; - } - - get seed() { - if (this.caller !== 'getSeed') { - throw new Error( - `seedToIterator must be called on getSeed. Called on ${this.caller}` - ); - } - return seedToIterator(this.tokens); - } - - get tests() { - if (this.caller !== 'getTests') { - throw new Error( - `textsAndTests must be called on getTests. Called on ${this.caller}` - ); - } - const textTokens = []; - const testTokens = []; - for (const token of this.tokens) { - if (token.type === 'paragraph') { - textTokens.push(token); - } - if (token.type === 'code') { - testTokens.push(token); - } - } - const texts = textTokens.map(t => t.text); - const tests = testTokens.map(t => t.text); - return texts.map((text, i) => [text, tests[i]]); - } - - get hints() { - if (this.caller !== 'getHints') { - throw new Error( - `hints must be called on getHints. Called on ${this.caller}` - ); - } - const hintTokens = [[]]; - let currentHint = 0; - for (const token of this.tokens) { - if (token.type === 'heading' && token.depth === 4) { - if (token.text != currentHint) { - currentHint = token.text; - hintTokens[currentHint] = []; - } - } else { - hintTokens[currentHint].push(token); - } - } - const hints = hintTokens - .map(t => t.map(t => t.raw).join('')) - .filter(Boolean); - return hints; - } - - get markdown() { - return this.tokens.map(t => t.raw).join(''); - } - - get text() { - return this.tokens.map(t => t.text).join(''); - } -} - -function seedToIterator(tokens) { - const seed = []; - const sectionTokens = {}; - let currentSection = 0; - for (const token of tokens) { - if ( - token.type === 'heading' && - token.depth === 4 && - token.text !== '--force--' - ) { - if (token.text !== currentSection) { - currentSection = token.text; - sectionTokens[currentSection] = {}; - } - } else if (token.type === 'code') { - sectionTokens[currentSection] = token; - } - } - for (const [filePath, { text }] of Object.entries(sectionTokens)) { - if (filePath === '--cmd--') { - seed.push(text); - } else { - seed.push({ - filePath: filePath.slice(3, filePath.length - 3), - fileSeed: text - }); - } - } - return seed; -} - -import { marked } from 'marked'; -import { markedHighlight } from 'marked-highlight'; -import Prism from 'prismjs'; -import loadLanguages from 'prismjs/components/index.js'; - -loadLanguages([ - 'javascript', - 'css', - 'html', - 'json', - 'markdown', - 'sql', - 'rust', - 'typescript', - 'jsx', - 'c', - 'csharp', - 'cpp', - 'dotnet', - 'python', - 'pug', - 'handlebars' -]); - -marked.use( - markedHighlight({ - highlight: (code, lang) => { - if (Prism.languages[lang]) { - return Prism.highlight(code, Prism.languages[lang], String(lang)); - } else { - return code; - } - } - }) -); - -export function parseMarkdown(markdown) { - return marked.parse(markdown, { gfm: true }); -} - -const TOKENS = [ - { - marker: /\d+/, - depth: 2 - }, - { - marker: '--fcc-end--', - depth: 2 - }, - { - marker: '--description--', - depth: 3 - }, - { - marker: '--tests--', - depth: 3 - }, - { - marker: '--seed--', - depth: 3 - }, - { - marker: '--hints--', - depth: 3 - }, - { - marker: '--before-all--', - depth: 3 - }, - { - marker: '--after-all--', - depth: 3 - }, - { - marker: '--before-each--', - depth: 3 - }, - { - marker: '--after-each--', - depth: 3 - }, - { - marker: '--cmd--', - depth: 4 - }, - { - marker: /(?<=--)[^"]+(?="--)/, - depth: 4 - }, - { - marker: '--force--', - depth: 4 - } -]; diff --git a/.freeCodeCamp/tooling/reset.js b/.freeCodeCamp/tooling/reset.js deleted file mode 100644 index d9a0374c..00000000 --- a/.freeCodeCamp/tooling/reset.js +++ /dev/null @@ -1,61 +0,0 @@ -// Handles all the resetting of the projects -import { resetBottomPanel, updateError, updateLoader } from './client-socks.js'; -import { getProjectConfig, getState } from './env.js'; -import { logover } from './logger.js'; -import { runCommand, runLessonSeed } from './seed.js'; -import { pluginEvents } from '../plugin/index.js'; - -/** - * Resets the current project by running, in order, every seed - * @param {WebSocket} ws - */ -export async function resetProject(ws) { - resetBottomPanel(ws); - // Get commands and handle file setting - const { currentProject } = await getState(); - const project = await getProjectConfig(currentProject); - const { currentLesson } = project; - updateLoader(ws, { - isLoading: true, - progress: { total: currentLesson, count: 0 } - }); - - let lessonNumber = 0; - try { - await gitResetCurrentProjectDir(); - while (lessonNumber <= currentLesson) { - const { seed } = await pluginEvents.getLesson( - currentProject, - lessonNumber - ); - if (seed) { - await runLessonSeed(seed, lessonNumber); - } - lessonNumber++; - updateLoader(ws, { - isLoading: true, - progress: { total: currentLesson, count: lessonNumber } - }); - } - } catch (err) { - updateError(ws, err); - logover.error(err); - } - updateLoader(ws, { - isLoading: false, - progress: { total: 1, count: 1 } - }); -} - -async function gitResetCurrentProjectDir() { - const { currentProject } = await getState(); - const project = await getProjectConfig(currentProject); - try { - logover.debug(`Cleaning '${project.dashedName}'`); - const { stdout, stderr } = await runCommand( - `git clean -f -q -- ${project.dashedName}` - ); - } catch (e) { - logover.error(e); - } -} diff --git a/.freeCodeCamp/tooling/server.js b/.freeCodeCamp/tooling/server.js deleted file mode 100644 index 43da20f4..00000000 --- a/.freeCodeCamp/tooling/server.js +++ /dev/null @@ -1,300 +0,0 @@ -import express from 'express'; -import { readFile } from 'fs/promises'; -import { getWorkerPool, runTests } from './tests/main.js'; -import { - getProjectConfig, - getState, - ROOT, - setProjectConfig, - setState, - getConfig -} from './env.js'; - -import { WebSocketServer } from 'ws'; -import { runLesson } from './lesson.js'; -import { - updateProjects, - updateFreeCodeCampConfig, - updateLocale -} from './client-socks.js'; -import { hotReload } from './hot-reload.js'; -import { hideAll, showFile, showAll } from './utils.js'; -import { join } from 'path'; -import { logover } from './logger.js'; -import { resetProject } from './reset.js'; -import { validateCurriculum } from './validate.js'; -import { pluginEvents } from '../plugin/index.js'; - -const freeCodeCampConfig = await getConfig(); - -await updateProjectConfig(); - -if (process.env.NODE_ENV === 'development') { - await validateCurriculum(); -} - -const app = express(); - -app.use( - express.static( - join(ROOT, 'node_modules/@freecodecamp/freecodecamp-os/.freeCodeCamp/dist') - ) -); - -// Serve static dir(s) -const staticDir = freeCodeCampConfig.client?.static; -if (Array.isArray(staticDir)) { - for (const dir of staticDir) { - if (typeof dir === 'object') { - for (const [route, dir] of Object.entries(dir)) { - app.use(route, express.static(join(ROOT, dir))); - } - } else if (typeof dir === 'string') { - app.use(express.static(join(ROOT, dir))); - } - } -} else if (typeof staticDir === 'string') { - app.use(express.static(join(ROOT, staticDir))); -} else if (typeof staticDir === 'object') { - for (const [route, dir] of Object.entries(staticDir)) { - app.use(route, express.static(join(ROOT, dir))); - } -} -async function handleRunTests(ws, data) { - const { currentProject } = await getState(); - await runTests(ws, currentProject); - ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} - -async function handleResetProject(ws, data) { - await resetProject(ws); - ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} -function handleResetLesson(ws, data) {} - -async function handleGoToNextLesson(ws, data) { - const { currentProject } = await getState(); - const project = await getProjectConfig(currentProject); - const nextLesson = project.currentLesson + 1; - - if (nextLesson > 0 && nextLesson <= project.numberOfLessons - 1) { - await setProjectConfig(currentProject, { currentLesson: nextLesson }); - await runLesson(ws, project.dashedName); - } - ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} - -async function handleGoToPreviousLesson(ws, data) { - const { currentProject } = await getState(); - const project = await getProjectConfig(currentProject); - const prevLesson = project.currentLesson - 1; - - if (prevLesson >= 0 && prevLesson <= project.numberOfLessons - 1) { - await setProjectConfig(currentProject, { currentLesson: prevLesson }); - await runLesson(ws, project.dashedName); - } - ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} - -/** - * Gets the projects from `projects.json` and adds the neta to each project object. - * - * The client relies on each project having a title, description, and tags. - */ -async function getProjects() { - const projects = JSON.parse( - await readFile( - join(ROOT, freeCodeCampConfig.config['projects.json']), - 'utf-8' - ) - ); - - for (const project of projects) { - const { - title, - description, - tags = [] - } = await pluginEvents.getProjectMeta(project.dashedName); - project.tags = tags; - project.title = title; - project.description = description; - } - return projects; -} - -async function handleConnect(ws) { - const projects = await getProjects(); - - updateProjects(ws, projects); - updateFreeCodeCampConfig(ws, freeCodeCampConfig); - const { currentProject, locale } = await getState(); - updateLocale(ws, locale); - if (!currentProject) { - return; - } - const project = await getProjectConfig(currentProject); - runLesson(ws, project.dashedName); -} - -async function handleSelectProject(ws, data) { - const projects = JSON.parse( - await readFile( - join(ROOT, freeCodeCampConfig.config['projects.json']), - 'utf-8' - ) - ); - const selectedProject = projects.find(p => p.id === data?.data?.id); - // TODO: Should this set the currentProject to `null` (empty string)? - // for the case where the Camper has navigated to the landing page. - await setState({ currentProject: selectedProject?.dashedName ?? null }); - if (!selectedProject && !data?.data?.id) { - return ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); - } - - // Disabled whilst in development because it is annoying - if (process.env.NODE_ENV === 'production') { - await hideAll(); - await showFile(selectedProject.dashedName); - } else { - await showAll(); - } - await runLesson(ws, selectedProject.dashedName); - return ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} - -async function handleRequestData(ws, data) { - if (data?.data?.request === 'projects') { - const projects = await getProjects(); - updateProjects(ws, projects); - } - ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} - -function handleCancelTests(ws, data) { - const workerPool = getWorkerPool(); - for (const worker of workerPool) { - worker.terminate(); - } - ws.send(parse({ data: { event: data.event }, event: 'RESPONSE' })); -} - -async function handleRunClientCode(ws, data) { - const code = data?.data; - if (!code) { - return; - } - try { - let __result; - await eval(`(async () => {${code}})()`); - ws.send( - parse({ - data: { event: data.event, __result }, - event: 'RESPONSE' - }) - ); - } catch (e) { - logover.error('Error running client code:\n', e); - ws.send( - parse({ - data: { event: data.event, error: e.message }, - event: 'RESPONSE' - }) - ); - } -} - -async function handleChangeLanguage(ws, data) { - await setState({ locale: data?.data?.locale }); - updateLocale(ws, data?.data?.locale); - const projects = await getProjects(); - updateProjects(ws, projects); -} - -const PORT = freeCodeCampConfig.port || 8080; - -const server = app.listen(PORT, () => { - logover.info(`Server listening on port ${PORT}`); -}); - -const handle = { - connect: (ws, data) => { - handleConnect(ws); - }, - 'run-tests': handleRunTests, - 'reset-project': handleResetProject, - 'go-to-next-lesson': handleGoToNextLesson, - 'go-to-previous-lesson': handleGoToPreviousLesson, - 'request-data': handleRequestData, - 'select-project': handleSelectProject, - 'cancel-tests': handleCancelTests, - 'change-language': handleChangeLanguage, - '__run-client-code': handleRunClientCode -}; - -const wss = new WebSocketServer({ server }); - -wss.on('connection', function connection(ws) { - hotReload(ws, freeCodeCampConfig.hotReload?.ignore); - ws.on('message', function message(data) { - const parsedData = parseBuffer(data); - handle[parsedData.event]?.(ws, parsedData); - }); - (async () => { - const projects = await getProjects(); - updateProjects(ws, projects); - updateFreeCodeCampConfig(ws, freeCodeCampConfig); - })(); - sock('connect', { message: "Server says 'Hello!'" }); - - function sock(type, data = {}) { - ws.send(parse({ event: type, data })); - } -}); - -function parse(obj) { - return JSON.stringify(obj); -} - -function parseBuffer(buf) { - return JSON.parse(buf.toString()); -} - -/** - * Files currently under ownership by another thread. - */ -const RACING_FILES = new Set(); -const FREEDOM_TIMEOUT = 100; - -/** - * Adds an operation to the race queue. If a file is already in the queue, the op is delayed until the file is released. - * @param {string} filepath Path to file to move - * @param {*} cb Callback to call once file is free - */ -async function addToRaceQueue(filepath, cb) { - const isFileFree = await new Promise(resolve => { - setTimeout(() => { - if (!RACING_FILES.has(filepath)) { - resolve(true); - } - }, FREEDOM_TIMEOUT); - }); - if (isFileFree) { - RACING_FILES.add(filepath); - cb(); - } -} - -async function updateProjectConfig() { - const projects = JSON.parse( - await readFile( - join(ROOT, freeCodeCampConfig.config['projects.json']), - 'utf-8' - ) - ); - for (const project of projects) { - const { numberOfLessons } = await pluginEvents.getProjectMeta( - project.dashedName - ); - await setProjectConfig(project.dashedName, { numberOfLessons }); - } -} diff --git a/.freeCodeCamp/tooling/tests/test-worker.js b/.freeCodeCamp/tooling/tests/test-worker.js deleted file mode 100644 index 27a121c7..00000000 --- a/.freeCodeCamp/tooling/tests/test-worker.js +++ /dev/null @@ -1,39 +0,0 @@ -import { parentPort, workerData } from 'node:worker_threads'; -// These are used in the local scope of the `eval` in `runTests` -import { assert, AssertionError, expect, config as chaiConfig } from 'chai'; -import __helpers_c from '../test-utils.js'; - -import { freeCodeCampConfig, ROOT } from '../env.js'; -import { join } from 'path'; -import { logover } from '../logger.js'; - -let __helpers = __helpers_c; - -// Update __helpers with dynamic utils: -const helpers = freeCodeCampConfig.tooling?.['helpers']; -if (helpers) { - const dynamicHelpers = await import(join(ROOT, helpers)); - __helpers = { ...__helpers_c, ...dynamicHelpers }; -} - -const { beforeEach = '', project } = workerData; - -parentPort.on('message', async ({ testCode, testId }) => { - let passed = false; - let error = null; - try { - const _eval_out = await eval(`(async () => { - ${beforeEach} - ${testCode} -})();`); - passed = true; - } catch (e) { - error = {}; - Object.getOwnPropertyNames(e).forEach(key => { - error[key] = e[key]; - }); - // Cannot pass `e` "as is", because classes cannot be passed between threads - error.type = e instanceof AssertionError ? 'AssertionError' : 'Error'; - } - parentPort.postMessage({ passed, testId, error }); -}); diff --git a/.freeCodeCamp/tsconfig.json b/.freeCodeCamp/tsconfig.json deleted file mode 100644 index 84dc722a..00000000 --- a/.freeCodeCamp/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist/", - // "noImplicitAny": true, - "sourceMap": true, - "jsx": "react-jsx", - "allowJs": true, - "moduleResolution": "node", - "lib": ["WebWorker", "DOM", "DOM.Iterable", "ES2015"], - "target": "es5", - "module": "esnext", - "strict": true, - "esModuleInterop": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "resolveJsonModule": true, - // "skipLibCheck": true, - "types": ["node"] - }, - "exclude": ["node_modules", "**/*.spec.ts"], - "include": ["client/**/*"] -} diff --git a/.freeCodeCamp/webpack.config.cjs b/.freeCodeCamp/webpack.config.cjs deleted file mode 100644 index 5d350484..00000000 --- a/.freeCodeCamp/webpack.config.cjs +++ /dev/null @@ -1,82 +0,0 @@ -const path = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -module.exports = { - entry: path.join(__dirname, 'client/index.tsx'), - devtool: 'inline-source-map', - mode: process.env.NODE_ENV || 'development', - devServer: { - compress: true, - port: 9000 - }, - watch: process.env.NODE_ENV === 'development', - watchOptions: { - ignored: ['**/node_modules', '**/config'] - }, - module: { - rules: [ - { - test: /\.(js|jsx|tsx|ts)$/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'], - plugins: [ - require.resolve('@babel/plugin-syntax-import-assertions'), - [ - 'prismjs', - { - languages: [ - 'javascript', - 'css', - 'html', - 'json', - 'markdown', - 'sql', - 'rust', - 'typescript', - 'jsx', - 'c', - 'csharp', - 'cpp', - 'dotnet', - 'python', - 'pug', - 'handlebars' - ], - plugins: [], - theme: 'okaidia', - css: true - } - ] - ] - } - } - }, - { - test: /\.(ts|tsx)$/, - use: ['ts-loader'] - }, - { - test: /\.(css|scss)$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(jpg|jpeg|png|gif|mp3|svg)$/, - type: 'asset/resource' - } - ] - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'] - }, - output: { - filename: 'bundle.js', - path: path.resolve(__dirname, 'dist') - }, - plugins: [ - new HtmlWebpackPlugin({ - template: path.join(__dirname, 'client', 'index.html'), - favicon: path.join(__dirname, 'client', 'assets/fcc_primary_small.svg') - }) - ] -}; diff --git a/.gitignore b/.gitignore index e747329b..f87d2b73 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ Cargo.lock .freeCodeCamp/dist .DS_Store /docs/book -self/.logs/ \ No newline at end of file +self/.logs/ +client/dist/ diff --git a/.npmignore b/.npmignore index c65dba4a..8861e0f8 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,6 @@ -.freeCodeCamp/client -.freeCodeCamp/tests -.freeCodeCamp/tsconfig.json -.freeCodeCamp/webpack.config.cjs +client +server/tests +tsconfig.json .devcontainer .github @@ -17,3 +16,5 @@ self CONTRIBUTING.md Dockerfile renovate.json + +!client/dist diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 96707ab1..00000000 --- a/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -**/.cache -**/package-lock.json -**/pkg diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 2c652fea..00000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "endOfLine": "lf", - "semi": true, - "singleQuote": true, - "jsxSingleQuote": true, - "tabWidth": 2, - "trailingComma": "none", - "arrowParens": "avoid" -} diff --git a/bun.lock b/bun.lock new file mode 100644 index 00000000..3881d691 --- /dev/null +++ b/bun.lock @@ -0,0 +1,369 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@freecodecamp/freecodecamp-os", + "dependencies": { + "@tanstack/react-query": "5.66.0", + "@tanstack/react-router": "1.102.5", + "chai": "5.1.2", + "chokidar": "4.0.3", + "hono": "4.7.0", + "logover": "2.0.0", + "marked": "15.0.7", + "marked-highlight": "2.2.1", + "pino": "9.6.0", + "pino-http": "10.4.0", + "pino-pretty": "13.0.0", + "prismjs": "1.29.0", + "react": "19.0.0", + "react-dom": "19.0.0", + "ws": "8.18.0", + }, + "devDependencies": { + "@types/bun": "1.2.2", + "@types/prismjs": "1.26.5", + "@types/react": "19.0.8", + "@types/react-dom": "19.0.3", + "@vitejs/plugin-react": "4.3.4", + "typescript": "5.7.3", + "vite": "6.1.0", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], + + "@babel/compat-data": ["@babel/compat-data@7.26.8", "", {}, "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="], + + "@babel/core": ["@babel/core@7.26.8", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.8", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.7", "@babel/parser": "^7.26.8", "@babel/template": "^7.26.8", "@babel/traverse": "^7.26.8", "@babel/types": "^7.26.8", "@types/gensync": "^1.0.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ=="], + + "@babel/generator": ["@babel/generator@7.26.8", "", { "dependencies": { "@babel/parser": "^7.26.8", "@babel/types": "^7.26.8", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.26.5", "", { "dependencies": { "@babel/compat-data": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="], + + "@babel/helpers": ["@babel/helpers@7.26.7", "", { "dependencies": { "@babel/template": "^7.25.9", "@babel/types": "^7.26.7" } }, "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A=="], + + "@babel/parser": ["@babel/parser@7.26.8", "", { "dependencies": { "@babel/types": "^7.26.8" }, "bin": "./bin/babel-parser.js" }, "sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg=="], + + "@babel/template": ["@babel/template@7.26.8", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.26.8", "@babel/types": "^7.26.8" } }, "sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q=="], + + "@babel/traverse": ["@babel/traverse@7.26.8", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.8", "@babel/parser": "^7.26.8", "@babel/template": "^7.26.8", "@babel/types": "^7.26.8", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA=="], + + "@babel/types": ["@babel/types@7.26.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.6", "", { "os": "android", "cpu": "arm" }, "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.6", "", { "os": "android", "cpu": "arm64" }, "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.6", "", { "os": "win32", "cpu": "x64" }, "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w=="], + + "@tanstack/history": ["@tanstack/history@1.99.13", "", {}, "sha512-JMd7USmnp8zV8BRGIjALqzPxazvKtQ7PGXQC7n39HpbqdsmfV2ePCzieO84IvN+mwsTrXErpbjI4BfKCa+ZNCg=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.66.0", "", {}, "sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.66.0", "", { "dependencies": { "@tanstack/query-core": "5.66.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw=="], + + "@tanstack/react-router": ["@tanstack/react-router@1.102.5", "", { "dependencies": { "@tanstack/history": "1.99.13", "@tanstack/react-store": "^0.7.0", "@tanstack/router-core": "^1.102.5", "jsesc": "^3.1.0", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-stZgbXE++aW9sGeAC9fGyFiY58OsEDrn4L7y7eC5A8egC0mTrG0q0yoaxBstXWXGkahmnaKIKkHnl4F6+pNt2g=="], + + "@tanstack/react-store": ["@tanstack/react-store@0.7.0", "", { "dependencies": { "@tanstack/store": "0.7.0", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng=="], + + "@tanstack/router-core": ["@tanstack/router-core@1.102.5", "", { "dependencies": { "@tanstack/history": "1.99.13", "@tanstack/store": "^0.7.0" } }, "sha512-NdPrR7h+b7mwwn0+guEYhay/iZMpQZB324iNF+fJq7VZUrXG+dO/wgDJGSOYBhM4rObWned0gFjLHNAaAsBqBA=="], + + "@tanstack/store": ["@tanstack/store@0.7.0", "", {}, "sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.6.8", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], + + "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], + + "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], + + "@types/gensync": ["@types/gensync@1.0.4", "", {}, "sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA=="], + + "@types/node": ["@types/node@20.17.17", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-/WndGO4kIfMicEQLTi/mDANUu/iVUhT7KboZPdEqqHQ4aTS+3qT3U5gIqWDFV+XouorjfgGqvKILJeHhuQgFYg=="], + + "@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], + + "@types/react": ["@types/react@19.0.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="], + + "@types/react-dom": ["@types/react-dom@19.0.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="], + + "@types/ws": ["@types/ws@8.5.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.3.4", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + + "browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], + + "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001699", "", {}, "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w=="], + + "chai": ["chai@5.1.2", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw=="], + + "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], + + "debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.97", "", {}, "sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ=="], + + "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], + + "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "fast-copy": ["fast-copy@3.0.2", "", {}, "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ=="], + + "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], + + "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + + "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], + + "hono": ["hono@4.7.0", "", {}, "sha512-hV97aIR4WYbG30k234sD9B3VNr1ZWdQRmrVF76LKFlmI7O9Yo70mG9+mFwyQ6Sjrz4wH71GfnBxv6CPjcx3QNw=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "logover": ["logover@2.0.0", "", {}, "sha512-LZOEXlRUb7uDDfq34kFjt8HTnjxXcFgvd/rsl3TO+mBHtTC5JGNMVh7H3FkaBO0OecsuDMRU+15zyiZdo8z/+g=="], + + "loupe": ["loupe@3.1.3", "", {}, "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "marked": ["marked@15.0.7", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg=="], + + "marked-highlight": ["marked-highlight@2.2.1", "", { "peerDependencies": { "marked": ">=4 <16" } }, "sha512-SiCIeEiQbs9TxGwle9/OwbOejHCZsohQRaNTY2u8euEXYt2rYUFoiImUirThU3Gd/o6Q1gHGtH9qloHlbJpNIA=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + + "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "pathval": ["pathval@2.0.0", "", {}, "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "pino": ["pino@9.6.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^4.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg=="], + + "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], + + "pino-http": ["pino-http@10.4.0", "", { "dependencies": { "get-caller-file": "^2.0.5", "pino": "^9.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^4.0.0" } }, "sha512-vjQsKBE+VN1LVchjbfLE7B6nBeGASZNRNKsR68VS0DolTm5R3zo+47JX1wjm0O96dcbvA7vnqt8YqOWlG5nN0w=="], + + "pino-pretty": ["pino-pretty@13.0.0", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^3.0.2", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pump": "^3.0.0", "secure-json-parse": "^2.4.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^3.1.1" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA=="], + + "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="], + + "postcss": ["postcss@8.5.2", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA=="], + + "prismjs": ["prismjs@1.29.0", "", {}, "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="], + + "process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], + + "pump": ["pump@3.0.2", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + + "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], + + "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], + + "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], + + "readdirp": ["readdirp@4.1.1", "", {}, "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw=="], + + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + + "rollup": ["rollup@4.34.6", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.6", "@rollup/rollup-android-arm64": "4.34.6", "@rollup/rollup-darwin-arm64": "4.34.6", "@rollup/rollup-darwin-x64": "4.34.6", "@rollup/rollup-freebsd-arm64": "4.34.6", "@rollup/rollup-freebsd-x64": "4.34.6", "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", "@rollup/rollup-linux-arm-musleabihf": "4.34.6", "@rollup/rollup-linux-arm64-gnu": "4.34.6", "@rollup/rollup-linux-arm64-musl": "4.34.6", "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", "@rollup/rollup-linux-riscv64-gnu": "4.34.6", "@rollup/rollup-linux-s390x-gnu": "4.34.6", "@rollup/rollup-linux-x64-gnu": "4.34.6", "@rollup/rollup-linux-x64-musl": "4.34.6", "@rollup/rollup-win32-arm64-msvc": "4.34.6", "@rollup/rollup-win32-ia32-msvc": "4.34.6", "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ=="], + + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + + "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + + "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], + + "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + + "undici-types": ["undici-types@6.19.6", "", {}, "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg=="], + + "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], + + "vite": ["vite@6.1.0", "", { "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.5.1", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 428e10b2..a44ffc6f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -17,7 +17,8 @@ fn main() -> InquireResult<()> { match args.sub_commands { Some(SubCommand::AddProject) => { - add_project()?; + unimplemented!() + // add_project()?; } None => { create_course()?; diff --git a/.freeCodeCamp/client/index.html b/client/index.html similarity index 60% rename from .freeCodeCamp/client/index.html rename to client/index.html index 446cf3de..95d86540 100644 --- a/.freeCodeCamp/client/index.html +++ b/client/index.html @@ -1,12 +1,15 @@ + freeCodeCamp: Courses - + +
    + - \ No newline at end of file + diff --git a/client/public/vite.svg b/client/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/client/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.freeCodeCamp/client/assets/Lato-Regular.woff b/client/src/assets/Lato-Regular.woff similarity index 100% rename from .freeCodeCamp/client/assets/Lato-Regular.woff rename to client/src/assets/Lato-Regular.woff diff --git a/.freeCodeCamp/client/assets/fcc_primary_large.tsx b/client/src/assets/fcc_primary_large.tsx similarity index 100% rename from .freeCodeCamp/client/assets/fcc_primary_large.tsx rename to client/src/assets/fcc_primary_large.tsx diff --git a/.freeCodeCamp/client/assets/fcc_primary_small.svg b/client/src/assets/fcc_primary_small.svg similarity index 100% rename from .freeCodeCamp/client/assets/fcc_primary_small.svg rename to client/src/assets/fcc_primary_small.svg diff --git a/client/src/components/block.tsx b/client/src/components/block.tsx new file mode 100644 index 00000000..74b82fda --- /dev/null +++ b/client/src/components/block.tsx @@ -0,0 +1,85 @@ +import { Tag } from "./tag"; +import { Checkmark } from "./checkmark"; +import { Project, ProjectState } from "../../../types"; +import { LessonRoute } from "../templates/project"; +import { useNavigate } from "@tanstack/react-router"; + +type BlockProps = { + project: Project; + projectState: ProjectState; +}; + +export const Block = ({ + project: { + id, + title, + description, + isIntegrated, + isPublic, + numberOfLessons, + tags, + }, + projectState: { currentLesson, completedDate }, +}: BlockProps) => { + const navigate = useNavigate(); + function selectProject() { + navigate({ + to: LessonRoute.to, + params: { projectId: id, lessonId: currentLesson }, + }); + } + + let lessonsCompleted = 0; + if (completedDate) { + lessonsCompleted = numberOfLessons; + } else { + lessonsCompleted = + !isIntegrated && currentLesson === numberOfLessons - 1 + ? currentLesson + 1 + : currentLesson; + } + return ( +
  • + +
  • + ); +}; diff --git a/.freeCodeCamp/client/components/checkmark.tsx b/client/src/components/checkmark.tsx similarity index 100% rename from .freeCodeCamp/client/components/checkmark.tsx rename to client/src/components/checkmark.tsx diff --git a/client/src/components/console.tsx b/client/src/components/console.tsx new file mode 100644 index 00000000..15e537a8 --- /dev/null +++ b/client/src/components/console.tsx @@ -0,0 +1,28 @@ +import { ConsoleError } from "../../../types"; + +interface ConsoleProps { + consoleErrors: ConsoleError[]; +} + +export const Console = ({ consoleErrors }: ConsoleProps) => { + return ( +
      + {consoleErrors.map((consoleError) => ( + + ))} +
    + ); +}; + +const ConsoleElement = ({ testText, testId, error }: ConsoleError) => { + const details = `${testId + 1} ${testText} + + ${error}`; + return ( +
    + ); +}; diff --git a/client/src/components/controls.tsx b/client/src/components/controls.tsx new file mode 100644 index 00000000..62be890d --- /dev/null +++ b/client/src/components/controls.tsx @@ -0,0 +1,98 @@ +import { useContext, useEffect, useState } from "react"; +import { Project, TestState, WSSEvents } from "../../../types"; +import { WebSocketContext } from "../context/websocket"; +import { cancelTests, runTests } from "../utils/fetch"; + +interface ControlsProps { + project: Project; + // loader?: LoaderT; +} + +// Changes the Reset button background to a filling progress bar when the seed is running +function progressStyle(loader: any) { + if (!loader) { + return {}; + } + + const { + isLoading, + progress: { total, count }, + } = loader; + if (isLoading) { + return { + background: `linear-gradient(to right, #0065A9 ${ + (count / total) * 100 + }%, rgba(0,0,0,0) 0%)`, + }; + } +} + +export const Controls = ({ + project, +}: // loader +ControlsProps) => { + const [isTestsRunning, setIsTestsRunning] = useState(false); + const [testsState, setTestsState] = useState([]); + const { socket } = useContext(WebSocketContext)!; + + useEffect(() => { + if (socket) { + socket.addEventListener("message", handleTestsState); + } + + return () => { + if (socket) { + socket.removeEventListener("message", handleTestsState); + } + }; + }, []); + + function handleTestsState(event: MessageEvent) { + const data = JSON.parse(event.data); + switch (data.event) { + case WSSEvents.UPDATE_TESTS_STATE: + const testsState = data.data.testsState as TestState[]; + setTestsState(testsState); + break; + default: + break; + } + } + + useEffect(() => { + if (testsState.some((t) => t.isLoading)) { + setIsTestsRunning(true); + } else { + setIsTestsRunning(false); + } + }, [testsState]); + + function handleTests() { + if (isTestsRunning) { + cancelTests(); + } else { + runTests(); + } + } + + const resetDisabled = !project.isResetEnabled; + // const resetDisabled = !project.isResetEnabled || loader?.isLoading; + + return ( +
    + + +
    + ); +}; diff --git a/.freeCodeCamp/client/components/description.tsx b/client/src/components/description.tsx similarity index 100% rename from .freeCodeCamp/client/components/description.tsx rename to client/src/components/description.tsx diff --git a/.freeCodeCamp/client/components/error.tsx b/client/src/components/error.tsx similarity index 100% rename from .freeCodeCamp/client/components/error.tsx rename to client/src/components/error.tsx diff --git a/client/src/components/header.tsx b/client/src/components/header.tsx new file mode 100644 index 00000000..47a5c3f0 --- /dev/null +++ b/client/src/components/header.tsx @@ -0,0 +1,41 @@ +import { useQuery } from "@tanstack/react-query"; +import FreeCodeCampLogo from "../assets/fcc_primary_large"; +import { LanguageList } from "./language-list"; +import { getConfig } from "../utils/fetch"; +import { useNavigate } from "@tanstack/react-router"; +import { LandingRoute } from "../templates/landing"; + +export const Header = () => { + const navigate = useNavigate(); + const configQuery = useQuery({ + queryKey: ["config"], + queryFn: getConfig, + }); + + if (configQuery.isPending) { + return null; + } + + if (configQuery.isError) { + return
    Error: {configQuery.error.message}
    ; + } + + const config = configQuery.data; + + const locales = config?.curriculum?.locales + ? Object.keys(config.curriculum?.locales) + : []; + return ( +
    + + {locales.length > 1 ? : null} +
    + ); +}; diff --git a/client/src/components/heading.tsx b/client/src/components/heading.tsx new file mode 100644 index 00000000..9801c96d --- /dev/null +++ b/client/src/components/heading.tsx @@ -0,0 +1,85 @@ +import { useNavigate } from "@tanstack/react-router"; +import { LessonRoute } from "../templates/project"; +import { useEffect, useState } from "react"; + +interface HeadingProps { + projectId: number; + currentLesson: number; + numberOfLessons: number; + title: string; +} + +export const Heading = ({ + projectId, + currentLesson, + numberOfLessons, + title, +}: HeadingProps) => { + const navigate = useNavigate(); + const [anim, setAnim] = useState(""); + + const canGoBack = currentLesson > 0; + const canGoForward = currentLesson < numberOfLessons - 1; + + useEffect(() => { + setAnim("fade-in"); + setTimeout(() => setAnim(""), 1000); + }, [currentLesson]); + + function goToPreviousLesson() { + if (!canGoBack) { + return; + } + navigate({ + to: LessonRoute.to, + params: { + projectId, + lessonId: currentLesson - 1, + }, + }); + } + + function goToNextLesson() { + if (!canGoForward) { + return; + } + navigate({ + to: LessonRoute.to, + params: { + projectId, + lessonId: currentLesson + 1, + }, + }); + } + + const h1 = title + " - Lesson " + currentLesson; + return ( + + ); +}; diff --git a/.freeCodeCamp/client/components/hints.tsx b/client/src/components/hints.tsx similarity index 100% rename from .freeCodeCamp/client/components/hints.tsx rename to client/src/components/hints.tsx diff --git a/.freeCodeCamp/client/components/language-globe.tsx b/client/src/components/language-globe.tsx similarity index 100% rename from .freeCodeCamp/client/components/language-globe.tsx rename to client/src/components/language-globe.tsx diff --git a/.freeCodeCamp/client/components/language-list.tsx b/client/src/components/language-list.tsx similarity index 60% rename from .freeCodeCamp/client/components/language-list.tsx rename to client/src/components/language-list.tsx index a14d1838..425ef357 100644 --- a/.freeCodeCamp/client/components/language-list.tsx +++ b/client/src/components/language-list.tsx @@ -1,24 +1,22 @@ -import { useRef, useState } from 'react'; -import { LanguageGlobe } from './language-globe'; -import { Events } from '../types'; +import { useRef, useState } from "react"; +import { LanguageGlobe } from "./language-globe"; type LanguageListProps = { locales: string[]; - sock: (type: Events, data: {}) => void; }; -export function LanguageList({ locales, sock }: LanguageListProps) { +export function LanguageList({ locales }: LanguageListProps) { const [showList, setShowList] = useState(false); const listRef = useRef(null); const handleClick = (): void => { if (listRef.current) { if (showList) { - listRef.current.classList.add('hidden'); + listRef.current.classList.add("hidden"); setShowList(false); return; } - listRef.current.classList.remove('hidden'); + listRef.current.classList.remove("hidden"); setShowList(true); } }; @@ -29,26 +27,26 @@ export function LanguageList({ locales, sock }: LanguageListProps) { event.preventDefault(); const selectedLanguage = event.currentTarget.dataset.value; if (selectedLanguage === undefined) return; - sock(Events.CHANGE_LANGUAGE, { locale: selectedLanguage }); + // sock(Events.CHANGE_LANGUAGE, { locale: selectedLanguage }); }; return ( <>