From 5056454536c224820eed2dd2d9744854b2010044 Mon Sep 17 00:00:00 2001 From: Marius Date: Tue, 3 Mar 2026 13:39:51 +0200 Subject: [PATCH] 818 - Implement an XML sitemap #818 --- .github/workflows/publish.yaml | 18 ++ config/webpack.config.cs.js | 4 + package.json | 9 +- public/robots.txt | 5 +- scripts/generate-sitemap.ts | 366 +++++++++++++++++++++++++++++++++ scripts/sitemap-config.ts | 222 ++++++++++++++++++++ scripts/tsconfig.json | 18 ++ yarn.lock | 117 ++++++++++- 8 files changed, 756 insertions(+), 3 deletions(-) create mode 100644 scripts/generate-sitemap.ts create mode 100644 scripts/sitemap-config.ts create mode 100644 scripts/tsconfig.json diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index dbafc12878..671cbb6386 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -18,6 +18,24 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Generate sitemap + run: yarn generate-sitemap + env: + # Tags (v*) = production, pushes to main = staging + # URLs are resolved from ISAAC_ENV in scripts/sitemap-config.ts, + # mirroring the envSpecific() pattern from src/app/services/constants.ts + ISAAC_ENV: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || 'staging' }} + - name: Log in to the Container registry uses: docker/login-action@v3 with: diff --git a/config/webpack.config.cs.js b/config/webpack.config.cs.js index 0e93323990..4221d1b146 100644 --- a/config/webpack.config.cs.js +++ b/config/webpack.config.cs.js @@ -39,6 +39,10 @@ module.exports = env => { },{ from: resolve('public/robots.txt'), to: 'robots.txt', + }, { + from: resolve('public/sitemap.xml'), + to: 'sitemap.xml', + noErrorOnMissing: true, // Sitemap is generated separately so is ok }] }), ], diff --git a/package.json b/package.json index 4e76053ba9..0b3ce0f98b 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "uuid": "^9.0.1" }, "scripts": { + "generate-sitemap": "ts-node --project scripts/tsconfig.json scripts/generate-sitemap.ts", "build-cs": "webpack --env prod --config config/webpack.config.cs.js", "build-dev": "webpack --config config/webpack.config.cs.js", "start": "webpack-dev-server --hot --port 8003 --history-api-fallback --config config/webpack.config.cs.js --allowed-hosts=true", @@ -104,7 +105,9 @@ } } }, - "eslintIgnore": ["/src/IsaacApiTypesAutoGenerated.tsx"], + "eslintIgnore": [ + "/src/IsaacApiTypesAutoGenerated.tsx" + ], "browserslist": [ ">0.2%", "not dead", @@ -128,9 +131,11 @@ "@types/html-to-text": "^9.0.4", "@types/jest": "^29.5.12", "@types/js-cookie": "^3.0.6", + "@types/js-yaml": "^4.0.9", "@types/katex": "^0.16.7", "@types/leaflet": "^1.9.6", "@types/lodash": "^4.17.5", + "@types/node": "^20.11.0", "@types/object-hash": "^3.0.4", "@types/qrcode": "^1.5.2", "@types/react-beautiful-dnd": "^13.1.8", @@ -166,6 +171,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-watch-typeahead": "^2.2.2", + "js-yaml": "^4.1.1", "mini-css-extract-plugin": "^2.9.0", "msw": "^1.3.3", "prettier": "^3.0.3", @@ -179,6 +185,7 @@ "style-loader": "^3.3.4", "ts-jest": "^29.4.6", "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "typescript": "^4.9.5", "webpack": "^5.101.3", "webpack-bundle-analyzer": "^4.10.2", diff --git a/public/robots.txt b/public/robots.txt index 49b5cdf660..03681ba52a 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -9,4 +9,7 @@ Disallow: # Allow Googlebot access to everything User-agent: Googlebot User-agent: Bingbot -Disallow: \ No newline at end of file +Disallow: + +# Sitemap location +Sitemap: https://isaaccomputerscience.org/sitemap.xml \ No newline at end of file diff --git a/scripts/generate-sitemap.ts b/scripts/generate-sitemap.ts new file mode 100644 index 0000000000..090817903d --- /dev/null +++ b/scripts/generate-sitemap.ts @@ -0,0 +1,366 @@ +#!/usr/bin/env ts-node +/** + * Sitemap Generator + * + * Generates an XML sitemap by: + * 1. Including static routes from configuration + * 2. Fetching dynamic content (concepts, questions, events) from the API + * 3. Generating topic URLs from the topic ID list - needed? + * + * Usage: + * npm run generate-sitemap + * + * Environment variables: + * API_URL - API base URL (default: http://localhost:8080/isaac-api/api) + * SITE_URL - Site base URL for sitemap (default: https://isaaccomputerscience.org) + * SITEMAP_OUTPUT - Output file path (default: public/sitemap.xml) + * + */ + +import axios, { AxiosInstance } from "axios"; +import * as fs from "fs"; +import * as path from "path"; +import { + STATIC_ROUTES, + TOPIC_IDS, + HIDDEN_TOPICS, + CONTENT_PRIORITIES, + API_CONFIG, + OUTPUT_CONFIG, + SitemapURL, +} from "./sitemap-config"; + +interface ContentSummary { + id?: string; + title?: string; + type?: string; + published?: boolean; + tags?: string[]; +} + +interface ResultsWrapper { + results: T[]; + totalResults: number; +} + +interface EventPage { + id?: string; + title?: string; + date?: number; + endDate?: number; + eventStatus?: string; +} + +const api: AxiosInstance = axios.create({ + baseURL: API_CONFIG.baseUrl, + timeout: API_CONFIG.timeout, + headers: { + Accept: "application/json", + }, +}); + +/** + * Escape XML special characters (refactor if needed) + * + */ +function escapeXml(str: string): string { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +/** + * Format date as YYYY-MM-DD for sitemap + */ +function formatDate(date: Date): string { + return date.toISOString().split("T")[0]; // cut time after date +} + +/** + * Retry wrapper for API calls + * Not exepected to fail but solves a headache + */ +async function withRetry( + fn: () => Promise, + retries: number = API_CONFIG.maxRetries, + delay: number = 1000 +): Promise { + try { + return await fn(); + } catch (error) { + if (retries > 0) { + console.log(` Retrying... (${retries} attempts remaining)`); + await new Promise((resolve) => setTimeout(resolve, delay)); + return withRetry(fn, retries - 1, delay * 2); + } + throw error; + } +} + +/** + * STATIC ROUTES + * + */ +function getStaticUrls(): SitemapURL[] { + const today = formatDate(new Date()); + + return STATIC_ROUTES.map((route) => ({ + loc: `${API_CONFIG.siteUrl}${route.path}`, + lastmod: today, + changefreq: route.changefreq, + priority: route.priority, + })); +} + +/** + * TOPIC PAGES + * + */ +function getTopicUrls(): SitemapURL[] { + const today = formatDate(new Date()); + const { priority, changefreq } = CONTENT_PRIORITIES.topic; + + // Filter out hidden topics + const visibleTopics = TOPIC_IDS.filter((topicId) => !HIDDEN_TOPICS.includes(topicId)); + + return visibleTopics.map((topicId) => ({ + loc: `${API_CONFIG.siteUrl}/topics/${topicId}`, + lastmod: today, + changefreq, + priority, + })); +} + +/** + * CONCEPTS + * + */ +async function getConceptUrls(): Promise { + console.log("Fetching concepts..."); + + try { + const response = await withRetry(() => + api.get>("/pages/concepts", { + params: { limit: API_CONFIG.pageLimit }, + }) + ); + + const concepts = response.data.results || []; + console.log(` Found ${concepts.length} concepts`); + + const today = formatDate(new Date()); + const { priority, changefreq } = CONTENT_PRIORITIES.concept; + + return concepts + .filter((concept) => concept.id && concept.published !== false) + .map((concept) => ({ + loc: `${API_CONFIG.siteUrl}/concepts/${concept.id}`, + lastmod: today, + changefreq, + priority, + })); + } catch (error) { + console.error(" Error fetching concepts:", (error as Error).message); + return []; + } +} + +/** + * QUESTIONS + * + */ +async function getQuestionUrls(): Promise { + console.log("Fetching questions..."); + + try { + const response = await withRetry(() => + api.get>("/pages/questions/", { + params: { limit: API_CONFIG.pageLimit }, + }) + ); + + const questions = response.data.results || []; + console.log(` Found ${questions.length} questions`); + + const today = formatDate(new Date()); + const { priority, changefreq } = CONTENT_PRIORITIES.question; + + return questions + .filter((question) => question.id && question.published !== false) + .map((question) => ({ + loc: `${API_CONFIG.siteUrl}/questions/${question.id}`, + lastmod: today, + changefreq, + priority, + })); + } catch (error) { + console.error(" Error fetching questions:", (error as Error).message); + return []; + } +} + +/** + * EVENTS + * + */ +async function getEventUrls(): Promise { + console.log("Fetching events..."); + + try { + const response = await withRetry(() => + api.get<{ results: EventPage[]; totalResults: number }>("/events", { + params: { + limit: API_CONFIG.pageLimit, + show_active_only: false, + show_inactive_only: false, + }, + }) + ); + + const events = response.data.results || []; + console.log(` Found ${events.length} events`); + + const today = formatDate(new Date()); + const { priority, changefreq } = CONTENT_PRIORITIES.event; + + return events + .filter((event) => event.id) + .map((event) => ({ + loc: `${API_CONFIG.siteUrl}/events/${event.id}`, + lastmod: today, + changefreq, + priority, + })); + } catch (error) { + console.error(" Error fetching events:", (error as Error).message); + return []; + } +} + +/** + * Generate XML sitemap from URL list + */ +function generateXml(urls: SitemapURL[]): string { + const urlEntries = urls + .map( + (url) => ` + ${escapeXml(url.loc)} + ${url.lastmod} + ${url.changefreq} + ${url.priority.toFixed(1)} + ` + ) + .join("\n"); + + return ` + +${urlEntries} + +`; +} + +/** + * Remove duplicate URLs (keep highest priority) + */ +function deduplicateUrls(urls: SitemapURL[]): SitemapURL[] { + const urlMap = new Map(); + + for (const url of urls) { + const existing = urlMap.get(url.loc); + if (!existing || url.priority > existing.priority) { + urlMap.set(url.loc, url); + } + } + + return Array.from(urlMap.values()); +} + +/** + * Sort URLs by priority (descending) then alphabetically + * + */ +function sortUrls(urls: SitemapURL[]): SitemapURL[] { + return urls.sort((a, b) => { + if (b.priority !== a.priority) { + return b.priority - a.priority; + } + return a.loc.localeCompare(b.loc); + }); +} + +/** + * GENERATE SITEMAP + * + */ +async function generateSitemap(): Promise { + console.log("=== Isaac Computer Science Sitemap Generator ===\n"); + console.log(`API URL: ${API_CONFIG.baseUrl}`); + console.log(`Site URL: ${API_CONFIG.siteUrl}`); + console.log(`Output: ${OUTPUT_CONFIG.outputPath}\n`); + + const allUrls: SitemapURL[] = []; + + // Add STATIC + console.log("Adding static routes..."); + const staticUrls = getStaticUrls(); + console.log(` Added ${staticUrls.length} static URLs`); + allUrls.push(...staticUrls); + + // Add TOPICS + console.log("Adding topic URLs..."); + const topicUrls = getTopicUrls(); + console.log(` Added ${topicUrls.length} topic URLs`); + allUrls.push(...topicUrls); + + // Add DYNAMIC + const conceptUrls = await getConceptUrls(); + allUrls.push(...conceptUrls); + + const questionUrls = await getQuestionUrls(); + allUrls.push(...questionUrls); + + const eventUrls = await getEventUrls(); + allUrls.push(...eventUrls); + + // Deduplicate and sort + console.log("\nProcessing URLs..."); + const uniqueUrls = deduplicateUrls(allUrls); + const sortedUrls = sortUrls(uniqueUrls); + console.log(` Total unique URLs: ${sortedUrls.length}`); + + // Generate XML + console.log("\nGenerating XML..."); + const xml = generateXml(sortedUrls); + + // Write to file + const outputPath = path.resolve(process.cwd(), OUTPUT_CONFIG.outputPath); + const outputDir = path.dirname(outputPath); + + // Ensure output directory exists + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFileSync(outputPath, xml, "utf-8"); + console.log(`\nSitemap written to: ${outputPath}`); + console.log(`File size: ${(xml.length / 1024).toFixed(2)} KB`); + + // Summary + console.log("\n=== Summary ==="); + console.log(`Static routes: ${staticUrls.length}`); + console.log(`Topics: ${topicUrls.length}`); + console.log(`Concepts: ${conceptUrls.length}`); + console.log(`Questions: ${questionUrls.length}`); + console.log(`Events: ${eventUrls.length}`); + console.log(`Total URLs: ${sortedUrls.length}`); + console.log("\nDone!"); +} + +// Run the generator +generateSitemap().catch((error) => { + console.error("\nFatal error:", error.message); + process.exit(1); +}); diff --git a/scripts/sitemap-config.ts b/scripts/sitemap-config.ts new file mode 100644 index 0000000000..2a27ffa5e7 --- /dev/null +++ b/scripts/sitemap-config.ts @@ -0,0 +1,222 @@ +/** + * Sitemap Configuration + * + * Defines static routes, topic IDs, and sitemap generation settings + * + * Environment variable: + * ISAAC_ENV - "production" | "staging" | "local" (default: "local") + * Mirrors the envSpecific() pattern from src/app/services/constants.ts + * but uses a Node-compatible env var instead of document.location. + * + * Override individual URLs if needed: + * SITE_URL - overrides the site URL derived from ISAAC_ENV + * API_URL - overrides the API URL derived from ISAAC_ENV + * SITEMAP_OUTPUT - output path (default: public/sitemap.xml) + */ + +// Mirrors STAGING_URL from src/app/services/constants.ts +const PROD_SITE_URL = "https://isaaccomputerscience.org"; +const STAGING_SITE_URL = "https://www.staging.development.isaaccomputerscience.org"; + +type IsaacEnv = "production" | "staging" | "local"; + +function detectEnv(): IsaacEnv { + const env = process.env.ISAAC_ENV; + if (env === "production") return "production"; + if (env === "staging") return "staging"; + return "local"; +} + +// Node-compatible equivalent of envSpecific(live, staging, dev) from constants.ts +function envSpecific(live: L, staging: S, dev: D): L | S | D { + const env = detectEnv(); + if (env === "staging") return staging; + if (env === "production") return live; + return dev; +} + +const resolvedSiteUrl = + process.env.SITE_URL ?? + envSpecific(PROD_SITE_URL, STAGING_SITE_URL, PROD_SITE_URL); + +const resolvedApiUrl = + process.env.API_URL ?? + envSpecific( + `${PROD_SITE_URL}/api/any/api`, + `${STAGING_SITE_URL}/api/any/api`, + "http://localhost:8080/isaac-api/api", + ); + +export type ChangeFreq = "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"; + +export interface SitemapRoute { + path: string; + priority: number; + changefreq: ChangeFreq; +} + +export interface SitemapURL { + loc: string; + lastmod?: string; + changefreq: ChangeFreq; + priority: number; +} + +/** + * Static routes to include in the sitemap (hardcoded). + * + */ +export const STATIC_ROUTES: SitemapRoute[] = [ + // Homepage + { path: "/", priority: 1.0, changefreq: "daily" }, + + // Topic index pages + { path: "/topics", priority: 0.9, changefreq: "weekly" }, + { path: "/topics/gcse", priority: 0.9, changefreq: "weekly" }, + { path: "/topics/a_level", priority: 0.9, changefreq: "weekly" }, + + // Events + { path: "/events", priority: 0.8, changefreq: "daily" }, + + // Glossary + { path: "/glossary", priority: 0.7, changefreq: "monthly" }, + + // Student and teacher info pages + { path: "/students", priority: 0.7, changefreq: "monthly" }, + { path: "/teachers", priority: 0.7, changefreq: "monthly" }, + + // Competition page + { path: "/national-computer-science-competition", priority: 0.7, changefreq: "weekly" }, + + // Careers + { path: "/careers_in_computer_science", priority: 0.6, changefreq: "monthly" }, + + // Contact and support + { path: "/contact", priority: 0.6, changefreq: "monthly" }, + { path: "/support", priority: 0.5, changefreq: "monthly" }, + + // Static info pages + { path: "/about", priority: 0.5, changefreq: "monthly" }, + { path: "/privacy", priority: 0.4, changefreq: "yearly" }, + { path: "/terms", priority: 0.4, changefreq: "yearly" }, + { path: "/cookies", priority: 0.4, changefreq: "yearly" }, + { path: "/accessibility", priority: 0.4, changefreq: "yearly" }, + { path: "/safeguarding", priority: 0.4, changefreq: "yearly" }, + { path: "/teachcomputing", priority: 0.5, changefreq: "monthly" }, + { path: "/gcse_programming_challenges", priority: 0.5, changefreq: "monthly" }, +]; + +/** + * Topic IDs from TAG_ID enum in constants.ts + * These map to /topics/:topicName routes + */ +export const TOPIC_IDS: string[] = [ + // Computer networks & systems + "networking", + "network_hardware", + "communication", + "the_internet", + "web_technologies", + + "boolean_logic", + "architecture", + "memory_and_storage", + "hardware", + "software", + "operating_systems", + "translators", + "programming_languages", + + // Cyber security + "security", + "social_engineering", + "malicious_code", + + // Data and information + "number_representation", + "text_representation", + "image_representation", + "sound_representation", + "compression", + "encryption", + "databases", + "file_organisation", + "sql", + "big_data", + + // Data structures & algorithms + "searching", + "sorting", + "pathfinding", + "complexity", + "data_structures", + + // Impacts of digital technology + "legislation", + "impacts_of_tech", + + // Math + "number_systems", + "functions", + + // Programming fundamentals & paradigms + "programming_concepts", + "subroutines", + "files", + "recursion", + "string_handling", + "ide", + + "object_oriented_programming", + "functional_programming", + "event_driven_programming", + "procedural_programming", + + // Software engineering + "software_engineering_principles", + "program_design", + "testing", + "software_project", + + // Theory of computation + "computational_thinking", + "models_of_computation", +]; + +/** + * Hidden topics that should NOT be included in the sitemap. + * These are marked as hidden: true in tagsCS.ts + */ +export const HIDDEN_TOPICS: string[] = [ + "machine_learning_ai", + "graphs_for_ai", + "neural_networks", + "machine_learning", + "regression", + "declarative_programming", +]; + +export const CONTENT_PRIORITIES = { + topic: { priority: 0.8, changefreq: "weekly" as ChangeFreq }, + concept: { priority: 0.7, changefreq: "monthly" as ChangeFreq }, + question: { priority: 0.7, changefreq: "monthly" as ChangeFreq }, + event: { priority: 0.6, changefreq: "weekly" as ChangeFreq }, + page: { priority: 0.5, changefreq: "monthly" as ChangeFreq }, +}; + + +export const API_CONFIG = { + baseUrl: resolvedApiUrl, + siteUrl: resolvedSiteUrl, + timeout: 30000, // Request timeout in milliseconds + maxRetries: 3, // Max retries for failed requests + pageLimit: 9999, // Pagination limit for API requests +}; + +/** + * Output configuration + */ +export const OUTPUT_CONFIG = { + // Output path relative to project root + outputPath: process.env.SITEMAP_OUTPUT || "public/sitemap.xml", +}; diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000000..05bb5c1d00 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": false, + "moduleResolution": "node" + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/yarn.lock b/yarn.lock index e26470e484..1118c72277 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1091,6 +1091,13 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@discoveryjs/json-ext@0.5.7", "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -1519,6 +1526,11 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + "@jridgewell/resolve-uri@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" @@ -1547,6 +1559,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": version "0.3.25" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" @@ -1746,6 +1766,26 @@ path-browserify "^1.0.1" tinyglobby "^0.2.9" +"@tsconfig/node10@^1.0.7": + version "1.0.12" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/aria-query@^5.0.1": version "5.0.1" resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz" @@ -1984,6 +2024,11 @@ resolved "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz" integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== +"@types/js-yaml@^4.0.9": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" + integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== + "@types/jsdom@^20.0.0": version "20.0.1" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz" @@ -2040,6 +2085,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-20.4.6.tgz" integrity sha512-q0RkvNgMweWWIvSMDiXhflGUKMdIxBo2M2tYM/0kEGDueQByFzK4KZAgu5YHGFNxziTlppNpTIBcqHQAxlfHdA== +"@types/node@^20.11.0": + version "20.19.33" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.33.tgz#ac8364c623b72d43125f0e7dd722bbe968f0c65e" + integrity sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw== + dependencies: + undici-types "~6.21.0" + "@types/object-hash@^3.0.4": version "3.0.4" resolved "https://registry.npmjs.org/@types/object-hash/-/object-hash-3.0.4.tgz" @@ -2602,7 +2654,14 @@ acorn-walk@^8.0.0, acorn-walk@^8.0.2: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.11.0, acorn@^8.14.0, acorn@^8.15.0, acorn@^8.4.1, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.15.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -2702,6 +2761,11 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.10, argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" @@ -3553,6 +3617,11 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" @@ -4025,6 +4094,11 @@ diff-sequences@^29.6.3: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== +diff@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== + dijkstrajs@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz" @@ -6569,6 +6643,13 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + jsdom@^20.0.0: version "20.0.3" resolved "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz" @@ -9197,6 +9278,25 @@ ts-morph@^24.0.0: "@ts-morph/common" "~0.25.0" code-block-writer "^13.0.3" +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.6.2: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" @@ -9371,6 +9471,11 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -9478,6 +9583,11 @@ uuid@^9.0.1: resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-to-istanbul@^9.0.1: version "9.2.0" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz" @@ -9955,6 +10065,11 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.1.1" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"