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 ( -
- {freeCodeCampConfig.client?.landing?.[locale]?.description} -
- - {freeCodeCampConfig.client?.landing?.[locale]?.['faq-text']} - -` 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 In this course, you will build x using y. Some description here. Here is an image: First test using Chai.js Second test using global variables passed from Dynamic helpers should be imported. Inline hint with Multi-line hint with:fn main() {
- println!("Hello, world!");
-}
-
`
- );
-
- const expectedTests = [
- [
- '
assert
.before
hook.some
code blocks
.
`
- ];
-
- 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 @@
+
const code_block = true;
-