test(test): migrate from Mocha to Vitest (#62085)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Sem Bauke
2025-09-17 21:11:50 +02:00
committed by GitHub
parent a0c1876e36
commit 0ec12631e9
15 changed files with 565 additions and 741 deletions
+1
View File
@@ -199,6 +199,7 @@ curriculum/curricula.json
### Additional Folders ###
curriculum/dist
curriculum/build
curriculum/test/blocks-generated
### Playwright ###
+23 -11
View File
@@ -18,7 +18,8 @@ const {
getCurriculumStructure,
getBlockStructure,
getSuperblockStructure,
getBlockStructurePath
getBlockStructurePath,
getBlockStructureDir
} = require('./file-handler');
/**
@@ -294,14 +295,9 @@ function validateBlocks(superblocks, blockStructureDir) {
}
}
async function buildCurriculum(lang, filters) {
const contentDir = getContentDir(lang);
const blockStructureDir = getLanguageConfig(lang).blockStructureDir;
const builder = new SuperblockCreator({
blockCreator: getBlockCreator(lang, !isEmpty(filters))
});
async function parseCurriculumStructure(filters) {
const curriculum = getCurriculumStructure();
const blockStructureDir = getBlockStructureDir();
if (isEmpty(curriculum.superblocks))
throw Error('No superblocks found in curriculum.json');
if (isEmpty(curriculum.certifications))
@@ -314,8 +310,22 @@ async function buildCurriculum(lang, filters) {
const superblockList = addBlockStructure(
addSuperblockStructure(curriculum.superblocks)
);
return {
fullSuperblockList: applyFilters(superblockList, filters),
certifications: curriculum.certifications
};
}
async function buildCurriculum(lang, filters) {
const contentDir = getContentDir(lang);
const builder = new SuperblockCreator({
blockCreator: getBlockCreator(lang, !isEmpty(filters))
});
const { fullSuperblockList, certifications } =
await parseCurriculumStructure(filters);
const fullSuperblockList = applyFilters(superblockList, filters);
const fullCurriculum = { certifications: { blocks: {} } };
for (const superblock of fullSuperblockList) {
@@ -323,7 +333,7 @@ async function buildCurriculum(lang, filters) {
await builder.processSuperblock(superblock);
}
for (const cert of curriculum.certifications) {
for (const cert of certifications) {
const certPath = path.resolve(contentDir, 'certifications', `${cert}.yml`);
if (!fs.existsSync(certPath)) {
throw Error(`Certification file not found: ${certPath}`);
@@ -344,5 +354,7 @@ module.exports = {
getSuperblockStructure,
createCommentMap,
superBlockToFilename,
getSuperblocks
getSuperblocks,
addSuperblockStructure,
parseCurriculumStructure
};
+7 -6
View File
@@ -94,6 +94,10 @@ function getBlockStructurePath(block) {
return path.resolve(BLOCK_STRUCTURE_DIR, `${block}.json`);
}
function getBlockStructureDir() {
return BLOCK_STRUCTURE_DIR;
}
function getBlockStructure(block) {
return JSON.parse(fs.readFileSync(getBlockStructurePath(block), 'utf8'));
}
@@ -148,17 +152,15 @@ function getSuperblockStructurePath(superblockFilename) {
*/
function getLanguageConfig(
lang,
{ baseDir, i18nBaseDir, structureDir } = {
{ baseDir, i18nBaseDir } = {
baseDir: CURRICULUM_DIR,
i18nBaseDir: I18N_CURRICULUM_DIR,
structureDir: STRUCTURE_DIR
i18nBaseDir: I18N_CURRICULUM_DIR
}
) {
const contentDir = path.resolve(baseDir, 'challenges', 'english');
const i18nContentDir = path.resolve(i18nBaseDir, 'challenges', lang);
const blockContentDir = path.resolve(contentDir, 'blocks');
const i18nBlockContentDir = path.resolve(i18nContentDir, 'blocks');
const blockStructureDir = path.resolve(structureDir, 'blocks');
const dictionariesDir = path.resolve(baseDir, 'dictionaries');
const i18nDictionariesDir = path.resolve(i18nBaseDir, 'dictionaries');
@@ -179,7 +181,6 @@ function getLanguageConfig(
debug(`Using content directory: ${contentDir}`);
debug(`Using i18n content directory: ${i18nContentDir}`);
debug(`Using block content directory: ${blockContentDir}`);
debug(`Using i18n block content directory: ${i18nBlockContentDir}`);
debug(`Using dictionaries directory: ${dictionariesDir}`);
debug(`Using i18n dictionaries directory: ${i18nDictionariesDir}`);
@@ -189,7 +190,6 @@ function getLanguageConfig(
i18nContentDir,
blockContentDir,
i18nBlockContentDir,
blockStructureDir,
dictionariesDir,
i18nDictionariesDir
};
@@ -197,6 +197,7 @@ function getLanguageConfig(
exports.getContentConfig = getContentConfig;
exports.getContentDir = getContentDir;
exports.getBlockStructureDir = getBlockStructureDir;
exports.getBlockStructure = getBlockStructure;
exports.getBlockStructurePath = getBlockStructurePath;
exports.getSuperblockStructure = getSuperblockStructure;
+6 -3
View File
@@ -35,13 +35,14 @@
"reorder-tasks": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/reorder-tasks",
"update-challenge-order": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/update-challenge-order",
"update-step-titles": "CALLING_DIR=$INIT_CWD tsx --tsconfig ../tsconfig.json ../tools/challenge-helper-scripts/update-step-titles",
"test": "NODE_OPTIONS='--max-old-space-size=7168' tsx ./node_modules/mocha/bin/mocha.js --delay --exit --reporter progress --bail",
"test:full-output": "NODE_OPTIONS='--max-old-space-size=7168' FULL_OUTPUT=true tsx ./node_modules/mocha/bin/mocha.js --delay --reporter progress"
"test-gen": "node ./test/utils/generate-block-tests.mjs",
"test": "NODE_OPTIONS='--max-old-space-size=7168' pnpm -s test-gen && vitest -c test/vitest.config.mjs",
"test:full-output": "NODE_OPTIONS='--max-old-space-size=7168' pnpm -s test-gen && FULL_OUTPUT=true vitest -c test/vitest.config.mjs --reporter=default"
},
"devDependencies": {
"@babel/core": "7.23.7",
"@babel/register": "7.23.7",
"@compodoc/live-server": "^1.2.3",
"@types/polka": "^0.5.7",
"@vitest/ui": "^3.2.4",
"chai": "4.4.1",
"glob": "8.1.0",
@@ -54,8 +55,10 @@
"mocha": "10.3.0",
"mock-require": "3.0.3",
"ora": "5.4.1",
"polka": "^0.5.2",
"puppeteer": "22.12.1",
"readdirp": "3.6.0",
"sirv": "^3.0.2",
"string-similarity": "4.0.4",
"vitest": "^3.2.4"
}
+69
View File
@@ -0,0 +1,69 @@
import { assert, describe, it } from 'vitest';
import { testedLang } from '../utils';
import { getChallenges } from './test-challenges';
// Daily coding challenges are upcoming changes, so this test does nothing
// unless SHOW_UPCOMING_CHANGES is true.
describe('Daily Coding Challenges', async () => {
const lang = testedLang();
const challenges = await getChallenges(lang, {
superBlock: 'dev-playground'
});
const jsDailyChallenges = challenges.filter(
c => c.block === 'daily-coding-challenges-javascript'
);
const pyDailyChallenges = challenges.filter(
c => c.block === 'daily-coding-challenges-python'
);
it('should have matching number of JavaScript and Python challenges', function () {
assert.equal(
jsDailyChallenges.length,
pyDailyChallenges.length,
`JavaScript challenges: ${jsDailyChallenges.length}, Python challenges: ${pyDailyChallenges.length}`
);
});
for (let i = 0; i < jsDailyChallenges.length; i++) {
describe(`Challenge ${i + 1} Parity`, function () {
const jsChallenge = jsDailyChallenges[i];
const pyChallenge = pyDailyChallenges[i];
it("should have matching ID's", function () {
assert.equal(
jsChallenge.id,
pyChallenge.id,
`Challenge ${i + 1} ID mismatch - JS: ${jsChallenge.id}, Python: ${pyChallenge.id}`
);
});
it(`should have matching titles`, function () {
assert.equal(
jsChallenge.title,
pyChallenge.title,
`Challenge ${i + 1} title mismatch - JS: ${jsChallenge.title}, Python: ${pyChallenge.title}`
);
});
it('should have matching descriptions', function () {
assert.equal(
jsChallenge.description,
pyChallenge.description,
`Challenge ${i + 1} description mismatch`
);
});
it('should have the same number of tests', function () {
const jsTestCount = jsChallenge.tests.length;
const pyTestCount = pyChallenge.tests.length;
assert.equal(
jsTestCount,
pyTestCount,
`Challenge ${i + 1} test count mismatch - JS: ${jsTestCount}, Python: ${pyTestCount}`
);
});
});
}
});
+251 -407
View File
@@ -1,42 +1,26 @@
const path = require('path');
const { assert, AssertionError } = require('chai');
const jsdom = require('jsdom');
const liveServer = require('@compodoc/live-server');
const lodash = require('lodash');
const Mocha = require('mocha');
const mockRequire = require('mock-require');
const spinner = require('ora')();
const puppeteer = require('puppeteer');
import { createRequire } from 'node:module';
// lodash-es can't easily be used in node environments, so we just mock it out
// for the original lodash in testing.
mockRequire('lodash-es', lodash);
import { describe, it, beforeAll } from 'vitest';
import { assert, AssertionError } from 'chai';
import jsdom from 'jsdom';
import lodash from 'lodash';
const clientPath = path.resolve(__dirname, '../../client');
require('@babel/register')({
root: clientPath,
babelrc: false,
presets: ['@babel/preset-env', '@babel/typescript'],
plugins: ['dynamic-import-node'],
ignore: [/node_modules/],
only: [clientPath]
});
const {
import {
buildChallenge,
runnerTypes
} = require('../../client/src/templates/Challenges/utils/build');
const {
} from '../../client/src/templates/Challenges/utils/build';
import {
challengeTypes,
hasNoSolution
} = require('../../shared/config/challenge-types');
const { getLines } = require('../../shared/utils/get-lines');
} from '../../shared/config/challenge-types';
import { getLines } from '../../shared/utils/get-lines';
import { prefixDoctype } from '../../client/src/templates/Challenges/utils/frame';
const require = createRequire(import.meta.url);
const { getChallengesForLang } = require('../get-challenges');
const { challengeSchemaValidator } = require('../schema/challenge-schema');
const { testedLang } = require('../utils');
const {
prefixDoctype,
helperVersion
} = require('../../client/src/templates/Challenges/utils/frame');
const { curriculumSchemaValidator } = require('../schema/curriculum-schema');
const { validateMetaSchema } = require('../schema/meta-schema');
@@ -49,183 +33,79 @@ const { sortChallenges } = require('./utils/sort-challenges');
const { flatten, isEmpty, cloneDeep } = lodash;
const testFilter = {
block: process.env.FCC_BLOCK ? process.env.FCC_BLOCK.trim() : undefined,
challengeId: process.env.FCC_CHALLENGE_ID
? process.env.FCC_CHALLENGE_ID.trim()
: undefined,
superBlock: process.env.FCC_SUPERBLOCK
? process.env.FCC_SUPERBLOCK.trim()
: undefined
};
// rethrow unhandled rejections to make sure the tests exit with non-zero code
process.on('unhandledRejection', err => handleRejection(err));
// If an uncaught exception gets here, then mocha is in an unexpected state. All
// we can do is log the exception and exit with a non-zero code.
process.on('uncaughtException', err => {
console.error('Uncaught exception:');
console.error(err);
process.exit(1);
});
// some errors *may* not be reported, since cleanup is triggered by the first
// error and that starts shutting down the browser and the server.
const handleRejection = err => {
console.error('Unhandled rejection:');
// setting the error code because node does not (yet) exit with a non-zero
// code on unhandled exceptions.
process.exitCode = 1;
cleanup();
console.error(err);
if (process.env.FULL_OUTPUT !== 'true') process.exit();
};
const dom = new jsdom.JSDOM('');
global.document = dom.window.document;
global.DOMParser = dom.window.DOMParser;
const oldRunnerFail = Mocha.Runner.prototype.fail;
Mocha.Runner.prototype.fail = function (test, err) {
if (err instanceof AssertionError) {
const errMessage = String(err.message || '');
const assertIndex = errMessage.indexOf(': expected');
if (assertIndex !== -1) {
err.message = errMessage.slice(0, assertIndex);
}
// Don't show stacktrace for assertion errors.
if (err.stack) {
delete err.stack;
}
}
return oldRunnerFail.call(this, test, err);
};
// The puppeteer page is global so that we can recreate it during tests, if
// needed.
let page;
async function newPageContext(browser) {
const page = await browser.newPage();
// it's needed for workers as context.
await page.goto('http://127.0.0.1:8080/index.html');
const poolId = process.env.VITEST_POOL_ID;
async function createAndVisitNewPage() {
const page = await globalThis.puppeteerBrowserContext[poolId].newPage();
await page.goto(`http://127.0.0.1:8080/index.html`);
return page;
}
spinner.start();
spinner.text = 'Populate tests.';
async function newPageContext() {
// Reuse a single page per worker/pool to avoid the overhead of creating a
// new page for every block file.
globalThis.__fccPuppeteerPages ??= {};
globalThis.__fccPuppeteerPages[poolId] ??= globalThis.__fccPuppeteerPages[
poolId
] = createAndVisitNewPage();
let browser;
let page;
setup()
.then(runTests)
.catch(err => handleRejection(err));
async function setup() {
// liveServer starts synchronously
liveServer.start({
host: '127.0.0.1',
port: '8080',
root: path.resolve(__dirname, 'stubs'),
mount: [
[
'/dist',
path.join(clientPath, `static/js/test-runner/${helperVersion}`)
],
['/js', path.join(clientPath, 'static/js')]
],
open: false,
logLevel: 0
});
browser = await puppeteer.launch({
args: [
// Required for Docker version of Puppeteer
'--no-sandbox',
'--disable-setuid-sandbox',
// This will write shared memory files into /tmp instead of /dev/shm,
// because Dockers default for /dev/shm is 64MB
'--disable-dev-shm-usage'
// dumpio: true
],
headless: 'new'
});
global.Worker = createPseudoWorker(await newPageContext(browser));
page = await newPageContext(browser);
await page.setViewport({ width: 300, height: 150 });
return globalThis.__fccPuppeteerPages[poolId];
}
export async function defineTestsForBlock({ block }) {
const lang = testedLang();
const challenges = await getChallenges(lang, testFilter);
const challenges = await getChallenges(lang, { block });
const nonCertificationChallenges = challenges.filter(
({ challengeType }) => challengeType !== 7
);
if (isEmpty(nonCertificationChallenges)) {
throw Error(
`No challenges to test when using filter ${JSON.stringify(testFilter)}
If the challenge file exists, try running 'build:curriculum' for more information.
`
);
console.warn(`No non-certification challenges to test for block ${block}.`);
describe('Check challenges', () => {
it('No non-certification challenges to test', () => {});
});
return;
}
// the next few statements create a list of all blocks and superblocks
// as they appear in the list of challenges
const superBlocks = challenges.map(({ superBlock }) => superBlock);
const targetSuperBlockStrings = [
...new Set(superBlocks.filter(el => Boolean(el)))
];
const meta = {};
for (const challenge of challenges) {
const dashedBlockName = challenge.block;
// certifications do not have dashedBlockName's and don't have metas so
// we can skip them.
// TODO: omit certifications from the list of challenges
if (dashedBlockName && !meta[dashedBlockName]) {
meta[dashedBlockName] = getBlockStructure(dashedBlockName);
const result = validateMetaSchema(meta[dashedBlockName]);
if (result.error) {
throw new AssertionError(result.error);
}
if (result.error) throw new AssertionError(result.error);
}
}
return {
meta,
challenges,
lang,
superBlocks: targetSuperBlockStrings
};
}
// cleanup calls some async functions, but it's the last thing that happens, so
// no need to await anything.
function cleanup() {
if (browser) {
browser.close();
}
liveServer.shutdown();
spinner.stop();
}
const challengeData = { meta, challenges, lang };
function runTests(challengeData) {
describe('Check challenges', function () {
after(function () {
cleanup();
describe('Check challenges', () => {
beforeAll(async () => {
page = await newPageContext();
global.Worker = createPseudoWorker(page);
});
populateTestsForLang(challengeData);
populateTestsForLang(challengeData, () => page);
});
spinner.text = 'Testing';
run();
}
async function getChallenges(lang, filters) {
export async function getChallenges(lang, filters) {
const challenges = await getChallengesForLang(lang, filters).then(
curriculum => {
const result = curriculumSchemaValidator(curriculum);
// If there are filters, we're testing a single challenge or block, so we
// can skip the validation.
if (result.error && isEmpty(filters)) {
throw new Error(
`Curriculum validation failed: ${result.error.message}`
);
if (isEmpty(filters)) {
const result = curriculumSchemaValidator(curriculum);
if (result.error)
throw new Error(
`Curriculum validation failed: ${result.error.message}`
);
}
return Object.keys(curriculum)
.map(key => curriculum[key].blocks)
@@ -242,213 +122,143 @@ async function getChallenges(lang, filters) {
return sortChallenges(challenges);
}
function populateTestsForLang({ lang, challenges, meta, superBlocks }) {
function populateTestsForLang({ lang, challenges, meta }) {
const validateChallenge = challengeSchemaValidator();
superBlocks.forEach(superBlock => {
describe(`Language: ${lang}`, function () {
describe(`SuperBlock: ${superBlock}`, function () {
this.timeout(5000);
const superBlockChallenges = challenges.filter(
c => c.superBlock === superBlock
);
describe(`Language: ${lang}`, function () {
const challengeTitles = new ChallengeTitles();
const mongoIds = new MongoIds();
// daily challenge tests
if (superBlock === 'dev-playground') {
describe('Daily Coding Challenges', function () {
const jsDailyChallenges = superBlockChallenges.filter(
c => c.block === 'daily-coding-challenges-javascript'
);
challenges.forEach((challenge, id) => {
// When testing single challenge, in project based curriculum,
// challenge to test (current challenge) might not have solution.
// Instead seed from next challenge is tested against tests from
// current challenge. Next challenge is skipped from testing.
if (process.env.FCC_CHALLENGE_ID && id > 0) return;
const pyDailyChallenges = superBlockChallenges.filter(
c => c.block === 'daily-coding-challenges-python'
);
const dashedBlockName = challenge.block;
// TODO: once certifications are not included in the list of challenges,
// stop returning early here.
if (typeof dashedBlockName === 'undefined') return;
describe(`Block: ${challenge.block}`, function () {
describe(`Title: ${challenge.title}`, function () {
describe(`ID: ${challenge.id}`, function () {
// Note: the title in meta.json are purely for human readability and
// do not include translations, so we do not validate against them.
it('Matches an ID in meta.json', function () {
const index = meta[dashedBlockName]?.challengeOrder?.findIndex(
({ id }) => id === challenge.id
);
it('should have matching number of JavaScript and Python challenges', function () {
assert.equal(
jsDailyChallenges.length,
pyDailyChallenges.length,
`JavaScript challenges: ${jsDailyChallenges.length}, Python challenges: ${pyDailyChallenges.length}`
if (index < 0) {
throw new AssertionError(
`Cannot find ID "${challenge.id}" in meta.json file for block "${dashedBlockName}"`
);
}
});
it('Common checks', function () {
const result = validateChallenge(challenge);
if (result.error) {
throw new AssertionError(result.error);
}
const { id, block, dashedName } = challenge;
assert.exists(
dashedName,
`Missing dashedName for challenge ${id} in ${block}.`
);
const pathAndTitle = `${block}/${dashedName}`;
const idVerificationMessage = mongoIds.check(id, block);
assert.isNull(idVerificationMessage, idVerificationMessage);
const dupeTitleCheck = challengeTitles.check(dashedName, block);
assert.isTrue(
dupeTitleCheck,
`All challenges within a block must have a unique dashed name. ${dashedName} (at ${pathAndTitle}) is already assigned`
);
});
for (let i = 0; i < jsDailyChallenges.length; i++) {
describe(`Challenge ${i + 1} Parity`, function () {
const jsChallenge = jsDailyChallenges[i];
const pyChallenge = pyDailyChallenges[i];
const { challengeType } = challenge;
it("should have matching ID's", function () {
assert.equal(
jsChallenge.id,
pyChallenge.id,
`Challenge ${i + 1} ID mismatch - JS: ${jsChallenge.id}, Python: ${pyChallenge.id}`
);
});
if (hasNoSolution(challengeType)) return;
it(`should have matching titles`, function () {
assert.equal(
jsChallenge.title,
pyChallenge.title,
`Challenge ${i + 1} title mismatch - JS: ${jsChallenge.title}, Python: ${pyChallenge.title}`
);
});
it('should have matching descriptions', function () {
assert.equal(
jsChallenge.description,
pyChallenge.description,
`Challenge ${i + 1} description mismatch`
);
});
it('should have the same number of tests', function () {
const jsTestCount = jsChallenge.tests.length;
const pyTestCount = pyChallenge.tests.length;
assert.equal(
jsTestCount,
pyTestCount,
`Challenge ${i + 1} test count mismatch - JS: ${jsTestCount}, Python: ${pyTestCount}`
);
});
});
let { tests = [] } = challenge;
tests = tests.filter(test => !!test.testString);
if (tests.length === 0) {
it('Check tests. No tests.', () => {});
return;
}
});
}
const challengeTitles = new ChallengeTitles();
const mongoIds = new MongoIds();
if (challengeType === challengeTypes.backend) {
it('Check tests is not implemented.', () => {});
return;
}
superBlockChallenges.forEach((challenge, id) => {
// When testing single challenge, in project based curriculum,
// challenge to test (current challenge) might not have solution.
// Instead seed from next challenge is tested against tests from
// current challenge. Next challenge is skipped from testing.
if (process.env.FCC_CHALLENGE_ID && id > 0) return;
const dashedBlockName = challenge.block;
// TODO: once certifications are not included in the list of challenges,
// stop returning early here.
if (typeof dashedBlockName === 'undefined') return;
describe(`Block: ${challenge.block}`, function () {
describe(`Title: ${challenge.title}`, function () {
describe(`ID: ${challenge.id}`, function () {
// Note: the title in meta.json are purely for human readability and
// do not include translations, so we do not validate against them.
it('Matches an ID in meta.json', function () {
const index = meta[
dashedBlockName
]?.challengeOrder?.findIndex(({ id }) => id === challenge.id);
if (index < 0) {
throw new AssertionError(
`Cannot find ID "${challenge.id}" in meta.json file for block "${dashedBlockName}"`
);
}
});
it('Common checks', function () {
const result = validateChallenge(challenge);
if (result.error) {
throw new AssertionError(result.error);
}
const { id, block, dashedName } = challenge;
assert.exists(
dashedName,
`Missing dashedName for challenge ${id} in ${block}.`
// The python tests are (currently) slow, so we give them more time.
const timePerTest =
challengeType === challengeTypes.python ? 10000 : 5000;
it(
'Test suite must fail on the initial contents',
async function () {
// suppress errors in the console.
const oldConsoleError = console.error;
console.error = () => {};
let fails = false;
let testRunner;
// TODO: if this times out, try wrapping this whole test in a
// new describe block and create the runner in a beforeAll (i.e.
// same as "Check tests against solutions")
try {
testRunner = await createTestRunner(
challenge,
challenge.challengeFiles,
buildChallenge
);
const pathAndTitle = `${block}/${dashedName}`;
const idVerificationMessage = mongoIds.check(id, block);
assert.isNull(idVerificationMessage, idVerificationMessage);
const dupeTitleCheck = challengeTitles.check(
dashedName,
block
} catch (e) {
console.error(
`Error creating test runner for initial contents`
);
assert.isTrue(
dupeTitleCheck,
`All challenges within a block must have a unique dashed name. ${dashedName} (at ${pathAndTitle}) is already assigned`
);
});
const { challengeType } = challenge;
if (hasNoSolution(challengeType)) return;
let { tests = [] } = challenge;
tests = tests.filter(test => !!test.testString);
if (tests.length === 0) {
it('Check tests. No tests.');
return;
console.error(e);
fails = true;
}
if (challengeType === challengeTypes.backend) {
it('Check tests is not implemented.');
return;
}
// The python tests are (currently) slow, so we give them more time.
const timePerTest =
challengeType === challengeTypes.python ? 10000 : 5000;
it('Test suite must fail on the initial contents', async function () {
// TODO: some tests take a surprisingly long time to setup the
// test runner, so this timeout is large while we investigate.
this.timeout(timePerTest * tests.length + 20000);
// suppress errors in the console.
const oldConsoleError = console.error;
console.error = () => {};
let fails = false;
let testRunner;
if (!fails) {
try {
testRunner = await createTestRunner(
challenge,
challenge.challengeFiles,
buildChallenge
);
} catch (e) {
console.error(
`Error creating test runner for initial contents`
);
console.error(e);
await testRunner(tests);
} catch {
fails = true;
}
if (!fails) {
try {
await testRunner(tests);
} catch {
fails = true;
}
}
console.error = oldConsoleError;
assert(
fails,
'Test suite does not fail on the initial contents'
);
});
}
console.error = oldConsoleError;
assert(
fails,
'Test suite does not fail on the initial contents'
);
},
timePerTest * tests.length + 20000
);
let { solutions = [] } = challenge;
let { solutions = [] } = challenge;
// if there's an empty string as solution, this is likely a mistake
// TODO: what does this look like now? (this being detection of empty
// lines in solutions - rather than entirely missing solutions)
// if there's an empty string as solution, this is likely a mistake
// TODO: what does this look like now? (this being detection of empty
// lines in solutions - rather than entirely missing solutions)
// We need to track where the solution came from to give better
// feedback if the solution is failing.
let solutionFromNext = false;
// We need to track where the solution came from to give better
// feedback if the solution is failing.
let solutionFromNext = false;
if (isEmpty(solutions)) {
// if there are no solutions in the challenge, it's assumed the next
// challenge's seed will be a solution to the current challenge.
// This is expected to happen in the project based curriculum.
if (isEmpty(solutions)) {
// if there are no solutions in the challenge, it's assumed the next
// challenge's seed will be a solution to the current challenge.
// This is expected to happen in the project based curriculum.
const nextChallenge = superBlockChallenges[id + 1];
const nextChallenge = challenges[id + 1];
if (nextChallenge) {
const solutionFiles = cloneDeep(
nextChallenge.challengeFiles
);
if (!solutionFiles) {
throw Error(
`No solution found.
if (nextChallenge) {
const solutionFiles = cloneDeep(nextChallenge.challengeFiles);
if (!solutionFiles) {
throw Error(
`No solution found.
Check the next challenge (${nextChallenge.title}): it should have a seed which solves the current challenge.
For example:
@@ -460,61 +270,65 @@ For example:
seed goes here
\`\`\`
`
);
}
const solutionFilesWithEditableContents = solutionFiles.map(
file => ({
...file,
editableContents: getLines(
file.contents,
file.editableRegionBoundaries
)
})
);
// Since there is only one seed, there can only be one solution,
// but the tests assume solutions is an array.
solutions = [solutionFilesWithEditableContents];
solutionFromNext = true;
} else {
throw Error(
`solution omitted for ${challenge.superBlock} ${challenge.block} ${challenge.title}`
);
}
);
}
// TODO: the no-solution filtering is a little convoluted:
const noSolution = new RegExp('// solution required');
const filteredSolutions = solutions.filter(solution => {
return !isEmpty(
solution.filter(
challengeFile => !noSolution.test(challengeFile.contents)
const solutionFilesWithEditableContents = solutionFiles.map(
file => ({
...file,
editableContents: getLines(
file.contents,
file.editableRegionBoundaries
)
})
);
// Since there is only one seed, there can only be one solution,
// but the tests assume solutions is an array.
solutions = [solutionFilesWithEditableContents];
solutionFromNext = true;
} else {
throw Error(
`solution omitted for ${challenge.superBlock} ${challenge.block} ${challenge.title}`
);
}
}
// TODO: the no-solution filtering is a little convoluted:
const noSolution = new RegExp('// solution required');
const filteredSolutions = solutions.filter(solution => {
return !isEmpty(
solution.filter(
challengeFile => !noSolution.test(challengeFile.contents)
)
);
});
if (isEmpty(filteredSolutions)) {
it('Check tests. No solutions', () => {});
return;
}
describe('Check tests against solutions', function () {
solutions.forEach((solution, index) => {
let testRunner;
// Creating the test runner can be slow, so we do it in
// beforeAll rather than the test itself.
beforeAll(async () => {
testRunner = await createTestRunner(
challenge,
solution,
buildChallenge,
solutionFromNext
);
});
if (isEmpty(filteredSolutions)) {
it('Check tests. No solutions');
return;
}
describe('Check tests against solutions', function () {
solutions.forEach((solution, index) => {
it(`Solution ${
index + 1
} must pass the tests`, async function () {
this.timeout(timePerTest * tests.length + 2000);
const testRunner = await createTestRunner(
challenge,
solution,
buildChallenge,
solutionFromNext
);
await testRunner(tests);
});
});
});
it(
`Solution ${index + 1} must pass the tests`,
async function () {
await testRunner(tests);
},
timePerTest * tests.length + 2000
);
});
});
});
@@ -543,6 +357,8 @@ async function createTestRunner(
{ usesTestRunner: true }
);
if (!page) throw new Error('Browser page is not ready yet');
const evaluator = await getContextEvaluator({
// passing in challengeId so it's easier to debug timeouts
challengeId: challenge.id,
@@ -617,7 +433,7 @@ async function getContextEvaluator(config) {
};
}
async function initializeTestRunner({
async function _initializeTestRunner({
build,
sources,
type,
@@ -625,7 +441,6 @@ async function initializeTestRunner({
loadEnzyme
}) {
const source = type === 'dom' ? prefixDoctype({ build, sources }) : build;
await page.evaluate(
async (sources, source, type, hooks, loadEnzyme) => {
await window.FCCTestRunner.createTestRunner({
@@ -643,3 +458,32 @@ async function initializeTestRunner({
loadEnzyme
);
}
async function initializeTestRunner({
build,
sources,
type,
hooks,
loadEnzyme
}) {
// Ensure FCCTestRunner is available before creating it
await page.waitForFunction(
'window.FCCTestRunner && window.FCCTestRunner.createTestRunner',
{ timeout: 5000 }
);
try {
await _initializeTestRunner({ build, sources, type, hooks, loadEnzyme });
} catch (e) {
// It's not clear why, but sometimes the iframe load times out. It seems to
// be an issue with Puppeteer, so we give it one more try to reduce test
// flakiness.
if (e.message.includes('Timed out waiting for the test frame to load')) {
console.warn('Test frame load timed out. Retrying...');
page = await createAndVisitNewPage();
await _initializeTestRunner({ build, sources, type, hooks, loadEnzyme });
} else {
throw e;
}
}
}
@@ -0,0 +1,49 @@
import fs from 'node:fs';
import path from 'node:path';
import _ from 'lodash';
import { parseCurriculumStructure } from '../../build-curriculum.js';
const __dirname = import.meta.dirname;
const testFilter = {
block: process.env.FCC_BLOCK ? process.env.FCC_BLOCK.trim() : undefined,
challengeId: process.env.FCC_CHALLENGE_ID
? process.env.FCC_CHALLENGE_ID.trim()
: undefined,
superBlock: process.env.FCC_SUPERBLOCK
? process.env.FCC_SUPERBLOCK.trim()
: undefined
};
const GENERATED_DIR = path.resolve(__dirname, '../blocks-generated');
async function main() {
// clean and recreate directory
await fs.promises.rm(GENERATED_DIR, { force: true, recursive: true });
await fs.promises.mkdir(GENERATED_DIR, { recursive: true });
const { fullSuperblockList } = await parseCurriculumStructure(testFilter);
const blocks = _.uniq(
fullSuperblockList.flatMap(({ blocks }) => blocks).map(b => b.dashedName)
);
for (const block of blocks) {
const filePath = path.join(GENERATED_DIR, `${block}.test.js`);
const contents = generateSingleBlockFile({ block });
await fs.promises.writeFile(filePath, contents, 'utf8');
}
console.log(`Generated ${blocks.length} block test file(s).`);
}
function generateSingleBlockFile({ block }) {
return `import { defineTestsForBlock } from '../test-challenges.js';
await defineTestsForBlock({ block: ${JSON.stringify(block)} });
`;
}
main();
+53
View File
@@ -0,0 +1,53 @@
import path from 'node:path';
import sirv from 'sirv';
import polka from 'polka';
import puppeteer from 'puppeteer';
import { helperVersion } from '../../client/src/templates/Challenges/utils/frame';
const clientPath = path.resolve(__dirname, '../../client');
async function createBrowser() {
return puppeteer.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage'
],
headless: 'new'
});
}
let browser, server;
async function startServer() {
const host = '127.0.0.1';
const port = 8080;
const app = polka();
// Mount static files used by the tests
app.use(
'/dist',
sirv(path.join(clientPath, `static/js/test-runner/${helperVersion}`))
);
app.use('/js', sirv(path.join(clientPath, 'static/js')));
app.use('/', sirv(path.resolve(__dirname, 'stubs')));
app.listen(port, host);
return app.server;
}
export async function setup() {
server = await startServer();
browser = await createBrowser();
// Sharing the Websocket endpoint so that setup files can connect. This allows
// us to do as much work as possible once in the global setup while allowing
// each test pool to maintain its own connection.
process.env.PUPPETEER_WS_ENDPOINT = browser.wsEndpoint();
}
export async function teardown() {
await browser.close();
await server.close();
}
+13
View File
@@ -0,0 +1,13 @@
// connect to the puppeteer browser instance and create a new page context
// each VITEST_POOL_ID will have its own context
import puppeteer from 'puppeteer';
if (!globalThis.puppeteerBrowserContext?.[process.env.VITEST_POOL_ID]) {
globalThis.puppeteerBrowserContext ??= {};
const browser = await puppeteer.connect({
browserWSEndpoint: process.env.PUPPETEER_WS_ENDPOINT
});
globalThis.puppeteerBrowserContext[process.env.VITEST_POOL_ID] =
await browser.createBrowserContext();
}
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['test/blocks-generated/**/*.test.js'],
environment: 'node',
hookTimeout: 60000,
testTimeout: 30000,
isolate: false,
globalSetup: 'test/vitest-global-setup.mjs',
setupFiles: 'test/vitest-setup.mjs'
}
});
+6 -4
View File
@@ -58,23 +58,25 @@ function getSuperOrder(superblock) {
}
/**
* Filters the superblocks array to only include blocks with the specified dashedName (block).
* Filters the superblocks array to include, at most, a single superblock with the specified block.
* If no block is provided, returns the original superblocks array.
*
* @param {Array<Object>} superblocks - Array of superblock objects, each containing a blocks array.
* @param {Object} [options] - Options object
* @param {string} [options.block] - The dashedName of the block to filter for (in kebab case).
* @returns {Array<Object>} Filtered array of superblocks containing only the specified block, or the original array if block is not provided.
* @returns {Array<Object>} Array with one superblock containing the specified block, or the original array if block is not provided.
*/
function filterByBlock(superblocks, { block } = {}) {
if (!block) return superblocks;
return superblocks
const superblock = superblocks
.map(superblock => ({
...superblock,
blocks: superblock.blocks.filter(({ dashedName }) => dashedName === block)
}))
.filter(superblock => superblock.blocks.length > 0);
.find(superblock => superblock.blocks.length > 0);
return superblock ? [superblock] : [];
}
/**
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
exclude: ['test/blocks-generated/**/*.test.js']
}
});
+2 -1
View File
@@ -67,7 +67,8 @@ export default tseslint.config(
$: true,
ga: true,
jQuery: true,
router: true
router: true,
globalThis: true
},
parser: babelParser,
+2 -2
View File
@@ -75,9 +75,9 @@
"test:tools:challenge-helper-scripts": "cd ./tools/challenge-helper-scripts && pnpm test run",
"test:tools:scripts-build": "cd ./tools/scripts/build && pnpm test run",
"test:tools:challenge-parser": "cd ./tools/challenge-parser && pnpm test run",
"test:curriculum:content": "cd ./curriculum && pnpm test",
"test:curriculum:content": "cd ./curriculum && pnpm test run",
"test:curriculum:tooling": "cd ./curriculum && pnpm vitest run",
"test-curriculum-full-output": "cd ./curriculum && pnpm run test:full-output",
"test-curriculum-full-output": "cd ./curriculum && pnpm run test:full-output run",
"test:client": "cd ./client && pnpm test run",
"test-config": "jest config",
"test-tools": "jest tools",
+63 -307
View File
@@ -715,9 +715,9 @@ importers:
'@babel/register':
specifier: 7.23.7
version: 7.23.7(@babel/core@7.23.7)
'@compodoc/live-server':
specifier: ^1.2.3
version: 1.2.3
'@types/polka':
specifier: ^0.5.7
version: 0.5.7
'@vitest/ui':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4)
@@ -754,12 +754,18 @@ importers:
ora:
specifier: 5.4.1
version: 5.4.1
polka:
specifier: ^0.5.2
version: 0.5.2
puppeteer:
specifier: 22.12.1
version: 22.12.1(typescript@5.8.2)
readdirp:
specifier: 3.6.0
version: 3.6.0
sirv:
specifier: ^3.0.2
version: 3.0.2
string-similarity:
specifier: 4.0.4
version: 4.0.4
@@ -1171,6 +1177,10 @@ packages:
resolution: {integrity: sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ==}
engines: {node: '>=8'}
'@arr/every@1.0.1':
resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==}
engines: {node: '>=4'}
'@asamuzakjp/css-color@3.2.0':
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
@@ -2280,11 +2290,6 @@ packages:
'@bundled-es-modules/tough-cookie@0.1.6':
resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==}
'@compodoc/live-server@1.2.3':
resolution: {integrity: sha512-hDmntVCyjjaxuJzPzBx68orNZ7TW4BtHWMnXlIVn5dqhK7vuFF/11hspO1cMmc+2QTYgqde1TBcb3127S7Zrow==}
engines: {node: '>=0.10.0'}
hasBin: true
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -3640,8 +3645,8 @@ packages:
webpack-plugin-serve:
optional: true
'@polka/url@1.0.0-next.23':
resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==}
'@polka/url@0.5.0':
resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==}
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
@@ -4705,6 +4710,9 @@ packages:
resolution: {integrity: sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg==}
deprecated: This is a stub types definition. pino provides its own type definitions, so you do not need this installed.
'@types/polka@0.5.7':
resolution: {integrity: sha512-TH8CDXM8zoskPCNmWabtK7ziGv9Q21s4hMZLVYK5HFEfqmGXBqq/Wgi7jNELWXftZK/1J/9CezYa06x1RKeQ+g==}
'@types/prismjs@1.26.5':
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
@@ -4824,6 +4832,9 @@ packages:
'@types/tough-cookie@4.0.5':
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
'@types/trouter@3.1.4':
resolution: {integrity: sha512-4YIL/2AvvZqKBWenjvEpxpblT2KGO6793ipr5QS7/6DpQ3O3SwZGgNGWezxf3pzeYZc24a2pJIrR/+Jxh/wYNQ==}
'@types/unist@2.0.8':
resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==}
@@ -5495,14 +5506,6 @@ packages:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
apache-crypt@1.2.6:
resolution: {integrity: sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==}
engines: {node: '>=8'}
apache-md5@1.1.8:
resolution: {integrity: sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==}
engines: {node: '>=8'}
append-field@1.0.0:
resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
@@ -5917,12 +5920,6 @@ packages:
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
engines: {node: '>=10.0.0'}
batch@0.6.1:
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
bcryptjs@2.4.3:
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
better-opn@2.1.1:
resolution: {integrity: sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA==}
engines: {node: '>8.0.0'}
@@ -6406,10 +6403,6 @@ packages:
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
colors@1.4.0:
resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==}
engines: {node: '>=0.1.90'}
combine-source-map@0.8.0:
resolution: {integrity: sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==}
@@ -6478,10 +6471,6 @@ packages:
confusing-browser-globals@1.0.11:
resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==}
connect@3.7.0:
resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
engines: {node: '>= 0.10.0'}
console-browserify@1.2.0:
resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
@@ -6916,10 +6905,6 @@ packages:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
@@ -7190,10 +7175,6 @@ packages:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -7668,9 +7649,6 @@ packages:
event-source-polyfill@1.0.31:
resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
event-stream@4.0.1:
resolution: {integrity: sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@@ -7851,10 +7829,6 @@ packages:
fault@1.0.4:
resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
faye-websocket@0.11.4:
resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
engines: {node: '>=0.8.0'}
fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
@@ -7921,10 +7895,6 @@ packages:
resolution: {integrity: sha512-TL48Pi1oNHeMOHrKv1bCJUrWZDcD3DIG6AGYVNOnyZPr7Bd/pStN0pL+lfzF5BNoj/FclaoiaLenk4XUIFVYng==}
engines: {node: '>=8'}
finalhandler@1.1.2:
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
engines: {node: '>= 0.8'}
finalhandler@1.2.0:
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
engines: {node: '>= 0.8'}
@@ -8068,13 +8038,6 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
fresh@2.0.0:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
from@0.1.7:
resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -8715,21 +8678,9 @@ packages:
htmlparser2@8.0.2:
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
http-auth-connect@1.0.6:
resolution: {integrity: sha512-yaO0QSCPqGCjPrl3qEEHjJP+lwZ6gMpXLuCBE06eWwcXomkI5TARtu0kxf9teFuBj6iaV3Ybr15jaWUvbzNzHw==}
engines: {node: '>=8'}
http-auth@4.1.9:
resolution: {integrity: sha512-kvPYxNGc9EKGTXvOMnTBQw2RZfuiSihK/mLw/a4pbtRueTE45S55Lw/3k5CktIf7Ak0veMKEIteDj4YkNmCzmQ==}
engines: {node: '>=8'}
http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
http-errors@1.6.3:
resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==}
engines: {node: '>= 0.6'}
http-errors@1.8.0:
resolution: {integrity: sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==}
engines: {node: '>= 0.6'}
@@ -8738,9 +8689,6 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
http-parser-js@0.5.8:
resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==}
http-proxy-agent@4.0.1:
resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
engines: {node: '>= 6'}
@@ -10076,9 +10024,6 @@ packages:
resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==}
engines: {node: '>=0.10.0'}
map-stream@0.0.7:
resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==}
map-visit@1.0.0:
resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
engines: {node: '>=0.10.0'}
@@ -10101,6 +10046,10 @@ packages:
resolution: {integrity: sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==}
engines: {node: '>=18'}
matchit@1.1.0:
resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==}
engines: {node: '>=6'}
matchmediaquery@0.3.1:
resolution: {integrity: sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==}
@@ -10483,10 +10432,6 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime-types@3.0.1:
resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==}
engines: {node: '>= 0.6'}
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@@ -10647,10 +10592,6 @@ packages:
socks:
optional: true
morgan@1.10.0:
resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==}
engines: {node: '>= 0.8.0'}
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -10951,10 +10892,6 @@ packages:
on-exit-leak-free@2.1.0:
resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==}
on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -10978,10 +10915,6 @@ packages:
resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==}
engines: {node: '>=8'}
open@8.4.0:
resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
engines: {node: '>=12'}
openapi-types@12.1.3:
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
@@ -11231,9 +11164,6 @@ packages:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
pause-stream@0.0.11:
resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
pbkdf2@3.1.2:
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
engines: {node: '>=0.12'}
@@ -11340,6 +11270,9 @@ packages:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
polka@0.5.2:
resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==}
portfinder@1.0.37:
resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==}
engines: {node: '>= 10.12'}
@@ -11725,10 +11658,6 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
proxy-middleware@0.15.0:
resolution: {integrity: sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==}
engines: {node: '>=0.8.0'}
pseudomap@1.0.2:
resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
@@ -12552,10 +12481,6 @@ packages:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
engines: {node: '>= 0.8.0'}
send@1.2.0:
resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==}
engines: {node: '>= 18'}
serialize-javascript@5.0.1:
resolution: {integrity: sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==}
@@ -12568,10 +12493,6 @@ packages:
serve-handler@6.1.3:
resolution: {integrity: sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==}
serve-index@1.9.1:
resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==}
engines: {node: '>= 0.8.0'}
serve-static@1.15.0:
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
engines: {node: '>= 0.8.0'}
@@ -12610,9 +12531,6 @@ packages:
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
engines: {node: '>=0.10.0'}
setprototypeof@1.1.0:
resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@@ -12720,8 +12638,8 @@ packages:
resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==}
engines: {node: '>= 10'}
sirv@3.0.1:
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
sirv@3.0.2:
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
engines: {node: '>=18'}
sister@3.0.2:
@@ -12873,9 +12791,6 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
split@1.0.1:
resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==}
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
@@ -12940,9 +12855,6 @@ packages:
stream-combiner2@1.1.1:
resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==}
stream-combiner@0.2.2:
resolution: {integrity: sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==}
stream-http@3.2.0:
resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==}
@@ -13449,6 +13361,10 @@ packages:
trough@1.0.5:
resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==}
trouter@2.0.1:
resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==}
engines: {node: '>=6'}
true-case-path@2.2.1:
resolution: {integrity: sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==}
@@ -13813,9 +13729,6 @@ packages:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
unix-crypt-td-js@1.1.4:
resolution: {integrity: sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==}
unixify@1.0.0:
resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==}
engines: {node: '>=0.10.0'}
@@ -14202,14 +14115,6 @@ packages:
webpack-cli:
optional: true
websocket-driver@0.7.4:
resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==}
engines: {node: '>=0.8.0'}
websocket-extensions@0.1.4:
resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==}
engines: {node: '>=0.8.0'}
whatwg-encoding@1.0.5:
resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==}
@@ -14642,6 +14547,8 @@ snapshots:
dependencies:
tslib: 2.0.3
'@arr/every@1.0.1': {}
'@asamuzakjp/css-color@3.2.0':
dependencies:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)
@@ -17029,25 +16936,6 @@ snapshots:
'@types/tough-cookie': 4.0.5
tough-cookie: 4.1.4
'@compodoc/live-server@1.2.3':
dependencies:
chokidar: 3.6.0
colors: 1.4.0
connect: 3.7.0
cors: 2.8.5
event-stream: 4.0.1
faye-websocket: 0.11.4
http-auth: 4.1.9
http-auth-connect: 1.0.6
morgan: 1.10.0
object-assign: 4.1.1
open: 8.4.0
proxy-middleware: 0.15.0
send: 1.2.0
serve-index: 1.9.1
transitivePeerDependencies:
- supports-color
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -18492,7 +18380,7 @@ snapshots:
source-map: 0.7.4
webpack: 5.90.3(webpack-cli@4.10.0)
'@polka/url@1.0.0-next.23': {}
'@polka/url@0.5.0': {}
'@polka/url@1.0.0-next.29': {}
@@ -19832,6 +19720,13 @@ snapshots:
dependencies:
pino: 9.7.0
'@types/polka@0.5.7':
dependencies:
'@types/express': 4.17.21
'@types/express-serve-static-core': 4.17.37
'@types/node': 20.12.8
'@types/trouter': 3.1.4
'@types/prismjs@1.26.5': {}
'@types/prop-types@15.7.8': {}
@@ -19970,6 +19865,8 @@ snapshots:
'@types/tough-cookie@4.0.5': {}
'@types/trouter@3.1.4': {}
'@types/unist@2.0.8': {}
'@types/unist@3.0.0': {}
@@ -20493,7 +20390,7 @@ snapshots:
fflate: 0.8.2
flatted: 3.3.3
pathe: 2.0.3
sirv: 3.0.1
sirv: 3.0.2
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/node@20.12.8)(@vitest/ui@3.2.4)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.0)
@@ -20783,12 +20680,6 @@ snapshots:
normalize-path: 3.0.0
picomatch: 2.3.1
apache-crypt@1.2.6:
dependencies:
unix-crypt-td-js: 1.1.4
apache-md5@1.1.8: {}
append-field@1.0.0: {}
application-config-path@0.1.1: {}
@@ -21369,10 +21260,6 @@ snapshots:
basic-ftp@5.0.5: {}
batch@0.6.1: {}
bcryptjs@2.4.3: {}
better-opn@2.1.1:
dependencies:
open: 7.4.2
@@ -22017,8 +21904,6 @@ snapshots:
colorette@2.0.20: {}
colors@1.4.0: {}
combine-source-map@0.8.0:
dependencies:
convert-source-map: 1.1.3
@@ -22098,15 +21983,6 @@ snapshots:
confusing-browser-globals@1.0.11: {}
connect@3.7.0:
dependencies:
debug: 2.6.9
finalhandler: 1.1.2
parseurl: 1.3.3
utils-merge: 1.0.1
transitivePeerDependencies:
- supports-color
console-browserify@1.2.0: {}
constants-browserify@1.0.0: {}
@@ -22620,8 +22496,6 @@ snapshots:
es-errors: 1.3.0
gopd: 1.0.1
define-lazy-prop@2.0.0: {}
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
@@ -22915,8 +22789,6 @@ snapshots:
encodeurl@1.0.2: {}
encodeurl@2.0.0: {}
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
@@ -23756,16 +23628,6 @@ snapshots:
event-source-polyfill@1.0.31: {}
event-stream@4.0.1:
dependencies:
duplexer: 0.1.2
from: 0.1.7
map-stream: 0.0.7
pause-stream: 0.0.11
split: 1.0.1
stream-combiner: 0.2.2
through: 2.3.8
event-target-shim@5.0.1: {}
eventemitter3@3.1.2: {}
@@ -24038,10 +23900,6 @@ snapshots:
dependencies:
format: 0.2.2
faye-websocket@0.11.4:
dependencies:
websocket-driver: 0.7.4
fb-watchman@2.0.2:
dependencies:
bser: 2.1.1
@@ -24105,18 +23963,6 @@ snapshots:
dependencies:
'@babel/runtime': 7.23.9
finalhandler@1.1.2:
dependencies:
debug: 2.6.9
encodeurl: 1.0.2
escape-html: 1.0.3
on-finished: 2.3.0
parseurl: 1.3.3
statuses: 1.5.0
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
finalhandler@1.2.0:
dependencies:
debug: 2.6.9
@@ -24280,10 +24126,6 @@ snapshots:
fresh@0.5.2: {}
fresh@2.0.0: {}
from@0.1.7: {}
fs-constants@1.0.0: {}
fs-exists-cached@1.0.0: {}
@@ -25431,24 +25273,8 @@ snapshots:
domutils: 3.1.0
entities: 4.5.0
http-auth-connect@1.0.6: {}
http-auth@4.1.9:
dependencies:
apache-crypt: 1.2.6
apache-md5: 1.1.8
bcryptjs: 2.4.3
uuid: 8.3.2
http-cache-semantics@4.1.1: {}
http-errors@1.6.3:
dependencies:
depd: 1.1.2
inherits: 2.0.3
setprototypeof: 1.1.0
statuses: 1.5.0
http-errors@1.8.0:
dependencies:
depd: 1.1.2
@@ -25465,8 +25291,6 @@ snapshots:
statuses: 2.0.1
toidentifier: 1.0.1
http-parser-js@0.5.8: {}
http-proxy-agent@4.0.1:
dependencies:
'@tootallnate/once': 1.1.2
@@ -27148,8 +26972,6 @@ snapshots:
map-cache@0.2.2: {}
map-stream@0.0.7: {}
map-visit@1.0.0:
dependencies:
object-visit: 1.0.1
@@ -27176,6 +26998,10 @@ snapshots:
markdown-it: 14.0.0
markdownlint-micromark: 0.1.8
matchit@1.1.0:
dependencies:
'@arr/every': 1.0.1
matchmediaquery@0.3.1:
dependencies:
css-mediaquery: 0.1.2
@@ -27902,10 +27728,6 @@ snapshots:
dependencies:
mime-db: 1.52.0
mime-types@3.0.1:
dependencies:
mime-db: 1.54.0
mime@1.6.0: {}
mime@2.6.0: {}
@@ -28052,16 +27874,6 @@ snapshots:
'@aws-sdk/credential-providers': 3.521.0
socks: 2.8.3
morgan@1.10.0:
dependencies:
basic-auth: 2.0.1
debug: 2.6.9
depd: 2.0.0
on-finished: 2.3.0
on-headers: 1.0.2
transitivePeerDependencies:
- supports-color
mri@1.2.0: {}
mrmime@1.0.1: {}
@@ -28426,10 +28238,6 @@ snapshots:
on-exit-leak-free@2.1.0: {}
on-finished@2.3.0:
dependencies:
ee-first: 1.1.1
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -28453,12 +28261,6 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
open@8.4.0:
dependencies:
define-lazy-prop: 2.0.0
is-docker: 2.2.1
is-wsl: 2.2.0
openapi-types@12.1.3: {}
opener@1.5.2: {}
@@ -28732,10 +28534,6 @@ snapshots:
pathval@2.0.1: {}
pause-stream@0.0.11:
dependencies:
through: 2.3.8
pbkdf2@3.1.2:
dependencies:
create-hash: 1.2.0
@@ -28846,6 +28644,11 @@ snapshots:
pluralize@8.0.0: {}
polka@0.5.2:
dependencies:
'@polka/url': 0.5.0
trouter: 2.0.1
portfinder@1.0.37:
dependencies:
async: 3.2.6
@@ -29238,8 +29041,6 @@ snapshots:
proxy-from-env@1.1.0: {}
proxy-middleware@0.15.0: {}
pseudomap@1.0.2: {}
psl@1.9.0: {}
@@ -30299,22 +30100,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
send@1.2.0:
dependencies:
debug: 4.4.1
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
fresh: 2.0.0
http-errors: 2.0.0
mime-types: 3.0.1
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
serialize-javascript@5.0.1:
dependencies:
randombytes: 2.1.0
@@ -30338,18 +30123,6 @@ snapshots:
path-to-regexp: 2.2.1
range-parser: 1.2.0
serve-index@1.9.1:
dependencies:
accepts: 1.3.8
batch: 0.6.1
debug: 2.6.9
escape-html: 1.0.3
http-errors: 1.6.3
mime-types: 2.1.35
parseurl: 1.3.3
transitivePeerDependencies:
- supports-color
serve-static@1.15.0:
dependencies:
encodeurl: 1.0.2
@@ -30421,8 +30194,6 @@ snapshots:
is-plain-object: 2.0.4
split-string: 3.1.0
setprototypeof@1.1.0: {}
setprototypeof@1.2.0: {}
sha.js@2.4.11:
@@ -30549,11 +30320,11 @@ snapshots:
sirv@2.0.3:
dependencies:
'@polka/url': 1.0.0-next.23
'@polka/url': 1.0.0-next.29
mrmime: 1.0.1
totalist: 3.0.1
sirv@3.0.1:
sirv@3.0.2:
dependencies:
'@polka/url': 1.0.0-next.29
mrmime: 2.0.1
@@ -30744,10 +30515,6 @@ snapshots:
split2@4.2.0: {}
split@1.0.1:
dependencies:
through: 2.3.8
sprintf-js@1.0.3: {}
sprintf-js@1.1.3: {}
@@ -30811,11 +30578,6 @@ snapshots:
duplexer2: 0.1.4
readable-stream: 2.3.8
stream-combiner@0.2.2:
dependencies:
duplexer: 0.1.2
through: 2.3.8
stream-http@3.2.0:
dependencies:
builtin-status-codes: 3.0.0
@@ -31409,7 +31171,7 @@ snapshots:
tough-cookie@4.1.4:
dependencies:
psl: 1.9.0
punycode: 2.3.0
punycode: 2.3.1
universalify: 0.2.0
url-parse: 1.5.10
@@ -31443,6 +31205,10 @@ snapshots:
trough@1.0.5: {}
trouter@2.0.1:
dependencies:
matchit: 1.1.0
true-case-path@2.2.1: {}
ts-api-utils@2.0.1(typescript@5.7.3):
@@ -31890,8 +31656,6 @@ snapshots:
universalify@2.0.0: {}
unix-crypt-td-js@1.1.4: {}
unixify@1.0.0:
dependencies:
normalize-path: 2.1.1
@@ -32493,14 +32257,6 @@ snapshots:
- esbuild
- uglify-js
websocket-driver@0.7.4:
dependencies:
http-parser-js: 0.5.8
safe-buffer: 5.2.1
websocket-extensions: 0.1.4
websocket-extensions@0.1.4: {}
whatwg-encoding@1.0.5:
dependencies:
iconv-lite: 0.4.24