From f8efc4112b2191e6f21a4681b360ab3f1f4f27e1 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Mon, 8 Jul 2024 17:28:38 +0000 Subject: [PATCH 01/25] Implement Eleventy-based build system (#3917) Re-implements the Ant/XSLT-based build system, with no changes to source HTML files. See PR for full list of fixes and a few behavioral changes, and 11ty/README.md for running instructions. --- .eleventyignore | 24 + .gitignore | 7 + .nvmrc | 1 + .prettierignore | 3 + .prettierrc | 4 + 11ty/CustomLiquid.ts | 561 +++ 11ty/README.md | 70 + 11ty/cheerio.ts | 64 + 11ty/common.ts | 30 + 11ty/cp-cvs.ts | 50 + 11ty/guidelines.ts | 220 ++ 11ty/techniques.ts | 244 ++ 11ty/types.ts | 55 + 11ty/understanding.ts | 89 + _includes/back-to-top.html | 7 + _includes/head.html | 8 + _includes/header.html | 46 + _includes/help-improve.html | 17 + _includes/sidebar.html | 8 + _includes/site-footer.html | 29 + _includes/techniques/about.html | 28 + .../techniques/applicability-association.html | 15 + _includes/techniques/applicability.html | 19 + _includes/techniques/h1.html | 1 + _includes/techniques/head.html | 1 + _includes/techniques/intro/resources.html | 1 + _includes/test-rules.html | 25 + _includes/toc.html | 38 + _includes/understanding/about.html | 9 + _includes/understanding/h1.html | 10 + _includes/understanding/head.html | 1 + _includes/understanding/intro/advisory.html | 15 + _includes/understanding/intro/failure.html | 3 + _includes/understanding/intro/resources.html | 1 + .../intro/sufficient-situation.html | 4 + _includes/understanding/intro/techniques.html | 8 + _includes/understanding/key-terms.html | 6 + _includes/understanding/navigation-index.html | 19 + _includes/understanding/navigation.html | 50 + _includes/understanding/success-criteria.html | 10 + _includes/wai-site-footer.html | 17 + _includes/waiscript.html | 29 + eleventy.config.ts | 208 ++ package-lock.json | 3045 +++++++++++++++++ package.json | 31 + techniques/techniques.11tydata.js | 7 + tsconfig.json | 11 + understanding/understanding.11tydata.js | 7 + 48 files changed, 5156 insertions(+) create mode 100644 .eleventyignore create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 11ty/CustomLiquid.ts create mode 100644 11ty/README.md create mode 100644 11ty/cheerio.ts create mode 100644 11ty/common.ts create mode 100644 11ty/cp-cvs.ts create mode 100644 11ty/guidelines.ts create mode 100644 11ty/techniques.ts create mode 100644 11ty/types.ts create mode 100644 11ty/understanding.ts create mode 100644 _includes/back-to-top.html create mode 100644 _includes/head.html create mode 100644 _includes/header.html create mode 100644 _includes/help-improve.html create mode 100644 _includes/sidebar.html create mode 100644 _includes/site-footer.html create mode 100644 _includes/techniques/about.html create mode 100644 _includes/techniques/applicability-association.html create mode 100644 _includes/techniques/applicability.html create mode 100644 _includes/techniques/h1.html create mode 100644 _includes/techniques/head.html create mode 100644 _includes/techniques/intro/resources.html create mode 100644 _includes/test-rules.html create mode 100644 _includes/toc.html create mode 100644 _includes/understanding/about.html create mode 100644 _includes/understanding/h1.html create mode 100644 _includes/understanding/head.html create mode 100644 _includes/understanding/intro/advisory.html create mode 100644 _includes/understanding/intro/failure.html create mode 100644 _includes/understanding/intro/resources.html create mode 100644 _includes/understanding/intro/sufficient-situation.html create mode 100644 _includes/understanding/intro/techniques.html create mode 100644 _includes/understanding/key-terms.html create mode 100644 _includes/understanding/navigation-index.html create mode 100644 _includes/understanding/navigation.html create mode 100644 _includes/understanding/success-criteria.html create mode 100644 _includes/wai-site-footer.html create mode 100644 _includes/waiscript.html create mode 100644 eleventy.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 techniques/techniques.11tydata.js create mode 100644 tsconfig.json create mode 100644 understanding/understanding.11tydata.js diff --git a/.eleventyignore b/.eleventyignore new file mode 100644 index 0000000000..59386af4f7 --- /dev/null +++ b/.eleventyignore @@ -0,0 +1,24 @@ +*.* +11ty/ +acknowledgements/ +conformance-challenges/ +guidelines/ +lib/ +requirements/ +script/ +wcag20/ +# working-examples is directly copied; it should not be processed as templates +working-examples/ +xslt/ + +# These files under understanding don't end up in output in the old build +understanding/*/accessibility-support-documenting.html +understanding/*/identify-changes.html +understanding/*/interruptions-minimum.html +understanding/*/seizures.html + +# Ignore templates used for creating new documents +**/*-template.html + +# HTML files under img will be passthrough-copied +**/img/* diff --git a/.gitignore b/.gitignore index 26d41dc54a..598250a1d5 100644 --- a/.gitignore +++ b/.gitignore @@ -221,6 +221,12 @@ pip-log.txt build.properties +####### +## Node +####### + +node_modules/ + ############# ## Output ############# @@ -238,3 +244,4 @@ build.properties /guidelines/wcag.xml /guidelines/versions.xml /guidelines/index-flat.html +/_site/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..209e3ef4b6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..2c49d868d2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +*.* +!*.ts +!*.11tydata.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..12c91d0119 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "printWidth": 100, + "trailingComma": "es5" +} diff --git a/11ty/CustomLiquid.ts b/11ty/CustomLiquid.ts new file mode 100644 index 0000000000..49e071fd7a --- /dev/null +++ b/11ty/CustomLiquid.ts @@ -0,0 +1,561 @@ +import type { Cheerio, Element } from "cheerio"; +import { Liquid, type Template } from "liquidjs"; +import type { RenderOptions } from "liquidjs/dist/liquid-options"; +import compact from "lodash-es/compact"; +import uniq from "lodash-es/uniq"; + +import { basename } from "path"; + +import type { GlobalData } from "eleventy.config"; + +import { flattenDom, load } from "./cheerio"; +import { generateId } from "./common"; +import { getTermsMap } from "./guidelines"; +import { resolveTechniqueIdFromHref, understandingToTechniqueLinkSelector } from "./techniques"; +import { techniqueToUnderstandingLinkSelector } from "./understanding"; + +const titleSuffix = " | WAI | W3C"; + +const indexPattern = /(techniques|understanding)\/(index|about)\.html$/; +const techniquesPattern = /\btechniques\//; +const understandingPattern = /\bunderstanding\//; + +const termsMap = await getTermsMap(); +const termLinkSelector = "a:not([href])"; + +/** Generates {% include "foo.html" %} directives from 1 or more basenames */ +const generateIncludes = (...basenames: string[]) => + `\n${basenames.map((basename) => `{% include "${basename}.html" %}`).join("\n")}\n`; + +/** + * Determines whether a given string is actually HTML, + * not e.g. a data value Eleventy sent to the templating engine. + */ +const isHtmlFileContent = (html: string) => !html.startsWith("(((11ty") && !html.endsWith(".html"); + +/** + * Performs common cleanup of in-page headings, and by extension, table of contents links, + * for final output. + */ +const normalizeHeading = (label: string) => + label + .trim() + .replace(/In brief/, "In Brief") + .replace(/^(\S+) (of|for) .*$/, "$1") + .replace(/^Techniques and Failures .*$/, "Techniques") + .replace(/^Specific Benefits .*$/, "Benefits") + .replace(/^.* Examples$/, "Examples") + .replace(/^(Related )?Resources$/i, "Related Resources"); + +/** + * Performs additional common cleanup for table of contents links for final output. + * This is expected to piggyback off of normalizeHeading, which should be called on + * headings prior to processing the table of contents from them. + */ +const normalizeTocLabel = (label: string) => + label.replace(/ for this Guideline$/, "").replace(/ \(SC\)$/, ""); + +/** + * Replaces a link with a technique URL with a Liquid tag which will + * expand to a link with the full technique ID and title. + * @param $el a $()-wrapped link element + */ +function expandTechniqueLink($el: Cheerio) { + const href = $el.attr("href"); + if (!href) throw new Error("expandTechniqueLink: non-link element encountered"); + const id = resolveTechniqueIdFromHref(href); + // id will be empty string for links to index, which we don't need to modify + if (id) $el.replaceWith(`{{ "${id}" | linkTechniques }}`); +} + +const stripHtmlComments = (html: string) => html.replace(//g, ""); + +// Dev note: Eleventy doesn't expose typings for its template engines for us to neatly extend. +// Fortunately, it passes both the content string and the file path through to Liquid#parse: +// https://github.com/11ty/eleventy/blob/9c3a7619/src/Engines/Liquid.js#L253 + +/** + * Liquid class extension that adds support for parts of the existing build process: + * - flattening data-include directives prior to parsing Liquid tags + * (permitting Liquid even inside data-included files) + * - inserting header/footer content within the body of pages + * - generating/expanding sections with auto-generated content + */ +export class CustomLiquid extends Liquid { + public parse(html: string, filepath?: string) { + // Filter out Liquid calls for computed data and includes themselves + if (filepath && !filepath.includes("_includes/") && isHtmlFileContent(html)) { + /** Matches paths that would go through process-index.xslt in previous process */ + const isIndex = indexPattern.test(filepath); + const isTechniques = techniquesPattern.test(filepath); + const isUnderstanding = understandingPattern.test(filepath); + + const $ = flattenDom(html, filepath); + + // Clean out elements to be removed + // (e.g. editors.css & sources.css, and leftover template paragraphs) + // NOTE: some paragraphs with the "instructions" class actually have custom content, + // but for now this remains consistent with the XSLT process by stripping all of them. + $(".remove, p.instructions, section#meta, section.meta").remove(); + + const prependedIncludes = ["header"]; + const appendedIncludes = ["wai-site-footer", "site-footer"]; + + if (isUnderstanding) + prependedIncludes.push( + isIndex ? "understanding/navigation-index" : "understanding/navigation" + ); + + if (isIndex) { + if (isTechniques) $("section#changelog li a").each((_, el) => expandTechniqueLink($(el))); + } else { + $("head").append(generateIncludes("head")); + appendedIncludes.push("waiscript"); + + // Remove resources section if it only has a placeholder item + const $resourcesOnlyItem = $("section#resources li:only-child"); + if ( + $resourcesOnlyItem.length && + ($resourcesOnlyItem.html() === "Resource" || $resourcesOnlyItem.html() === "Link") + ) + $("section#resources").remove(); + + // Fix incorrect level-2 and first-child level-3 headings + // (avoid changing h3s that are appropriate but aren't nested within a subsection) + $("body > section section h2").each((_, el) => { + el.tagName = "h3"; + }); + $("body > section > h3:first-child").each((_, el) => { + el.tagName = "h2"; + }); + + if (isTechniques) { + // Remove any effectively-empty techniques/resources sections (from template) + $("section#related:not(:has(a))").remove(); + $("section#resources:not(:has(a, li))").remove(); + // Expand related technique links to include full title + // (the XSLT process didn't handle this in this particular context) + const siblingCode = basename(filepath).replace(/^([A-Z]+).*$/, "$1"); + $("section#related li") + .find(`a[href^='../'], a[href^=${siblingCode}]`) + .each((_, el) => expandTechniqueLink($(el))); + + // XSLT orders related and tests last, but they are not last in source files + $("body") + .append("\n", $(`body > section#related`)) + .append("\n", $(`body > section#tests`)); + + $("h1") + .after(generateIncludes("techniques/about")) + .replaceWith(generateIncludes("techniques/h1")); + + const sectionCounts: Record = {}; + let hasDuplicates = false; + $("body > section[id]").each((_, el) => { + const id = el.attribs.id.toLowerCase(); + // Fix non-lowercase top-level section IDs (e.g. H99) + el.attribs.id = id; + // Track duplicate sections, to be processed next + if (id in sectionCounts) { + hasDuplicates = true; + sectionCounts[id]++; + } else { + sectionCounts[id] = 1; + } + }); + + // Avoid loop altogether in majority of (correct) cases + if (hasDuplicates) { + for (const [id, count] of Object.entries(sectionCounts)) { + if (count === 1) continue; + console.warn( + `${filepath}: Merging duplicate ${id} sections; please fix this in the source file.` + ); + const $sections = $(`section[id='${id}']`); + const $first = $sections.first(); + $sections.each((i, el) => { + if (i === 0) return; + const $el = $(el); + $el.find("> h2:first-child").remove(); + $first.append($el.contents()); + $el.remove(); + }); + } + } + + $("section#resources h2").after(generateIncludes("techniques/intro/resources")); + $("section#examples section.example").each((i, el) => { + const $el = $(el); + const exampleText = `Example ${i + 1}`; + // Check for multiple h3 under one example, which should be h4 (e.g. SCR33) + $el.find("h3:not(:only-of-type)").each((_, el) => { + el.tagName = "h4"; + }); + + const $h3 = $el.find("h3"); + if ($h3.length) { + const h3Text = $h3.text(); // Used for comparisons below + // Some examples really have an empty h3... + if (!h3Text) $h3.text(exampleText); + // Only prepend "Example N: " if it won't be redundant (e.g. C31, F83) + else if (!/example \d+$/i.test(h3Text) && !/^example \d+/i.test(h3Text)) + $h3.prepend(`${exampleText}: `); + } else { + $el.prepend(`

${exampleText}

`); + } + }); + } else if (isUnderstanding) { + // Add numbers to figcaptions + $("figcaption").each((i, el) => { + const $el = $(el); + if (!$el.find("p").length) $el.wrapInner("

"); + $el.prepend(`Figure ${i + 1}`); + }); + + // Remove spurious copy-pasted content in 2.5.3 that doesn't belong there + if ($("section#benefits").length > 1) $("section#benefits").first().remove(); + // Some pages nest Benefits inside Intent; XSLT always pulls it back out + $("section#intent section#benefits") + .insertAfter("section#intent") + .find("h3:first-child") + .each((_, el) => { + el.tagName = "h2"; + }); + + // XSLT orders resources then techniques last, opposite of source files + $("body") + .append("\n", $(`body > section#resources`)) + .append("\n", $(`body > section#techniques`)); + + // Expand top-level heading and add box for guideline/SC pages + if ($("section#intent").length) $("h1").replaceWith(generateIncludes("understanding/h1")); + $("section#intent").before(generateIncludes("understanding/about")); + + $("section#techniques h2").after(generateIncludes("understanding/intro/techniques")); + if ($("section#sufficient .situation").length) { + $("section#sufficient h3").after( + generateIncludes("understanding/intro/sufficient-situation") + ); + } + // success-criteria section should be auto-generated; + // remove any handwritten ones (e.g. Input Modalities) + const $successCriteria = $("section#success-criteria"); + if ($successCriteria.length) { + console.warn( + `${filepath}: success-criteria section will be replaced with ` + + "generated version; please remove this from the source file." + ); + $successCriteria.remove(); + } + // success-criteria template only renders content for guideline (not SC) pages + $("body").append(generateIncludes("understanding/success-criteria")); + + // Remove unpopulated techniques subsections + for (const id of ["sufficient", "advisory", "failure"]) { + $(`section#${id}:not(:has(:not(h3)))`).remove(); + } + + // Normalize subsection names for Guidelines (h2) and/or SC (h3) + $("section#sufficient h3").text("Sufficient Techniques"); + $("section#advisory").find("h2, h3").text("Advisory Techniques"); + $("section#failure h3").text("Failures"); + + // Add intro prose to populated sections + $("section#advisory") + .find("h2, h3") + .after(generateIncludes("understanding/intro/advisory")); + $("section#failure h3").after(generateIncludes("understanding/intro/failure")); + $("section#resources h2").after(generateIncludes("understanding/intro/resources")); + + // Expand techniques links to always include title + $(understandingToTechniqueLinkSelector).each((_, el) => expandTechniqueLink($(el))); + + // Add key terms by default, to be removed in #parse if there are no terms + $("body").append(generateIncludes("understanding/key-terms")); + } + + // Remove h2-level sections with no content other than heading + $("body > section:not(:has(:not(h2)))").remove(); + + $("body") + .attr("dir", "ltr") // Already included in index/about pages + .append(generateIncludes("test-rules", "back-to-top")) + .wrapInner(`
`) + .prepend(generateIncludes("sidebar")) + .append(generateIncludes("help-improve")) + // index/about pages already include this wrapping; others do not, and need it. + // This wraps around table of contents & help improve, but not other includes + .wrapInner(`
`); + } + + $("body") + .prepend(generateIncludes(...prependedIncludes)) + .append(generateIncludes(...appendedIncludes)); + + return super.parse($.html(), filepath); + } + return super.parse(html); + } + + public async render(templates: Template[], scope: GlobalData, options?: RenderOptions) { + // html contains markup after Liquid tags/includes have been processed + const html = (await super.render(templates, scope, options)).toString(); + if (!isHtmlFileContent(html) || !scope) return html; + + const $ = load(html); + + if (!indexPattern.test(scope.page.inputPath)) { + if (scope.isTechniques) { + $("title").text(`${scope.technique.id}: ${scope.technique.title}${titleSuffix}`); + const aboutBoxSelector = "section#technique .box-i"; + + // Strip applicability paragraphs with metadata IDs (e.g. H99) + $("section#applicability").find("p#id, p#technology, p#type").remove(); + // Check for custom applicability paragraph before removing the section + const customApplicability = $("section#applicability p") + .html() + ?.trim() + .replace(/^th(e|is) (technique|failure)s? (is )?/i, "") + .replace(/^general( technique|ly applicable)?(\.|$).*$/i, "all technologies") + .replace(/^appropriate to use for /i, "") + .replace(/^use this technique on /i, "") + // Work around redundant sentences (e.g. F105) + .replace(/\.\s+This technique relates to Success Criterion [\d\.]+\d[^\.]+\.$/, ""); + if (customApplicability) { + const appliesPattern = /^(?:appli(?:es|cable)|relates) (to|when(?:ever)?)\s*/i; + const rephrasedApplicability = customApplicability.replace(appliesPattern, ""); + + // Failure pages have no default applicability paragraph, so append one first + if (scope.technique.technology === "failures") + $("section#technique .box-i").append("

"); + + const noun = scope.technique.technology === "failures" ? "failure" : "technique"; + const appliesMatch = appliesPattern.exec(customApplicability); + const connector = /^not/.test(customApplicability) + ? "is" + : `applies ${appliesMatch?.[1] || "to"}`; + $("section#technique .box-i p:last-child").html( + `This ${noun} ${connector} ` + + // Uncapitalize original sentence, except for all-caps abbreviations or titles + (/^[A-Z]{2,}/.test(rephrasedApplicability) || + /^([A-Z][a-z]+(\s+|\.?$))+(\/|$)/.test(rephrasedApplicability) + ? rephrasedApplicability + : rephrasedApplicability[0].toLowerCase() + rephrasedApplicability.slice(1)) + + (/(\.|:)$/.test(rephrasedApplicability) ? "" : ".") + ); + + // Append any relevant subsequent paragraphs or lists from applicability section + const $additionalApplicability = $("section#applicability").find( + "p:not(:first-of-type), ul, ol" + ); + const additionalApplicabilityText = $additionalApplicability.text(); + const excludes = [ + "None listed.", // Template filler + "This technique relates to:", // Redundant of auto-generated content + ]; + if (excludes.every((exclude) => !additionalApplicabilityText.includes(exclude))) + $additionalApplicability.appendTo(aboutBoxSelector); + } + $("section#applicability").remove(); + + if (scope.technique.technology === "flash") { + $(aboutBoxSelector).append( + "

Note: Adobe has plans to stop updating and distributing the Flash Player at the end of 2020, " + + "and encourages authors interested in creating accessible web content to use HTML.

" + ); + } else if (scope.technique.technology === "silverlight") { + $(aboutBoxSelector).append( + "

Note: Microsoft has stopped updating and distributing Silverlight, " + + "and authors are encouraged to use HTML for accessible web content.

" + ); + } + + // Update understanding links to always use base URL + // (mainly to avoid any case-sensitivity issues) + $(techniqueToUnderstandingLinkSelector).each((_, el) => { + el.attribs.href = el.attribs.href.replace(/^.*\//, scope.understandingUrl); + }); + } else if (scope.isUnderstanding) { + const $title = $("title"); + if (scope.guideline) { + const type = scope.guideline.type === "SC" ? "Success Criterion" : scope.guideline.type; + $title.text( + `Understanding ${type} ${scope.guideline.num}: ${scope.guideline.name}${titleSuffix}` + ); + } else { + $title.text( + $title.text().replace(/WCAG 2( |$)/, `WCAG ${scope.versionDecimal}$1`) + titleSuffix + ); + } + } + + // Process defined terms within #render, + // where we have access to global data and the about box's HTML + const $termLinks = $(termLinkSelector); + const extractTermName = ($el: Cheerio) => { + const name = $el.text().trim().toLowerCase(); + const term = termsMap[name]; + if (!term) { + console.warn(`${scope.page.inputPath}: Term not found: ${name}`); + return; + } + // Return standardized name for Key Terms definition lists + return term.name; + }; + + if (scope.isTechniques) { + $termLinks.each((_, el) => { + const $el = $(el); + const termName = extractTermName($el); + $el + .attr("href", `${scope.guidelinesUrl}#${termName ? termsMap[termName].trId : ""}`) + .attr("target", "terms"); + }); + } else if (scope.isUnderstanding) { + const $termsList = $("section#key-terms dl"); + const extractTermNames = ($links: Cheerio) => + compact(uniq($links.toArray().map((el) => extractTermName($(el))))); + + if ($termLinks.length) { + let termNames = extractTermNames($termLinks); + // This is one loop but effectively multiple passes, + // since terms may reference other terms in their own definitions. + // Each iteration may append to termNames. + for (let i = 0; i < termNames.length; i++) { + const term = termsMap[termNames[i]]; + if (!term) continue; // This will already warn via extractTermNames + + const $definition = load(term.definition); + const $definitionTermLinks = $definition(termLinkSelector); + if ($definitionTermLinks.length) { + termNames = uniq(termNames.concat(extractTermNames($definitionTermLinks))); + } + } + + // Iterate over sorted names to populate alphabetized Key Terms definition list + termNames.sort(); + for (const name of termNames) { + const term = termsMap[name]; // Already verified existence in the earlier loop + $termsList.append( + `
${term.name}
` + + `
${term.definition}
` + ); + } + + // Iterate over non-href links once more in now-expanded document to add hrefs + $(termLinkSelector).each((_, el) => { + const name = extractTermName($(el)); + el.attribs.href = `#${name ? termsMap[name].id : ""}`; + }); + } else { + // No terms: remove skeleton that was placed in #parse + $("section#key-terms").remove(); + } + } + + // Remove items that end up empty due to invalid technique IDs during #parse + // (e.g. removed/deprecated) + if (scope.isTechniques) { + $("section#related li:empty").remove(); + } else if (scope.isUnderstanding) { + // :empty doesn't work here since there may be whitespace + // (can't trim whitespace in the liquid tag since some links have more text after) + $(`section#techniques li`) + .filter((_, el) => !$(el).text().trim()) + .remove(); + + // Prepend guidelines base URL to non-dfn anchor links in guidelines-derived content + // (including both the guideline/SC box at the top and Key Terms at the bottom) + $("#guideline, #success-criterion, #key-terms") + .find("a[href^='#']:not([href^='#dfn-'])") + .each((_, el) => { + el.attribs.href = scope.guidelinesUrl + el.attribs.href; + }); + } + } + + // Expand note paragraphs after parsing and rendering, + // after Guideline/SC content for Understanding pages is rendered. + // (This is also needed for techniques/about) + $("div.note").each((_, el) => { + const $el = $(el); + $el.replaceWith(`
+

Note

+
${$el.html()}
+
`); + }); + // Handle p variant after div (the reverse would double-process) + $("p.note").each((_, el) => { + const $el = $(el); + $el.replaceWith(`
+

Note

+

${$el.html()}

+
`); + }); + + // We don't need to do any more processing for index/about pages other than stripping comments + if (indexPattern.test(scope.page.inputPath)) return stripHtmlComments($.html()); + + // Handle new-in-version content + $("[class^='wcag']").each((_, el) => { + // Just like the XSLT process, this naively assumes that version numbers are the same length + const classVersion = +el.attribs.class.replace(/^wcag/, ""); + const buildVersion = +scope.version; + if (isNaN(classVersion)) throw new Error(`Invalid wcagXY class found: ${el.attribs.class}`); + if (classVersion > buildVersion) { + $(el).remove(); + } else if (classVersion === buildVersion) { + $(el).prepend(`New in WCAG ${scope.versionDecimal}: `); + } + // Output as-is if content pertains to a version older than what's being built + }); + + if (!scope.isUnderstanding || scope.guideline) { + // Fix inconsistent heading labels + // (another pass is done on top of this for table of contents links below) + $("h2").each((_, el) => { + const $el = $(el); + $el.text(normalizeHeading($el.text())); + }); + } + + // Allow autogenerating missing top-level section IDs in understanding docs, + // but don't pick up incorrectly-nested sections in some techniques pages (e.g. H91) + const sectionSelector = scope.isUnderstanding ? "section" : "section[id]"; + const sectionH2Selector = "h2:first-child"; + const $h2Sections = $(`${sectionSelector}:has(${sectionH2Selector})`); + if ($h2Sections.length) { + // Generate table of contents after parsing and rendering, + // when we have sections and sidebar skeleton already reordered + const $tocList = $(".sidebar nav ul"); + $h2Sections.each((_, el) => { + if (!el.attribs.id) el.attribs.id = generateId($(el).find(sectionH2Selector).text()); + $("") + .attr("href", `#${el.attribs.id}`) + .text(normalizeTocLabel($(el).find(sectionH2Selector).text())) + .appendTo($tocList) + .wrap("
  • "); + $tocList.append("\n"); + }); + } else { + // Remove ToC sidebar that was added in #parse if there's nothing to list in it + $(".sidebar").remove(); + } + + // Autogenerate remaining IDs after constructing table of contents. + // NOTE: This may overwrite some IDs set in HTML (for techniques examples), + // and may result in duplicates; this is consistent with the XSLT process. + const sectionHeadingSelector = ["h3", "h4", "h5"] + .map((tag) => `> ${tag}:first-child`) + .join(", "); + const autoIdSectionSelectors = ["section:not([id])"]; + if (scope.isTechniques) autoIdSectionSelectors.push("section.example"); + $(autoIdSectionSelectors.join(", ")) + .filter(`:has(${sectionHeadingSelector})`) + .each((_, el) => { + el.attribs.id = generateId($(el).find(sectionHeadingSelector).text()); + }); + + return stripHtmlComments($.html()); + } +} diff --git a/11ty/README.md b/11ty/README.md new file mode 100644 index 0000000000..35b9ca85d8 --- /dev/null +++ b/11ty/README.md @@ -0,0 +1,70 @@ +# Eleventy Infrastructure for WCAG Techniques and Understanding + +This subdirectory contains ES Modules re-implementing pieces of the +XSLT-based build process using Eleventy. + +## Usage + +Make sure you have Node.js installed. This has primarily been tested with v20, +the current LTS at time of writing. + +If you use [fnm](https://github.com/Schniz/fnm) or [nvm](https://github.com/nvm-sh/nvm) to manage multiple Node.js versions, +you can switch to the recommended version by typing `fnm use` or `nvm use` +(with no additional arguments) while in the repository directory. + +Otherwise, you can download an installer from [nodejs.org](https://nodejs.org/). + +First, run `npm i` in the root directory of the repository to install dependencies. + +Common tasks: + +- `npm run build` runs a one-time build +- `npm start` runs a local server with hot-reloading to preview changes as you make them: + - http://localhost:8080/techniques + - http://localhost:8080/understanding + +Maintenance tasks (for working with Eleventy config and supporting files under this subdirectory): + +- `npm run check` checks for TypeScript errors +- `npm run fmt` formats all TypeScript files + +## Environment Variables + +### `WCAG_CVSDIR` + +**Usage context:** `publish-w3c` script only + +Indicates top-level path of W3C CVS checkout, for WAI site updates (via `publish-w3c` script). + +**Default:** `../../../w3ccvs` (same as in Ant/XSLT build process) + +### `WCAG_VERSION` + +**Usage context:** `publish-w3c` script only; +this should currently not be changed, pending future improvements to `21` support. + +Indicates WCAG version being built, in `XY` format (i.e. no `.`) + +**Default:** `22` + +### `WCAG_MODE` + +**Usage context:** should not need to be used manually except in specific testing scenarios + +Influences base URLs for links to guidelines, techniques, and understanding pages. +Typically set by specific npm scripts or CI processes. + +Possible values: + +- Unset **(default)** - Sets base URLs appropriate for local testing +- `editors` - Sets base URLs appropriate for `gh-pages` publishing; used by deploy action +- `publication` - Sets base URLs appropriate for WAI site publishing; used by `publish-w3c` script + +## Other points of interest + +- The main configuration can be found in top-level `eleventy.config.ts` +- Build commands are defined in top-level `package.json` under `scripts`, + and can be run via `npm run ` +- If you see files named `*.11tydata.js`, these contribute data to the Eleventy build + (see Template and Directory Data files under + [Sources of Data](https://www.11ty.dev/docs/data/#sources-of-data)) diff --git a/11ty/cheerio.ts b/11ty/cheerio.ts new file mode 100644 index 0000000000..f1292be14b --- /dev/null +++ b/11ty/cheerio.ts @@ -0,0 +1,64 @@ +import { load, type CheerioOptions } from "cheerio"; +import { readFileSync } from "fs"; +import { readFile } from "fs/promises"; +import { dirname, resolve } from "path"; + +export { load } from "cheerio"; + +/** Convenience function that combines readFile and load. */ +export const loadFromFile = async ( + inputPath: string, + options?: CheerioOptions | null, + isDocument?: boolean +) => load(await readFile(inputPath, "utf8"), options, isDocument); + +/** + * Retrieves content for a data-include, either from _includes, + * or relative to the input file. + * Operates synchronously for simplicity of use within Cheerio callbacks. + * + * @param includePath A data-include attribute value + * @param inputPath Path (relative to repo root) to file containing the directive + * @returns + */ +function readInclude(includePath: string, inputPath: string) { + const relativePath = resolve(dirname(inputPath), includePath); + if (includePath.startsWith("..")) return readFileSync(relativePath, "utf8"); + + try { + // Prioritize any match under _includes (e.g. over local toc.html built via XSLT) + return readFileSync(resolve("_includes", includePath), "utf8"); + } catch (error) { + return readFileSync(relativePath, "utf8"); + } +} + +/** + * Resolves data-include directives in the given file, a la flatten-document.xslt. + * This is a lower-level version for use in Eleventy configuration; + * you'd probably rather use flattenDomFromFile in other cases. + * + * @param content String containing HTML to process + * @param inputPath Path (relative to repo root) to file containing the HTML + * (needed for data-include resolution) + * @returns Cheerio instance containing "flattened" DOM + */ +export function flattenDom(content: string, inputPath: string) { + const $ = load(content); + + $("body [data-include]").each((_, el) => { + const replacement = readInclude(el.attribs["data-include"], inputPath); + // Replace entire element or children, depending on data-include-replace + if (el.attribs["data-include-replace"]) $(el).replaceWith(replacement); + else $(el).removeAttr("data-include").html(replacement); + }); + + return $; +} + +/** + * Convenience version of flattenDom that requires only inputPath to be passed. + * @see flattenDom + */ +export const flattenDomFromFile = async (inputPath: string) => + flattenDom(await readFile(inputPath, "utf8"), inputPath); diff --git a/11ty/common.ts b/11ty/common.ts new file mode 100644 index 0000000000..ade5d87f14 --- /dev/null +++ b/11ty/common.ts @@ -0,0 +1,30 @@ +/** @fileoverview Common functions used by multiple parts of the build process */ + +import type { Guideline, Principle, SuccessCriterion } from "./guidelines"; + +/** Generates an ID for heading permalinks. Equivalent to wcag:generate-id in base.xslt. */ +export function generateId(title: string) { + if (title === "Parsing (Obsolete and removed)") return "parsing"; + return title + .replace(/\s+/g, "-") + .replace(/[,\():]+/g, "") + .toLowerCase(); +} + +/** Given a string "xy", returns "x.y" */ +export const resolveDecimalVersion = (version: `${number}`) => version.split("").join("."); + +/** Sort function for ordering WCAG principle/guideline/SC numbers ascending */ +export function wcagSort( + a: Principle | Guideline | SuccessCriterion, + b: Principle | Guideline | SuccessCriterion +) { + const aParts = a.num.split(".").map((n) => +n); + const bParts = b.num.split(".").map((n) => +n); + + for (let i = 0; i < 3; i++) { + if (aParts[i] > bParts[i] || (aParts[i] && !bParts[i])) return 1; + if (aParts[i] < bParts[i] || (bParts[i] && !aParts[i])) return -1; + } + return 0; +} diff --git a/11ty/cp-cvs.ts b/11ty/cp-cvs.ts new file mode 100644 index 0000000000..2bae08c4e7 --- /dev/null +++ b/11ty/cp-cvs.ts @@ -0,0 +1,50 @@ +/** @fileoverview script to copy already-built output to CVS subfolders */ + +import { copyFile, unlink } from "fs/promises"; +import { glob } from "glob"; +import { mkdirp } from "mkdirp"; + +import { dirname, join } from "path"; + +const outputBase = "_site"; +const cvsBase = process.env.WCAG_CVSDIR || "../../../w3ccvs"; +const wcagVersion = process.env.WCAG_VERSION || "22"; +const wcagBase = `${cvsBase}/WWW/WAI/WCAG${wcagVersion}`; + +// Map (git) sources to (CVS) destinations, since some don't match case-sensitively +const dirs = { + techniques: "Techniques", + understanding: "Understanding", + "working-examples": "working-examples", +}; + +for (const [srcDir, destDir] of Object.entries(dirs)) { + const cleanPaths = await glob(`**`, { + cwd: join(wcagBase, destDir), + ignore: ["**/CVS/**"], + nodir: true, + }); + + for (const path of cleanPaths) await unlink(join(wcagBase, destDir, path)); + + const indexPaths = await glob(`**/index.html`, { cwd: join(outputBase, srcDir) }); + const nonIndexPaths = await glob(`**`, { + cwd: join(outputBase, srcDir), + ignore: ["**/index.html"], + nodir: true, + }); + + for (const path of indexPaths) { + const srcPath = join(outputBase, srcDir, path); + const destPath = join(wcagBase, destDir, path.replace(/index\.html$/, "Overview.html")); + await mkdirp(dirname(destPath)); + await copyFile(srcPath, destPath); + } + + for (const path of nonIndexPaths) { + const srcPath = join(outputBase, srcDir, path); + const destPath = join(wcagBase, destDir, path); + await mkdirp(dirname(destPath)); + await copyFile(srcPath, destPath); + } +} diff --git a/11ty/guidelines.ts b/11ty/guidelines.ts new file mode 100644 index 0000000000..5b081fc6bb --- /dev/null +++ b/11ty/guidelines.ts @@ -0,0 +1,220 @@ +import type { Cheerio, Element } from "cheerio"; +import { glob } from "glob"; + +import { readFile } from "fs/promises"; +import { basename } from "path"; + +import { flattenDomFromFile, load } from "./cheerio"; +import { generateId } from "./common"; + +export type WcagVersion = "20" | "21" | "22"; +function assertIsWcagVersion(v: string): asserts v is WcagVersion { + if (!/^2[012]$/.test(v)) throw new Error(`Unexpected version found: ${v}`); +} + +/** + * Interface describing format of entries in guidelines/act-mapping.json + */ +interface ActRule { + deprecated: boolean; + permalink: string; + proposed: boolean; + successCriteria: string[]; + title: string; + wcagTechniques: string[]; +} + +type ActMapping = { + "act-rules": ActRule[]; +}; + +/** Data used for test-rules sections, from act-mapping.json */ +export const actRules = ( + JSON.parse(await readFile("guidelines/act-mapping.json", "utf8")) as ActMapping +)["act-rules"]; + +/** + * Returns an object with keys for each existing WCAG 2 version, + * each mapping to an array of basenames of HTML files under understanding/ + * (Functionally equivalent to "guidelines-versions" target in build.xml) + */ +export async function getGuidelinesVersions() { + const paths = await glob("*/*.html", { cwd: "understanding" }); + const versions: Record = { "20": [], "21": [], "22": [] }; + + for (const path of paths) { + const [version, filename] = path.split("/"); + assertIsWcagVersion(version); + versions[version].push(basename(filename, ".html")); + } + + for (const version of Object.keys(versions)) { + assertIsWcagVersion(version); + versions[version].sort(); + } + return versions; +} + +/** + * Like getGuidelinesVersions, but mapping each basename to the version it appears in + */ +export async function getInvertedGuidelinesVersions() { + const versions = await getGuidelinesVersions(); + const invertedVersions: Record = {}; + for (const [version, basenames] of Object.entries(versions)) { + for (const basename of basenames) { + invertedVersions[basename] = version; + } + } + return invertedVersions; +} + +export interface DocNode { + id: string; + name: string; + /** Helps distinguish entity type when passed out-of-context; used for navigation */ + type?: "Principle" | "Guideline" | "SC"; +} + +export interface Principle extends DocNode { + content: string; + num: `${number}`; // typed as string for consistency with guidelines/SC + version: "WCAG20"; + guidelines: Guideline[]; +} + +export interface Guideline extends DocNode { + content: string; + num: `${Principle["num"]}.${number}`; + version: `WCAG${"20" | "21"}`; + successCriteria: SuccessCriterion[]; +} + +export interface SuccessCriterion extends DocNode { + content: string; + num: `${Guideline["num"]}.${number}`; + /** Level may be empty for obsolete criteria */ + level: "A" | "AA" | "AAA" | ""; + version: `WCAG${WcagVersion}`; +} + +export function isSuccessCriterion(criterion: any): criterion is SuccessCriterion { + return !!(criterion?.type === "SC" && "level" in criterion); +} + +/** + * Returns HTML content used for Understanding guideline/SC boxes. + * @param $el Cheerio element of the full section from flattened guidelines/index.html + */ +const getContentHtml = ($el: Cheerio) => { + // Load HTML into a new instance, remove elements we don't want, then return the remainder + const $ = load($el.html()!, null, false); + $("h1, h2, h3, h4, h5, h6, section, .change, .conformance-level").remove(); + return $.html(); +}; + +/** + * Resolves information from guidelines/index.html; + * comparable to the principles section of wcag.xml from the guidelines-xml Ant task. + */ +export async function getPrinciples() { + const versions = await getInvertedGuidelinesVersions(); + const $ = await flattenDomFromFile("guidelines/index.html"); + + const principles: Principle[] = []; + $(".principle").each((i, el) => { + const guidelines: Guideline[] = []; + $(".guideline", el).each((j, guidelineEl) => { + const successCriteria: SuccessCriterion[] = []; + $(".sc", guidelineEl).each((k, scEl) => { + const resolvedVersion = versions[scEl.attribs.id]; + assertIsWcagVersion(resolvedVersion); + + successCriteria.push({ + content: getContentHtml($(scEl)), + id: scEl.attribs.id, + name: $("h4", scEl).text().trim(), + num: `${i + 1}.${j + 1}.${k + 1}`, + level: $("p.conformance-level", scEl).text().trim() as SuccessCriterion["level"], + type: "SC", + version: `WCAG${resolvedVersion}`, + }); + }); + + guidelines.push({ + content: getContentHtml($(guidelineEl)), + id: guidelineEl.attribs.id, + name: $("h3", guidelineEl).text().trim(), + num: `${i + 1}.${j + 1}`, + type: "Guideline", + version: guidelineEl.attribs.id === "input-modalities" ? "WCAG21" : "WCAG20", + successCriteria, + }); + }); + + principles.push({ + content: getContentHtml($(el)), + id: el.attribs.id, + name: $("h2", el).text().trim(), + num: `${i + 1}`, + type: "Principle", + version: "WCAG20", + guidelines, + }); + }); + + return principles; +} + +/** + * Returns a flattened object hash, mapping shortcodes to each principle/guideline/SC. + */ +export function getFlatGuidelines(principles: Principle[]) { + const map: Record = {}; + for (const principle of principles) { + map[principle.id] = principle; + for (const guideline of principle.guidelines) { + map[guideline.id] = guideline; + for (const criterion of guideline.successCriteria) { + map[criterion.id] = criterion; + } + } + } + return map; +} +export type FlatGuidelinesMap = ReturnType; + +interface Term { + definition: string; + /** generated id for use in Understanding pages */ + id: string; + name: string; + /** id of dfn in TR, which matches original id in terms file */ + trId: string; +} + +/** + * Resolves term definitions from guidelines/index.html organized for lookup by name; + * comparable to the term elements in wcag.xml from the guidelines-xml Ant task. + */ +export async function getTermsMap() { + const $ = await flattenDomFromFile("guidelines/index.html"); + const terms: Record = {}; + + $("dfn").each((_, el) => { + const $el = $(el); + const term: Term = { + // Note: All applicable s have explicit id attributes for TR, + // but the XSLT process generates id from the element's text which is not always the same + id: `dfn-${generateId($el.text())}`, + definition: getContentHtml($el.parent().next()), + name: $el.text().toLowerCase(), + trId: el.attribs.id, + }; + + const names = [term.name].concat((el.attribs["data-lt"] || "").toLowerCase().split("|")); + for (const name of names) terms[name] = term; + }); + + return terms; +} diff --git a/11ty/techniques.ts b/11ty/techniques.ts new file mode 100644 index 0000000000..afa87ec3d7 --- /dev/null +++ b/11ty/techniques.ts @@ -0,0 +1,244 @@ +import type { Cheerio } from "cheerio"; +import { glob } from "glob"; +import capitalize from "lodash-es/capitalize"; +import uniqBy from "lodash-es/uniqBy"; + +import { readFile } from "fs/promises"; +import { basename } from "path"; + +import { load, loadFromFile } from "./cheerio"; +import { isSuccessCriterion, type FlatGuidelinesMap, type SuccessCriterion } from "./guidelines"; +import { wcagSort } from "./common"; + +/** Maps each technology to its title for index.html */ +export const technologyTitles = { + aria: "ARIA Techniques", + "client-side-script": "Client-Side Script Techniques", + css: "CSS Techniques", + failures: "Common Failures", + flash: "Flash Techniques", // Deprecated in 2020 + general: "General Techniques", + html: "HTML Techniques", + pdf: "PDF Techniques", + "server-side-script": "Server-Side Script Techniques", + smil: "SMIL Techniques", + silverlight: "Silverlight Techniques", // Deprecated in 2020 + text: "Plain-Text Techniques", +}; +type Technology = keyof typeof technologyTitles; +export const technologies = Object.keys(technologyTitles) as Technology[]; + +function assertIsTechnology( + technology: string +): asserts technology is keyof typeof technologyTitles { + if (!(technology in technologyTitles)) throw new Error(`Invalid technology name: ${technology}`); +} + +const associationTypes = ["sufficient", "advisory", "failure"] as const; +type AssociationType = (typeof associationTypes)[number]; + +interface TechniqueAssociation { + criterion: SuccessCriterion; + type: Capitalize; + /** Indicates this technique must be paired with specific "child" techniques to fulfill SC */ + hasUsageChildren: boolean; + /** + * Technique ID of "parent" technique(s) this is paired with to fulfill SC. + * This is typically 0 or 1 technique, but may be multiple in rare cases. + */ + usageParentIds: string[]; + /** + * Text description of "parent" association, if it does not reference a specific technique; + * only populated if usageParentIds is empty. + */ + usageParentDescription: string; + /** Technique IDs this technique must be implemented with to fulfill SC, if any */ + with: string[]; +} + +function assertIsAssociationType(type?: string): asserts type is AssociationType { + if (!associationTypes.includes(type as AssociationType)) + throw new Error(`Association processed for unexpected section ${type}`); +} + +/** + * Pulls the basename out of a technique link href. + * This intentionally returns empty string (falsy) if a directory link happens to be passed. + */ +export const resolveTechniqueIdFromHref = (href: string) => + href.replace(/^.*\//, "").replace(/\.html$/, ""); + +/** + * Selector that can detect relative and absolute technique links from understanding docs + */ +export const understandingToTechniqueLinkSelector = [ + "[href^='../Techniques/' i]", + "[href^='../../techniques/' i]", + "[href^='https://www.w3.org/WAI/WCAG' i][href*='/Techniques/' i]", +] + .map((value) => `a${value}`) + .join(", ") as "a"; + +/** + * Returns object mapping technique IDs to SCs that reference it; + * comparable to technique-associations.xml but in a more ergonomic format. + */ +export async function getTechniqueAssociations(guidelines: FlatGuidelinesMap) { + const associations: Record = {}; + const itemSelector = associationTypes.map((type) => `section#${type} li`).join(", "); + + const paths = await glob("understanding/*/*.html"); + for (const path of paths) { + const criterion = guidelines[basename(path, ".html")]; + if (!isSuccessCriterion(criterion)) continue; + + const $ = await loadFromFile(path); + $(itemSelector).each((_, liEl) => { + const $liEl = $(liEl); + const $parentListItem = $liEl.closest("ul, ol").closest("li"); + // Identify which expected section the list was found under + const associationType = $liEl + .closest(associationTypes.map((type) => `section#${type}`).join(", ")) + .attr("id"); + assertIsAssociationType(associationType); + + /** Finds matches only within the given list item (not under child lists) */ + const queryNonNestedChildren = ($el: Cheerio, selector: string) => + $el.find(selector).filter((_, aEl) => $(aEl).closest("li")[0] === $el[0]); + + const $techniqueLinks = queryNonNestedChildren($liEl, understandingToTechniqueLinkSelector); + $techniqueLinks.each((_, aEl) => { + const usageParentIds = queryNonNestedChildren( + $parentListItem, + understandingToTechniqueLinkSelector + ) + .toArray() + .map((el) => resolveTechniqueIdFromHref(el.attribs.href)); + + // Capture the "X" in "X or more" phrasing, to include a phrase about + // combining with other techniques if more than one is required. + const descriptionDependencyPattern = + /(?:^|,?\s+)(?:by )?using\s+(?:(\w+) (?:or more )?of )?the\s+(?:following )?techniques(?: below)?(?::|\.)?\s*$/i; + const parentHtml = usageParentIds.length + ? null + : queryNonNestedChildren($parentListItem, "p").html(); + const match = parentHtml && descriptionDependencyPattern.exec(parentHtml); + const parentDescription = parentHtml + ? parentHtml.replace( + descriptionDependencyPattern, + !match?.[1] || match?.[1] === "one" ? "" : "when combined with other techniques" + ) + : ""; + const usageParentDescription = + parentDescription && + (parentDescription.startsWith("when") + ? parentDescription + : `when used for ${parentDescription[0].toLowerCase()}${parentDescription.slice(1)}`); + + const association: TechniqueAssociation = { + criterion, + type: capitalize(associationType) as Capitalize, + hasUsageChildren: !!$liEl.find("ul, ol").length, + usageParentIds, + usageParentDescription, + with: $techniqueLinks + .toArray() + .filter((el) => el !== aEl) + .map((el) => resolveTechniqueIdFromHref(el.attribs.href)), + }; + + const id = resolveTechniqueIdFromHref(aEl.attribs.href); + if (!(id in associations)) associations[id] = [association]; + else associations[id].push(association); + }); + }); + } + + // Remove duplicates (due to similar shape across understanding docs) and sort by SC number + for (const [key, list] of Object.entries(associations)) + associations[key] = uniqBy(list, (v) => JSON.stringify(v)).sort((a, b) => + wcagSort(a.criterion, b.criterion) + ); + + return associations; +} + +interface Technique { + /** Letter(s)-then-number technique code; corresponds to source HTML filename */ + id: string; + /** Technology this technique is filed under */ + technology: Technology; + /** Title derived from each technique page's h1 */ + title: string; + /** Title derived from each technique page's h1, with HTML preserved */ + titleHtml: string; + /** + * Like title, but preserving the XSLT process behavior of truncating + * text on intermediate lines between the first and last for long headings. + * (This was probably accidental, but helps avoid long link text.) + */ + truncatedTitle: string; +} + +/** + * Returns an object mapping each technology category to an array of Techniques. + * Used to generate index table of contents. + * (Functionally equivalent to "techniques-list" target in build.xml) + */ +export async function getTechniquesByTechnology() { + const paths = await glob("*/*.html", { cwd: "techniques" }); + const techniques = technologies.reduce( + (map, technology) => ({ + ...map, + [technology]: [] as string[], + }), + {} as Record + ); + + for (const path of paths) { + const [technology, filename] = path.split("/"); + assertIsTechnology(technology); + + // Isolate h1 from each file before feeding into Cheerio to save ~300ms total + const match = (await readFile(`techniques/${path}`, "utf8")).match(/]*>([\s\S]+?)<\/h1>/); + if (!match || !match[1]) throw new Error(`No h1 found in techniques/${path}`); + const $h1 = load(match[1], null, false); + + const title = $h1.text(); + techniques[technology].push({ + id: basename(filename, ".html"), + technology, + title, + titleHtml: $h1.html(), + truncatedTitle: title.replace(/\s*\n[\s\S]*\n\s*/, " … "), + }); + } + + for (const technology of technologies) { + techniques[technology].sort((a, b) => { + const aId = +a.id.replace(/\D/g, ""); + const bId = +b.id.replace(/\D/g, ""); + if (aId < bId) return -1; + if (aId > bId) return 1; + return 0; + }); + } + + return techniques; +} + +/** + * Returns a flattened object hash, mapping each technique ID directly to its data. + */ +export const getFlatTechniques = ( + techniques: Awaited> +) => + Object.values(techniques) + .flat() + .reduce( + (map, technique) => { + map[technique.id] = technique; + return map; + }, + {} as Record + ); diff --git a/11ty/types.ts b/11ty/types.ts new file mode 100644 index 0000000000..9bdd86c9f1 --- /dev/null +++ b/11ty/types.ts @@ -0,0 +1,55 @@ +/** @fileoverview Typings for common Eleventy entities */ + +interface EleventyPage { + date: Date; + filePathStem: string; + fileSlug: string; + inputPath: string; + outputFileExtension: string; + outputPath: string; + rawInput: string; + templateSyntax: string; + url: string; +} + +interface EleventyDirectories { + data: string; + includes: string; + input: string; + layouts?: string; + output: string; +} + +type EleventyRunMode = "build" | "serve" | "watch"; + +interface EleventyMeta { + directories: EleventyDirectories; + env: { + config: string; + root: string; + runMode: EleventyRunMode; + source: "cli" | "script"; + }; + generator: string; + version: string; +} + +/** Limited 11ty data available when defining filters and shortcodes. */ +export interface EleventyContext { + eleventy: EleventyMeta; + page: EleventyPage; +} + +/** Eleventy-supplied data available to templates. */ +export interface EleventyData extends EleventyContext { + content: string; + // Allow access to anything else in data cascade + [index: string]: any; +} + +/** Properties available in Eleventy event callbacks (eleventyConfig.on(...)) */ +export interface EleventyEvent { + dir: EleventyDirectories; + outputMode: "fs" | "json" | "ndjson"; + runMode: EleventyRunMode; +} diff --git a/11ty/understanding.ts b/11ty/understanding.ts new file mode 100644 index 0000000000..c9b414b63f --- /dev/null +++ b/11ty/understanding.ts @@ -0,0 +1,89 @@ +import { resolveDecimalVersion } from "./common"; +import type { DocNode, Principle, WcagVersion } from "./guidelines"; + +/** + * Selector that can detect relative and absolute understanding links from techniques docs + */ +export const techniqueToUnderstandingLinkSelector = [ + "[href^='../../Understanding/' i]", + "[href^='https://www.w3.org/WAI/WCAG' i][href*='/Understanding/' i]", +] + .map((value) => `a${value}`) + .join(", ") as "a"; + +/** + * Resolves information for top-level understanding pages; + * ported from generate-structure-xml.xslt + */ +export async function getUnderstandingDocs(version: WcagVersion): Promise { + const decimalVersion = resolveDecimalVersion(version); + return [ + { + id: "intro", + name: `Introduction to Understanding WCAG ${decimalVersion}`, + }, + { + id: "understanding-techniques", + name: "Understanding Techniques for WCAG Success Criteria", + }, + { + id: "understanding-act-rules", + name: "Understanding Test Rules for WCAG Success Criteria", + }, + { + id: "conformance", + name: "Understanding Conformance", + }, + { + id: "refer-to-wcag", + name: `How to Refer to WCAG ${decimalVersion} from Other Documents`, + }, + { + id: "documenting-accessibility-support", + name: "Documenting Accessibility Support for Uses of a Web Technology", + }, + { + id: "understanding-metadata", + name: "Understanding Metadata", + }, + ]; +} + +interface NavData { + parent?: DocNode; + previous?: DocNode; + next?: DocNode; +} + +/** + * Generates mappings from guideline/SC/understanding doc IDs to next/previous/parent information, + * for efficient lookup when rendering navigation banner + */ +export function generateUnderstandingNavMap(principles: Principle[], understandingDocs: DocNode[]) { + const allGuidelines = Object.values(principles).flatMap(({ guidelines }) => guidelines); + const map: Record = {}; + + // Guideline navigation wraps across principles, so iterate over flattened list + allGuidelines.forEach((guideline, i) => { + map[guideline.id] = { + ...(i > 0 && { previous: allGuidelines[i - 1] }), + ...(i < allGuidelines.length - 1 && { next: allGuidelines[i + 1] }), + }; + guideline.successCriteria.forEach((criterion, j) => { + map[criterion.id] = { + parent: guideline, + ...(j > 0 && { previous: guideline.successCriteria[j - 1] }), + ...(j < guideline.successCriteria.length - 1 && { next: guideline.successCriteria[j + 1] }), + }; + }); + }); + + understandingDocs.forEach((doc, i) => { + map[doc.id] = { + ...(i > 0 && { previous: understandingDocs[i - 1] }), + ...(i < understandingDocs.length - 1 && { next: understandingDocs[i + 1] }), + }; + }); + + return map; +} diff --git a/_includes/back-to-top.html b/_includes/back-to-top.html new file mode 100644 index 0000000000..da38ab5fbc --- /dev/null +++ b/_includes/back-to-top.html @@ -0,0 +1,7 @@ + + + Back to Top + + diff --git a/_includes/head.html b/_includes/head.html new file mode 100644 index 0000000000..55e7145ca2 --- /dev/null +++ b/_includes/head.html @@ -0,0 +1,8 @@ +{% # common tags inserted into all non-index/about pages %} + + +{% if isTechniques %} + {% include "techniques/head.html" %} +{% elsif isUnderstanding %} + {% include "understanding/head.html" %} +{% endif %} diff --git a/_includes/header.html b/_includes/header.html new file mode 100644 index 0000000000..7d687e9019 --- /dev/null +++ b/_includes/header.html @@ -0,0 +1,46 @@ +{% comment %} +Expected inputs from directory data for techniques and understanding: +headerUrl and headerLabel (set in both folders) +isUnderstanding and isTechniques (each set to true in respective folder) +{% endcomment %} +Skip to content +
    + +
    diff --git a/_includes/help-improve.html b/_includes/help-improve.html new file mode 100644 index 0000000000..340acedf69 --- /dev/null +++ b/_includes/help-improve.html @@ -0,0 +1,17 @@ + diff --git a/_includes/sidebar.html b/_includes/sidebar.html new file mode 100644 index 0000000000..529933f9a0 --- /dev/null +++ b/_includes/sidebar.html @@ -0,0 +1,8 @@ + diff --git a/_includes/site-footer.html b/_includes/site-footer.html new file mode 100644 index 0000000000..4d142dad4f --- /dev/null +++ b/_includes/site-footer.html @@ -0,0 +1,29 @@ + diff --git a/_includes/techniques/about.html b/_includes/techniques/about.html new file mode 100644 index 0000000000..c1f4392431 --- /dev/null +++ b/_includes/techniques/about.html @@ -0,0 +1,28 @@ +{% sectionbox "technique" "About this Technique" %} +{% include "techniques/applicability.html" %} +{% case technique.technology %} +{% when "aria" %} +

    This technique applies to content using WAI-ARIA.

    +{% when "client-side-script" %} +

    This technique applies to content using client-side script (JavaScript, ECMAScript).

    +{% when "css" %} +

    This technique applies to content using technologies that support CSS.

    +{% # failures intentionally has no applicability statement %} +{% when "flash" %} +

    This technique applies to content implemented in Adobe Flash.

    +{% when "general" %} +

    This technique applies to content implemented in any technology.

    +{% when "html" %} +

    This technique applies to content structured in HTML.

    +{% when "pdf" %} +

    This technique applies to content implemented in Adobe Tagged PDF.

    +{% when "server-side-script" %} +

    This technique applies to content modified by the server before being sent to the user.

    +{% when "silverlight" %} +

    This technique applies to content implemented in Microsoft Silverlight.

    +{% when "smil" %} +

    This technique applies to content implemented in Synchronized Multimedia Integration Language (SMIL).

    +{% when "text" %} +

    This technique applies to content implemented in plain text, including Markdown and similar formats, without use of a structure technology.

    +{% endcase %} +{% endsectionbox %} diff --git a/_includes/techniques/applicability-association.html b/_includes/techniques/applicability-association.html new file mode 100644 index 0000000000..7ded7f2b75 --- /dev/null +++ b/_includes/techniques/applicability-association.html @@ -0,0 +1,15 @@ +{% # See getTechniqueAssociations in 11ty/techniques.ts for typings %} +{{ association.criterion.id | linkUnderstanding }} +({{ association.type }} +{%- if association.with.length > 0 -%} + , together with {{ association.with | linkTechniques }} +{%- endif -%} +{%- if association.hasUsageChildren %} + using a more specific technique +{%- endif -%} +{%- if association.usageParentIds.length > 0 %} + when used with {{ association.usageParentIds | linkTechniques }} +{%- elsif association.usageParentDescription != "" %} + {{ association.usageParentDescription }} +{%- endif -%} +) \ No newline at end of file diff --git a/_includes/techniques/applicability.html b/_includes/techniques/applicability.html new file mode 100644 index 0000000000..3b62424a2e --- /dev/null +++ b/_includes/techniques/applicability.html @@ -0,0 +1,19 @@ +{% # Autogenerated content added to About box %} +{% if techniqueAssociations %} + {% if techniqueAssociations.size == 1 %} +

    + This technique relates to + {% include "techniques/applicability-association.html", association: techniqueAssociations[0] %}.

    + {% else %} +

    This technique relates to:

    +
      + {% for association in techniqueAssociations %} +
    • + {% include "techniques/applicability-association.html", association: association %} +
    • + {% endfor %} +
    + {% endif %} +{% else %} +

    This technique is not referenced from any Understanding document.

    +{% endif %} diff --git a/_includes/techniques/h1.html b/_includes/techniques/h1.html new file mode 100644 index 0000000000..3e99c97508 --- /dev/null +++ b/_includes/techniques/h1.html @@ -0,0 +1 @@ +

    Technique {{ page.fileSlug }}:{{ technique.titleHtml }}

    diff --git a/_includes/techniques/head.html b/_includes/techniques/head.html new file mode 100644 index 0000000000..ea96946262 --- /dev/null +++ b/_includes/techniques/head.html @@ -0,0 +1 @@ + diff --git a/_includes/techniques/intro/resources.html b/_includes/techniques/intro/resources.html new file mode 100644 index 0000000000..bccea53ad1 --- /dev/null +++ b/_includes/techniques/intro/resources.html @@ -0,0 +1 @@ +

    No endorsement implied.

    diff --git a/_includes/test-rules.html b/_includes/test-rules.html new file mode 100644 index 0000000000..1d30ac01b4 --- /dev/null +++ b/_includes/test-rules.html @@ -0,0 +1,25 @@ +{% if testRules.size > 0 %} +{%- if isTechniques -%} + {% assign understandingActRulesHref = "/understanding/understanding-act-rules.html" %} +{%- else -%} + {% assign understandingActRulesHref = "understanding-act-rules.html" %} +{%- endif -%} +
    +

    Test Rules

    +

    + The following are Test Rules + {% if isTechniques -%} + related to this Technique. + {%- elsif isUnderstanding -%} + for certain aspects of this Success Criterion. + {%- endif %} + It is not necessary to use these particular Test Rules to check for conformance with WCAG, but they are defined and approved test methods. + For information on using Test Rules, see Understanding Test Rules for WCAG Success Criteria. +

    + +
    +{%- endif %} diff --git a/_includes/toc.html b/_includes/toc.html new file mode 100644 index 0000000000..4c521cea3a --- /dev/null +++ b/_includes/toc.html @@ -0,0 +1,38 @@ +{% if isTechniques %} + {% for technology in technologies %} + {%- if techniques[technology].size == 0 -%}{%- continue -%}{%- endif -%} + {%- assign technologyTitle = technologyTitles[technology] -%} +

    {{ technologyTitle }}§

    + +
      + {% for technique in techniques[technology] %} +
    • {{ technique.id | linkTechniques }}
    • + {% endfor %} +
    + {% endfor %} +{% elsif isUnderstanding %} + {%- # See 11ty/guidelines.ts for typings -%} + {% for principle in principles %} +
    +

    {{ principle.name }}

    + {% for guideline in principle.guidelines %} +
    +

    {{ guideline.num }} {{ guideline.name }}

    + +
    + {% endfor %} +
    + {% endfor %} +
    +

    Other Understanding documents

    +
      + {% for doc in understandingDocs %} +
    • {{ doc.name }}
    • + {% endfor %} +
    +
    +{% endif %} diff --git a/_includes/understanding/about.html b/_includes/understanding/about.html new file mode 100644 index 0000000000..7b398bd6b7 --- /dev/null +++ b/_includes/understanding/about.html @@ -0,0 +1,9 @@ +{%- if guideline.type == "SC" -%} + {% sectionbox "success-criterion" "Success Criterion (SC)" -%} + {{ guideline.content }} + {%- endsectionbox %} +{%- else if guideline.type == "Guideline" -%} + {% sectionbox "guideline" "Guideline" -%} + {{ guideline.content }} + {%- endsectionbox %} +{%- endif -%} diff --git a/_includes/understanding/h1.html b/_includes/understanding/h1.html new file mode 100644 index 0000000000..29db4de86b --- /dev/null +++ b/_includes/understanding/h1.html @@ -0,0 +1,10 @@ +{%- # Expanded heading content, intended only for guideline and SC understanding pages -%} +

    + + Understanding + + {{- guideline.type }} {{ guideline.num -}} + : + {{ guideline.name }} + {%- if guideline.level %} (Level {{ guideline.level }}){% endif %} +

    diff --git a/_includes/understanding/head.html b/_includes/understanding/head.html new file mode 100644 index 0000000000..a4e625abff --- /dev/null +++ b/_includes/understanding/head.html @@ -0,0 +1 @@ + diff --git a/_includes/understanding/intro/advisory.html b/_includes/understanding/intro/advisory.html new file mode 100644 index 0000000000..3c64c9a91c --- /dev/null +++ b/_includes/understanding/intro/advisory.html @@ -0,0 +1,15 @@ +{%- if guideline.type == "Guideline" -%} +

    + Specific techniques for meeting each Success Criterion for this guideline + are listed in the understanding sections for each Success Criterion (listed below). + If there are techniques, however, for addressing this guideline that do not fall under + any of the success criteria, they are listed here. + These techniques are not required or sufficient for meeting any success criteria, + but can make certain types of Web content more accessible to more people. +

    +{%- else -%} +

    + Although not required for conformance, the following additional techniques should be considered in order to make content more accessible. + Not all techniques can be used or would be effective in all situations. +

    +{%- endif -%} diff --git a/_includes/understanding/intro/failure.html b/_includes/understanding/intro/failure.html new file mode 100644 index 0000000000..b1865f7e74 --- /dev/null +++ b/_includes/understanding/intro/failure.html @@ -0,0 +1,3 @@ +

    + The following are common mistakes that are considered failures of this Success Criterion by the WCAG Working Group. +

    diff --git a/_includes/understanding/intro/resources.html b/_includes/understanding/intro/resources.html new file mode 100644 index 0000000000..a782d78bdc --- /dev/null +++ b/_includes/understanding/intro/resources.html @@ -0,0 +1 @@ +

    Resources are for information purposes only, no endorsement implied.

    diff --git a/_includes/understanding/intro/sufficient-situation.html b/_includes/understanding/intro/sufficient-situation.html new file mode 100644 index 0000000000..fabc003240 --- /dev/null +++ b/_includes/understanding/intro/sufficient-situation.html @@ -0,0 +1,4 @@ +

    + Select the situation below that matches your content. + Each situation includes techniques or combinations of techniques that are known and documented to be sufficient for that situation. +

    diff --git a/_includes/understanding/intro/techniques.html b/_includes/understanding/intro/techniques.html new file mode 100644 index 0000000000..bfb3ffa5e9 --- /dev/null +++ b/_includes/understanding/intro/techniques.html @@ -0,0 +1,8 @@ +

    + Each numbered item in this section represents a technique or combination of techniques + that the WCAG Working Group deems sufficient for meeting this Success Criterion. + However, it is not necessary to use these particular techniques. + For information on using other techniques, see + Understanding Techniques for WCAG Success Criteria, + particularly the "Other Techniques" section. +

    diff --git a/_includes/understanding/key-terms.html b/_includes/understanding/key-terms.html new file mode 100644 index 0000000000..bea2906e38 --- /dev/null +++ b/_includes/understanding/key-terms.html @@ -0,0 +1,6 @@ +
    +
    +

    Key Terms

    +
    +
    +
    diff --git a/_includes/understanding/navigation-index.html b/_includes/understanding/navigation-index.html new file mode 100644 index 0000000000..12fdd810df --- /dev/null +++ b/_includes/understanding/navigation-index.html @@ -0,0 +1,19 @@ +{% comment %} +Navigation used for Understanding index/about pages only. +See _includes/header.html for expected input notes +{% endcomment %} + diff --git a/_includes/understanding/navigation.html b/_includes/understanding/navigation.html new file mode 100644 index 0000000000..9f41d9ae5e --- /dev/null +++ b/_includes/understanding/navigation.html @@ -0,0 +1,50 @@ +{% comment %} +Navigation used for individual Understanding pages. +See _includes/header.html for expected input notes +{% endcomment %} + diff --git a/_includes/understanding/success-criteria.html b/_includes/understanding/success-criteria.html new file mode 100644 index 0000000000..5a1804afc2 --- /dev/null +++ b/_includes/understanding/success-criteria.html @@ -0,0 +1,10 @@ +{%- if guideline.successCriteria %} +
    +

    Success Criteria for this Guideline

    + +
    +{% endif -%} diff --git a/_includes/wai-site-footer.html b/_includes/wai-site-footer.html new file mode 100644 index 0000000000..8c24e359a1 --- /dev/null +++ b/_includes/wai-site-footer.html @@ -0,0 +1,17 @@ +
    +
    +

    Date: Updated {{ "now" | date_to_long_string }}.

    +

    Developed by + Accessibility Guidelines Working Group (AG WG) Participants + (Co-Chairs: Alastair Campbell, Charles Adams, Rachael Bradley Montgomery. W3C Staff Contact: Kevin White). +

    +

    The content was developed as part of the + WAI-Core projects funded by U.S. Federal funds. + The user interface was designed by the Education and Outreach Working Group + (EOWG) with contributions from + Shadi Abou-Zahra, Steve Lee, and Shawn Lawton Henry as part of the + WAI-Guide project, + co-funded by the European Commission. +

    +
    +
    diff --git a/_includes/waiscript.html b/_includes/waiscript.html new file mode 100644 index 0000000000..7f65b9f8f8 --- /dev/null +++ b/_includes/waiscript.html @@ -0,0 +1,29 @@ + + + + +{% # Matomo %} + + +{% # End Matomo Code %} diff --git a/eleventy.config.ts b/eleventy.config.ts new file mode 100644 index 0000000000..9528e68a12 --- /dev/null +++ b/eleventy.config.ts @@ -0,0 +1,208 @@ +import compact from "lodash-es/compact"; + +import { copyFile } from "fs/promises"; + +import { CustomLiquid } from "11ty/CustomLiquid"; +import { actRules, getFlatGuidelines, getPrinciples } from "11ty/guidelines"; +import { + getFlatTechniques, + getTechniqueAssociations, + getTechniquesByTechnology, + technologies, + technologyTitles, +} from "11ty/techniques"; +import { generateUnderstandingNavMap, getUnderstandingDocs } from "11ty/understanding"; +import type { EleventyContext, EleventyData, EleventyEvent } from "11ty/types"; + +/** Version of WCAG to build */ +const version = "22"; + +const principles = await getPrinciples(); +const flatGuidelines = getFlatGuidelines(principles); +const techniques = await getTechniquesByTechnology(); +const flatTechniques = getFlatTechniques(techniques); +const techniqueAssociations = await getTechniqueAssociations(flatGuidelines); +const understandingDocs = await getUnderstandingDocs(version); +const understandingNav = await generateUnderstandingNavMap(principles, understandingDocs); + +// Declare static global data up-front so we can build typings from it +const globalData = { + version, + versionDecimal: version.split("").join("."), + techniques, // Used for techniques/index.html + technologies, // Used for techniques/index.html + technologyTitles, // Used for techniques/index.html + principles, // Used for understanding/index.html + understandingDocs, // Used for understanding/index.html +}; + +export type GlobalData = EleventyData & + typeof globalData & { + // Expected data cascade properties from *.11tydata.js + headerLabel?: string; // akin to documentset.name in build.xml + headerUrl?: string; + isTechniques?: boolean; + isUnderstanding?: boolean; + }; + +const baseUrls = { + guidelines: `https://www.w3.org/TR/WCAG${version}/`, + techniques: "/techniques/", + understanding: "/understanding/", +}; + +if (process.env.WCAG_MODE === "editors") { + // For pushing to gh-pages + baseUrls.guidelines = "https://w3c.github.io/wcag/guidelines/"; + baseUrls.techniques = "https://w3c.github.io/wcag/techniques/"; + baseUrls.understanding = "https://w3c.github.io/wcag/understanding/"; +} else if (process.env.WCAG_MODE === "publication") { + // For pushing to W3C site + baseUrls.guidelines = `https://www.w3.org/TR/WCAG${version}/`; + baseUrls.techniques = `https://www.w3.org/WAI/WCAG${version}/Techniques/`; + baseUrls.understanding = `https://www.w3.org/WAI/WCAG${version}/Understanding/`; +} + +export default function (eleventyConfig: any) { + for (const [name, value] of Object.entries(globalData)) eleventyConfig.addGlobalData(name, value); + + // Make baseUrls available to templates + for (const [name, value] of Object.entries(baseUrls)) + eleventyConfig.addGlobalData(`${name}Url`, value); + + // eleventyComputed data is assigned here rather than in 11tydata files; + // we have access to typings here, and can keep the latter fully static. + eleventyConfig.addGlobalData("eleventyComputed", { + // permalink determines output structure; see https://www.11ty.dev/docs/permalinks/ + permalink: ({ page, isUnderstanding }: GlobalData) => { + if (isUnderstanding) { + // understanding-metadata.html exists in 2 places; top-level wins in XSLT process + if (/\/20\/understanding-metadata/.test(page.inputPath)) return false; + // Flatten pages into top-level directory, out of version subdirectories + return page.inputPath.replace(/\/2\d\//, "/"); + } + // Preserve existing structure: write to x.html instead of x/index.html + return page.inputPath; + }, + + nav: ({ page, isUnderstanding }: GlobalData) => + isUnderstanding ? understandingNav[page.fileSlug] : null, + testRules: ({ page, isTechniques, isUnderstanding }: GlobalData) => { + if (isTechniques) + return actRules.filter(({ wcagTechniques }) => wcagTechniques.includes(page.fileSlug)); + if (isUnderstanding) + return actRules.filter(({ successCriteria }) => successCriteria.includes(page.fileSlug)); + }, + + // Data for individual technique pages + technique: ({ page, isTechniques }: GlobalData) => + isTechniques ? flatTechniques[page.fileSlug] : null, + techniqueAssociations: ({ page, isTechniques }: GlobalData) => + isTechniques ? techniqueAssociations[page.fileSlug] : null, + + // Data for individual understanding pages + guideline: ({ page, isUnderstanding }: GlobalData) => + isUnderstanding ? flatGuidelines[page.fileSlug] : null, + }); + + // See https://www.11ty.dev/docs/copy/#emulate-passthrough-copy-during-serve + eleventyConfig.setServerPassthroughCopyBehavior("passthrough"); + + eleventyConfig.addPassthroughCopy("techniques/*.css"); + eleventyConfig.addPassthroughCopy("techniques/*/img/*"); + eleventyConfig.addPassthroughCopy({ + "css/base.css": "techniques/base.css", + "css/a11y-light.css": "techniques/a11y-light.css", + "script/highlight.min.js": "techniques/highlight.min.js", + }); + + eleventyConfig.addPassthroughCopy("understanding/*.css"); + eleventyConfig.addPassthroughCopy({ + "guidelines/relative-luminance.html": "understanding/relative-luminance.html", + "understanding/*/img/*": "understanding/img", // Intentionally flatten + }); + + eleventyConfig.addPassthroughCopy("working-examples/**"); + + eleventyConfig.on("eleventy.after", async ({ dir }: EleventyEvent) => { + // addPassthroughCopy can only map each file once, + // but base.css needs to be copied to a 2nd destination + await copyFile(`${dir.input}/css/base.css`, `${dir.output}/understanding/base.css`); + }); + + eleventyConfig.setLibrary( + "liquid", + new CustomLiquid({ + // See https://www.11ty.dev/docs/languages/liquid/#liquid-options + root: ["_includes", "."], + jsTruthy: true, + strictFilters: true, + }) + ); + + // Filter that transforms a technique ID (or list of them) into links to their pages. + eleventyConfig.addFilter( + "linkTechniques", + function (this: EleventyContext, ids: string | string[]) { + const links = (Array.isArray(ids) ? ids : [ids]).map((id) => { + if (typeof id !== "string") throw new Error(`linkTechniques: invalid id ${id}`); + const technique = flatTechniques[id]; + if (!technique) { + console.warn( + `linkTechniques in ${this.page.inputPath}: ` + + `skipping unresolvable technique id ${id}` + ); + return; + } + // Support relative technique links from other techniques or from techniques/index.html, + // otherwise path-absolute when cross-linked from understanding/* + const urlBase = /^\/techniques\/.*\//.test(this.page.filePathStem) + ? "../" + : this.page.filePathStem.startsWith("/techniques") + ? "" + : baseUrls.techniques; + const label = `${id}: ${technique.truncatedTitle}`; + return `${label}`; + }); + return compact(links).join("\nand\n"); + } + ); + + // Filter that transforms a guideline or SC shortname (or list of them) into links to their pages. + eleventyConfig.addFilter( + "linkUnderstanding", + function (this: EleventyContext, ids: string | string[]) { + return (Array.isArray(ids) ? ids : [ids]) + .map((id) => { + if (typeof id !== "string") throw new Error("linkUnderstanding: invalid id passed"); + const guideline = flatGuidelines[id]; + if (!guideline) { + console.warn( + `linkUnderstanding in ${this.page.inputPath}: ` + + `skipping unresolvable guideline shortname ${id}` + ); + } + const urlBase = this.page.filePathStem.startsWith("/understanding/") + ? "" + : baseUrls.understanding; + const label = `${guideline.num}: ${guideline.name}`; + return `${label}`; + }) + .join("\nand\n"); + } + ); + + // Renders a section box (used for About this Technique and Guideline / SC) + eleventyConfig.addPairedShortcode( + "sectionbox", + (content: string, id: string, title: string) => ` +
    +

    ${title}

    +
    ${content}
    +
    + ` + ); + + // Suppress default build output that prints every path, to make our own output clearly visible + eleventyConfig.setQuietMode(true); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..65e3275add --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3045 @@ +{ + "name": "wcag", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wcag", + "version": "0.1.0", + "license": "W3C", + "dependencies": { + "@11ty/eleventy": "^3.0.0-alpha.14", + "cheerio": "^1.0.0-rc.12", + "glob": "^10.3.16", + "liquidjs": "^10.14.0", + "lodash-es": "^4.17.21", + "mkdirp": "^3.0.1", + "tsx": "^4.11.0" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.12.12", + "prettier": "^3.3.2", + "typescript": "^5.4.5" + } + }, + "node_modules/@11ty/dependency-tree": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-3.0.0.tgz", + "integrity": "sha512-+M+/KdAptDTK4USkI124CKoScvt2y0v1EoLBjS2rUi9zydjWgEC7+EJJ7Eu1DXuObe0JmY09LORjdBGKNPxgtg==", + "dependencies": { + "@11ty/eleventy-utils": "^1.0.2" + } + }, + "node_modules/@11ty/dependency-tree-esm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-1.0.0.tgz", + "integrity": "sha512-Z3KN1Fkv50UM/ZzTR3VBbyOY52HnmhIVCsAV1hn2UzFsGAjyF1Cw8uohhVtheDOSuBR7ZSeo1unwkz1HxFlUtQ==", + "dependencies": { + "@11ty/eleventy-utils": "^1.0.2", + "acorn": "^8.10.0", + "dependency-graph": "^0.11.0", + "normalize-path": "^3.0.0" + } + }, + "node_modules/@11ty/dependency-tree-esm/node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/@11ty/eleventy": { + "version": "3.0.0-alpha.14", + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.0.0-alpha.14.tgz", + "integrity": "sha512-SQGvi/0cSrgyjhTocO8nGpivQbZBXnFlVLp1M3H8xcdvpXYgCVHiEYvrY79TnDd9Nxvj5QtsQuCiselJb3X16g==", + "dependencies": { + "@11ty/dependency-tree": "^3.0.0", + "@11ty/dependency-tree-esm": "^1.0.0", + "@11ty/eleventy-dev-server": "^2.0.1", + "@11ty/eleventy-plugin-bundle": "^2.0.2", + "@11ty/eleventy-utils": "^1.0.3", + "@11ty/lodash-custom": "^4.17.21", + "@11ty/posthtml-urls": "^1.0.0", + "@sindresorhus/slugify": "^2.2.1", + "bcp-47-normalize": "^2.3.0", + "chardet": "^2.0.0", + "chokidar": "^3.6.0", + "cross-spawn": "^7.0.3", + "debug": "^4.3.5", + "dependency-graph": "^1.0.0", + "fast-glob": "^3.3.2", + "graceful-fs": "^4.2.11", + "gray-matter": "^4.0.3", + "is-glob": "^4.0.3", + "iso-639-1": "^3.1.2", + "kleur": "^4.1.5", + "liquidjs": "^10.14.0", + "luxon": "^3.4.4", + "markdown-it": "^14.1.0", + "micromatch": "^4.0.7", + "minimist": "^1.2.8", + "moo": "^0.5.2", + "multimatch": "^7.0.0", + "node-retrieve-globals": "^6.0.0", + "normalize-path": "^3.0.0", + "nunjucks": "^3.2.4", + "please-upgrade-node": "^3.2.0", + "posthtml": "^0.16.6", + "recursive-copy": "^2.0.14", + "semver": "^7.6.2", + "slugify": "^1.6.6" + }, + "bin": { + "eleventy": "cmd.cjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-dev-server": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.1.tgz", + "integrity": "sha512-G92KbOx5vg2G3mlQQ1B0+L+Ni7pcn1yeMgQdnT8b2G20ZnbGNtuGYEPehbQV4Xqr2iAtoBfbihNskbLDoVtNOg==", + "dependencies": { + "@11ty/eleventy-utils": "^1.0.2", + "chokidar": "^3.6.0", + "debug": "^4.3.5", + "dev-ip": "^1.0.1", + "finalhandler": "^1.2.0", + "mime": "^3.0.0", + "minimist": "^1.2.8", + "morphdom": "^2.7.2", + "please-upgrade-node": "^3.2.0", + "send": "^0.18.0", + "ssri": "^10.0.6", + "urlpattern-polyfill": "^10.0.0", + "ws": "^8.17.0" + }, + "bin": { + "eleventy-dev-server": "cmd.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-plugin-bundle": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-2.0.2.tgz", + "integrity": "sha512-zGyPp1g6bi+VC2I5ylwj4w29nivDmx4Uki5gWY6v3MT/1muK0JTtnc1KOMC7yUurv6YwtwdiLYyFK2eFyKv2wg==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-1.0.3.tgz", + "integrity": "sha512-nULO91om7vQw4Y/UBjM8i7nJ1xl+/nyK4rImZ41lFxiY2d+XUz7ChAj1CDYFjrLZeu0utAYJTZ45LlcHTkUG4g==", + "dependencies": { + "normalize-path": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/lodash-custom": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", + "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/posthtml-urls": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.0.tgz", + "integrity": "sha512-CcsRdI933x613u7CjM+QGs7iD/m8SaDup3Apohg1+7dybigrEUHc2jGS3mcMgQKvF2+IphqmepD/FrKLlPkPEg==", + "dependencies": { + "evaluate-value": "^2.0.0", + "http-equiv-refresh": "^2.0.1", + "list-to-array": "^1.1.0", + "object.entries": "^1.1.7", + "parse-srcset": "^1.0.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-differ": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", + "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", + "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", + "dependencies": { + "bcp-47": "^2.0.0", + "bcp-47-match": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chardet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz", + "integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==" + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio-select/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-select/node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dev-ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "bin": { + "dev-ip": "lib/dev-ip.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm-import-transformer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.2.tgz", + "integrity": "sha512-PgvO0wro44lTDM9pYeeOIfpS0lGF80jA+rjT7sBd3b07rxv1AxeNMEI5kSCqRKke2W6SPEz17W3kHOLjaiD7Cw==", + "dependencies": { + "acorn": "^8.11.2" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/evaluate-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz", + "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.3.16", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz", + "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-equiv-refresh": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz", + "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/iso-639-1": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.2.tgz", + "integrity": "sha512-Le7BRl3Jt9URvaiEHJCDEdvPZCfhiQoXnFgLAWNRhzFMwRFdWO7/5tLRQbiPzE394I9xd7KdRCM7S6qdOhwG5A==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/jackspeak": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", + "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/junk": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/junk/-/junk-1.0.3.tgz", + "integrity": "sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/liquidjs": { + "version": "10.14.0", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.14.0.tgz", + "integrity": "sha512-Zjg35Yo3L/2aNy7QkICha/ulbXRtZS7oRenWyDDfw+J34Xy3fOKWWHxASC9r0gbxN661nrwmG/kOIKHfYcVk4Q==", + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/list-to-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", + "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/maximatch": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", + "integrity": "sha512-9ORVtDUFk4u/NFfo0vG/ND/z7UQCVZBL539YW0+U1I7H1BkZwizcPx5foFv7LCPcBnm2U6RjFnQOsIvN4/Vm2A==", + "dependencies": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/maximatch/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/maximatch/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==" + }, + "node_modules/morphdom": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.3.tgz", + "integrity": "sha512-rvGK92GxSuPEZLY8D/JH07cG3BxyA+/F0Bxg32OoGAEFFhGWA3OqVpqPZlOgZTCR52clXrmz+z2pYSJ6gOig1w==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multimatch": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-7.0.0.tgz", + "integrity": "sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==", + "dependencies": { + "array-differ": "^4.0.0", + "array-union": "^3.0.1", + "minimatch": "^9.0.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/node-retrieve-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.0.tgz", + "integrity": "sha512-VoEp6WMN/JcbBrJr6LnFE11kdzpKiBKNPFrHCEK2GgFWtiYpeL85WgcZpZFFnWxAU0O65+b+ipQAy4Oxy/+Pdg==", + "dependencies": { + "acorn": "^8.1.3", + "acorn-walk": "^8.3.2", + "esm-import-transformer": "^3.0.2" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/posthtml": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==" + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-copy": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/recursive-copy/-/recursive-copy-2.0.14.tgz", + "integrity": "sha512-K8WNY8f8naTpfbA+RaXmkaQuD1IeW9EgNEfyGxSqqTQukpVtoOKros9jUqbpEsSw59YOmpd8nCBgtqJZy5nvog==", + "dependencies": { + "errno": "^0.1.2", + "graceful-fs": "^4.1.4", + "junk": "^1.0.1", + "maximatch": "^0.1.0", + "mkdirp": "^0.5.1", + "pify": "^2.3.0", + "promise": "^7.0.1", + "rimraf": "^2.7.1", + "slash": "^1.0.0" + } + }, + "node_modules/recursive-copy/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.0.tgz", + "integrity": "sha512-vzGGELOgAupsNVssAmZjbUDfdm/pWP4R+Kg8TVdsonxbXk0bEpE1qh0yV6/QxUVXaVlNemgcPajGdJJ82n3stg==", + "dependencies": { + "esbuild": "~0.20.2", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..526fdc94f3 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "wcag", + "private": true, + "version": "0.1.0", + "description": "Web Content Accessibility Guidelines", + "type": "module", + "scripts": { + "build": "tsx node_modules/@11ty/eleventy/cmd.cjs --config=eleventy.config.ts", + "check": "tsc", + "fmt": "prettier . -w", + "publish-w3c": "WCAG_MODE=publication npm run build && tsx 11ty/cp-cvs.ts", + "start": "npm run build -- --serve" + }, + "author": "W3C", + "license": "W3C", + "dependencies": { + "@11ty/eleventy": "^3.0.0-alpha.14", + "cheerio": "^1.0.0-rc.12", + "glob": "^10.3.16", + "liquidjs": "^10.14.0", + "lodash-es": "^4.17.21", + "mkdirp": "^3.0.1", + "tsx": "^4.11.0" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.12.12", + "prettier": "^3.3.2", + "typescript": "^5.4.5" + } +} diff --git a/techniques/techniques.11tydata.js b/techniques/techniques.11tydata.js new file mode 100644 index 0000000000..cbcafab4ec --- /dev/null +++ b/techniques/techniques.11tydata.js @@ -0,0 +1,7 @@ +export default function (data) { + return { + headerLabel: "Techniques", + headerUrl: data.techniquesUrl, + isTechniques: true, + }; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..2045da47d9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ES2022", + "moduleResolution": "Node", + "noEmit": true, + "strict": true, + "target": "ES2022", + "verbatimModuleSyntax": true + } +} diff --git a/understanding/understanding.11tydata.js b/understanding/understanding.11tydata.js new file mode 100644 index 0000000000..e6a12fdc66 --- /dev/null +++ b/understanding/understanding.11tydata.js @@ -0,0 +1,7 @@ +export default function (data) { + return { + headerLabel: "Understanding Docs", + headerUrl: data.understandingUrl, + isUnderstanding: true, + }; +} From 7646e38a83dd77a206b833fe85cc5f42353bd60d Mon Sep 17 00:00:00 2001 From: front-endian <11234702+front-endian@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:14:48 -0400 Subject: [PATCH 02/25] Grammar fix in use-of-color.html (#3909) Add missing "the" --- understanding/20/use-of-color.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/understanding/20/use-of-color.html b/understanding/20/use-of-color.html index e71b9d3b53..2f913938dd 100644 --- a/understanding/20/use-of-color.html +++ b/understanding/20/use-of-color.html @@ -30,7 +30,7 @@

    Intent of Use of Color

    another visual means ensures users who cannot see color can still perceive the information.

    -

    Color is an important asset in design of Web content, enhancing its aesthetic appeal, +

    Color is an important asset in the design of Web content, enhancing its aesthetic appeal, its usability, and its accessibility. However, some users have difficulty perceiving color. People with partial sight often experience limited color vision, and many older users do not see color well. In addition, people using limited-color or From 17b1503d01c35adac6be94a09a0105c5b2c0aab8 Mon Sep 17 00:00:00 2001 From: Detlev Fischer Date: Thu, 11 Jul 2024 00:17:00 +0200 Subject: [PATCH 03/25] Update test for H71.html (#3096) Modify test section to include the requirement that legend is first child in fieldset, as is technically required Closes #3090 --------- Co-authored-by: Francis Storr Co-authored-by: Patrick H. Lauke --- techniques/html/H71.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/techniques/html/H71.html b/techniques/html/H71.html index f80a0df693..4615b1dddc 100644 --- a/techniques/html/H71.html +++ b/techniques/html/H71.html @@ -112,7 +112,7 @@

    Procedure

    For groups of related controls where the individual labels for each control do not provide a sufficient description, and an additional group level description is needed:

    1. Check that the group of logically related input or select elements are contained within fieldset elements.
    2. -
    3. Check that each fieldset has a legend element that includes a description for that group.
    4. +
    5. Check that each fieldset has a legend element that is the first child in the fieldset and includes a description for that group.
    From b9c26dc1f25c9dd3f09788c8e54043141cc6d3b3 Mon Sep 17 00:00:00 2001 From: Francis Storr Date: Wed, 10 Jul 2024 15:17:39 -0700 Subject: [PATCH 04/25] Editorial changes to Target Size Enhanced (#3901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove two `note-title-marker` elements that were adding extra “Note” headings to notes. 2. Change `ul` element to `dl` --- understanding/21/target-size-enhanced.html | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/understanding/21/target-size-enhanced.html b/understanding/21/target-size-enhanced.html index 35887b9c4b..62f172fd39 100644 --- a/understanding/21/target-size-enhanced.html +++ b/understanding/21/target-size-enhanced.html @@ -3,7 +3,7 @@ Understanding Target Size (Enhanced) - +

    Understanding SC 2.5.5 Target Size (Enhanced)

    @@ -31,11 +31,9 @@

    Intent

    Equivalent targets: If there is more than one target on a screen that performs the same action, only one of the targets need to meet the target size of 44 by 44 CSS pixels.

    Inline: Content displayed can often be reflowed based on the screen width available. This is known as responsive design and makes it easier to read since you do not need to scroll both horizontally and vertically. In reflowed content, the targets can appear anywhere on a line and can change position based on the width of the available screen. Since targets can appear anywhere on the line, the size cannot be larger than the available text and spacing between the sentences or paragraphs, otherwise the targets could overlap. It is for this reason targets which are contained within one or more sentences are excluded from the target size requirements.

    -
    Note

    If the target is the full sentence and the sentence is not in a block of text, then the target needs to be at least 44 by 44 CSS pixels.

    -
    Note

    A footnote or an icon within or at the end of a sentence is considered to be part of a sentence and therefore are excluded from the minimum target size.

    User Agent Control: If the size of the target is not modified by the author through CSS or other size properties, then the target does not need to meet the target size of 44 by 44 CSS pixels.

    @@ -55,16 +53,23 @@

    Benefits

    Examples

    -
      -
    • Example 1: Buttons
      Three buttons are on-screen and the touch target area of each button is 44 by 44 CSS pixels.
    • -
    • Example 2: Equivalent target
      Multiple targets are provided on the page that perform the same function. One of the targets is 44 by 44 CSS pixels. The other targets do not have a minimum touch target of 44 by 44 CSS pixels.
    • -
    • Example 3: Anchor Link
      The target is an in-page link and the target is less than 44 by 44 CSS pixels.
    • -
    • Example 4: Text Link in a paragraph
      Links within a paragraph of text have varying touch target dimensions. Links within - paragraphs of text do no need to meet the 44 by 44 CSS pixels requirements.
    • -
    • Example 5: Text Link in a sentence
      A text link that is in a sentence is excluded and does not need to meet the 44 by 44 CSS pixel requirement. If the text link is the full sentence, then the text link target touch area does need to meet the 44 by 44 CSS pixels.
    • -
    • Example 6: Footnote
      A footnote link at the end of a sentence does not need to meet the 44 by 44 CSS pixels requirements. The footnote at the end of the sentence is considered to be part of the sentence.
    • -
    • Example 7: Help icon
      A help icon within or at the end of a sentence does not need to meet the 44 by 44 CSS pixels requirements. The icon at the end of the sentence is considered to be part of the sentence.
    • -
    +
    +
    Example 1: Buttons
    +
    Three buttons are on-screen and the touch target area of each button is 44 by 44 CSS pixels.
    +
    Example 2: Equivalent target
    +
    Multiple targets are provided on the page that perform the same function. One of the targets is 44 by 44 CSS pixels. The other targets do not have a minimum touch target of 44 by 44 CSS pixels.
    +
    Example 3: Anchor Link
    +
    The target is an in-page link and the target is less than 44 by 44 CSS pixels.
    +
    Example 4: Text Link in a paragraph
    +
    Links within a paragraph of text have varying touch target dimensions. Links within + paragraphs of text do no need to meet the 44 by 44 CSS pixels requirements.
    +
    Example 5: Text Link in a sentence
    +
    A text link that is in a sentence is excluded and does not need to meet the 44 by 44 CSS pixel requirement. If the text link is the full sentence, then the text link target touch area does need to meet the 44 by 44 CSS pixels.
    +
    Example 6: Footnote
    +
    A footnote link at the end of a sentence does not need to meet the 44 by 44 CSS pixels requirements. The footnote at the end of the sentence is considered to be part of the sentence.
    +
    Example 7: Help icon
    +
    A help icon within or at the end of a sentence does not need to meet the 44 by 44 CSS pixels requirements. The icon at the end of the sentence is considered to be part of the sentence.
    +

    Resources

    From 2d181a52d0dc42c254598d0171eb35d04f1cb169 Mon Sep 17 00:00:00 2001 From: Francis Storr Date: Wed, 10 Jul 2024 15:18:08 -0700 Subject: [PATCH 05/25] Updated Page Titled for better links (#3824) 1. updated the "document collection" Understanding link to say WCAG 2.2 instead of 2.1. 2. updated the quoted page title for the Introduction to WCAG page. 3. linked to Introduction to WCAG page. Note: I don't understand the references to the appendices on the WCAG homepage. What do they have to do with page titles? --------- Co-authored-by: Patrick H. Lauke --- understanding/20/page-titled.html | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/understanding/20/page-titled.html b/understanding/20/page-titled.html index f5ddc0c76a..a6c1f16ed8 100644 --- a/understanding/20/page-titled.html +++ b/understanding/20/page-titled.html @@ -98,13 +98,10 @@

    Examples of Page Titled

    that it will be displayed in the title bar of the user agent.
    A document collection
    -

    The title of Understanding WCAG 2.1 - is "Understanding WCAG 2.1."

    +

    The title of Understanding WCAG 2.2 is "Understanding WCAG 2.2".

      -
    • The introduction page has the title "Introduction to Understanding WCAG 2.0."
    • -
    • Major sections of the document are pages titled "Understanding Guideline X" and "Understanding - Success Criterion X." -
    • +
    • The Introduction to Understanding WCAG page has the title "Introduction to Understanding WCAG".
    • +
    • Major sections of the document collection are pages titled "Understanding Guideline X" and "Understanding Success Criterion X."
    • Appendix A has the title "Glossary."
    • Appendix B has the title "Acknowledgements."
    • Appendix C has the title "References."
    • From e293dc797c6c6235526da1ffabbcd79be55dbb3c Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Wed, 10 Jul 2024 23:19:01 +0100 Subject: [PATCH 06/25] Updates test steps in F94 (#3739) - Removes 1280 pixels wide step - Removes the unnecessary preamble before the test steps, and tweaks the last line of the test procedure to reference the last step, rather than giving it a number - Additionally, cleans up formatting of the HTML Closes https://github.com/w3c/wcag/issues/704 --------- Co-authored-by: Mike Gower --- techniques/failures/F94.html | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/techniques/failures/F94.html b/techniques/failures/F94.html index 3c47d95a74..ca87adc267 100644 --- a/techniques/failures/F94.html +++ b/techniques/failures/F94.html @@ -20,35 +20,31 @@

      Description

      The objective of this technique is to document the failure of text to re-scale when viewport units are used on text. As these units are relative to the viewport, it means they cannot be resized by zooming or adjusting text-size.

      There are various methods to increase and decrease the size of text and other content, but viewport units applied to text (generally via font-size in CSS) prevent most available methods. Attempts to use browser controls to zoom or adjust text-size will not work. Only methods that completely override the CSS will work, and those could cause other issues such as layouts collapsing or text overlapping.

      Some uses of viewport units may not prevent text-size adjustments, but if they are used as the primary method for defining text-size, they are likely to cause a failure of Success Criterion 1.4.4.

      - -

      If media queries were used to adjust the size of text or unit of measure at different screen sizes, it may not be a failure of Resize Text. On-page controls provided by the author are also a way of passing the resize text success criteria.

      +

      If media queries were used to adjust the size of text or unit of measure at different screen sizes, it may not be a failure of Resize Text. On-page controls provided by the author are also a way of passing the resize text success criteria.

    Examples

    Failure example 1

    The following CSS and HTML snippet uses VW units to size the text.

    -
    /* CSS */
    +
    /* CSS */
     .callout {
       font-size:1vw;
     }
     
     <p class="callout">Text that scales by viewport units</p>
    -

    Example page with an example of text sized in vh units.

    +

    Example page with an example of text sized in vh units.

    Tests

    Procedure

    -

    In a desktop browser with a resizable window:

      -
    1. Set zoom level to 100%.
    2. -
    3. Set window size to 1280 pixels wide.
    4. Visit the page to be tested.
    5. Use any of the following methods to resize text when available:
        -
      • the zoom feature of the browser
      • +
      • the zoom feature of the browser,
      • the text-sizing feature of the browser,
      • on-page controls for resizing text.
      @@ -59,7 +55,7 @@

      Procedure

      Expected Results

        -
      • If step #5 is false, then this failure condition applies and the content fails Success Criteria 1.4.4, Resize Text.
      • +
      • If the last step is false, then this failure condition applies and the content fails Success Criterion 1.4.4 Resize Text.
    From 916d649ec0a224d10a0ddbb7f3aa327e99a3c21f Mon Sep 17 00:00:00 2001 From: Scott O'Hara Date: Wed, 10 Jul 2024 18:19:21 -0400 Subject: [PATCH 07/25] Update labels-or-instructions.html (#3888) closes #3887 clarifies that wcag is not recommending use of placeholder text, and instead recommends accompanying text to provide the information (which could then be part of label or description - but those details are not applicable to this SC so not specified in the update) This helps better match [technique g89](https://www.w3.org/WAI/WCAG22/Techniques/general/G89.html) which includes the date format expectation as part of the label text --------- Co-authored-by: Patrick H. Lauke --- understanding/20/labels-or-instructions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/understanding/20/labels-or-instructions.html b/understanding/20/labels-or-instructions.html index fac5608a1f..8da1eca98a 100644 --- a/understanding/20/labels-or-instructions.html +++ b/understanding/20/labels-or-instructions.html @@ -92,7 +92,7 @@

    Examples of Labels or Instructions

    correct abbreviation. -
  • A field for entering a date contains initial text which indicates the correct format +
  • A field for entering a date has text instructions to indicate the correct format for the date.
  • From 9a291c981790a391cc01c5930d4e8fe6b08ce2e9 Mon Sep 17 00:00:00 2001 From: Mike Gower Date: Mon, 15 Jul 2024 08:52:08 -0700 Subject: [PATCH 08/25] Update H39.html (#2137) Update to address https://github.com/w3c/wcag/issues/2136 Tighten the language of [H39 Using caption elements to associate data table captions with data tables](https://www.w3.org/WAI/WCAG22/Techniques/html/H39) to isolate it to only passing 1.3.1 Info and Relationships: 1. if there is a visible text title/caption for the table, then 2. make sure it is referenced so that the title/caption is programmaticalyy associated. --------- Co-authored-by: Patrick H. Lauke Co-authored-by: Francis Storr --- techniques/html/H39.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/techniques/html/H39.html b/techniques/html/H39.html index 19963593e2..1fc656d6df 100644 --- a/techniques/html/H39.html +++ b/techniques/html/H39.html @@ -40,15 +40,14 @@

    Tests

    Procedure

    For each data table:

      -
    1. Check that the table has content that is presented as a table caption.
    2. Check that the table includes a caption element.
    3. -
    4. Check that the content of the caption element identifies the table.
    5. -
    +
  • Check that the text that titles or describes the table is included in the caption element.
  • +

    Expected Results

      -
    • #1, #2, and #3 are true.
    • +
    • #1 and #2 are true.
    From b2dc10aaa228c7c6e2b225f41b7788216d664970 Mon Sep 17 00:00:00 2001 From: EricDunsworth <1907279+EricDunsworth@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:52:46 -0400 Subject: [PATCH 09/25] Remove leftover references to Flash techniques (#3540) A few references to FLASH5 and FLASH7 were missed when #1142 resolved #1140. The leftovers seem to have resulted in broken FLASH# links appearing in some of WCAG 2.1 and 2.2's understanding pages: * Understanding SC 2.4.4: Link Purpose (In Context) (Level A) * WCAG 2.1: https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context#sufficient * WCAG 2.2: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context#sufficient * Understanding SC 2.4.9: Link Purpose (Link Only) (Level AAA) * WCAG 2.1: https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-link-only.html#sufficient * WCAG 2.2: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html#sufficient This should resolve it by removing the leftovers from the HTML and JSON versions of affected understanding pages. Closes https://github.com/w3c/wcag/issues/3928 --- guidelines/wcag.json | 32 ------------------- understanding/20/link-purpose-in-context.html | 12 ------- understanding/20/link-purpose-link-only.html | 12 ------- 3 files changed, 56 deletions(-) diff --git a/guidelines/wcag.json b/guidelines/wcag.json index d396c17bed..8e1bdcf24e 100644 --- a/guidelines/wcag.json +++ b/guidelines/wcag.json @@ -4765,14 +4765,6 @@ "title": "Using scripts to change the link text" } - , - { - "id": - "TECH:FLASH7" - , - "title": - "" - } ] @@ -4918,14 +4910,6 @@ , { "id": - "TECH:FLASH5" - , - "title": - "" - } - , - { - "id": "TECH:H80" , "title": @@ -5319,14 +5303,6 @@ "title": "Using scripts to change the link text" } - , - { - "id": - "TECH:FLASH7" - , - "title": - "" - } ] @@ -5395,14 +5371,6 @@ , { "id": - "TECH:FLASH5" - , - "title": - "" - } - , - { - "id": "TECH:H33" , "title": diff --git a/understanding/20/link-purpose-in-context.html b/understanding/20/link-purpose-in-context.html index 8d8ed0d0a8..4c7533ba7c 100644 --- a/understanding/20/link-purpose-in-context.html +++ b/understanding/20/link-purpose-in-context.html @@ -228,12 +228,6 @@

    Sufficient Techniques for Link Purpose (In Context)

    -
  • - - Using scripting to change control labels - -
  • - @@ -365,12 +359,6 @@

    Additional Techniques (Advisory) for Link Purpose (In Context)

    -
  • - - Combining adjacent image and text buttons for the same resource - -
  • -
  • Identifying the purpose of a link using link text combined with the preceding heading diff --git a/understanding/20/link-purpose-link-only.html b/understanding/20/link-purpose-link-only.html index 9007b3d65d..e22b02ce94 100644 --- a/understanding/20/link-purpose-link-only.html +++ b/understanding/20/link-purpose-link-only.html @@ -203,12 +203,6 @@

    Sufficient Techniques for Link Purpose (Link Only)

  • -
  • - - Using scripting to change control labels - -
  • - @@ -270,12 +264,6 @@

    Additional Techniques (Advisory) for Link Purpose (Link Only)

    -
  • - - Combining adjacent image and text buttons for the same resource - -
  • -
  • Supplementing link text with the title attribute From 485e01b52871862bc3bc7bfd8440cb9e04ca4396 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Wed, 17 Jul 2024 13:23:02 -0400 Subject: [PATCH 10/25] Update deploy workflow to use Eleventy build process (#3955) This replaces the existing files under `.github` (both `workflows` and `scripts`) with a new workflow using the Eleventy build process. - Current behavior RE `conformance-challenges`, `guidelines`, and `requirements` directories is retained - Also ensures necessary CSS is updated under `guidelines` - `techniques`, `understanding`, and `working-examples` are now generated by the Eleventy build process This has positive side effects over what's currently seen on github.io: - Fixes broken styles - Updates any auto-generated cross-links to guidelines to point to 2.2 editor's draft instead of 2.1 - All of the fixes mentioned in #3917 --- .github/scripts/deploy.sh | 28 ------------------ .github/workflows/11ty-publish.yaml | 44 ++++++++++++++++++++++++++++ .github/workflows/manual-publish.yml | 44 ---------------------------- 11ty/README.md | 17 ++++++++--- 11ty/cp-cvs.ts | 5 +++- 11ty/guidelines.ts | 2 +- eleventy.config.ts | 15 ++++++---- 7 files changed, 72 insertions(+), 83 deletions(-) delete mode 100755 .github/scripts/deploy.sh create mode 100644 .github/workflows/11ty-publish.yaml delete mode 100644 .github/workflows/manual-publish.yml diff --git a/.github/scripts/deploy.sh b/.github/scripts/deploy.sh deleted file mode 100755 index 05e06b0bc3..0000000000 --- a/.github/scripts/deploy.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -set -exu -# e: Exit immediately if a command exits with a non-zero status -# x: Print commands and their arguments as they are executed -# u: Treat unset variables as an error when substituting - -# NOTE: you probably need to add 'w3cbot' to the list of authorized users to push to your repository -git config --global user.email 87540780+w3cgruntbot@users.noreply.github.com -git config --global user.name w3cgruntbot -git config --global user.password $GITHUB_TOKEN - -REPO_URL="https://w3cbot:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git" - -cd ${LOCAL_DIR} - -git remote set-url origin "${REPO_URL}" - -if [[ -z $(git status --porcelain) ]]; then - echo "No changes to the output on this push; exiting." - exit 0 -fi - -git add -A . -git commit -m ":robot: Deploy to GitHub Pages: $GITHUB_SHA from branch $GITHUB_REF" - -git push $REPO_URL $BRANCH - -echo done \ No newline at end of file diff --git a/.github/workflows/11ty-publish.yaml b/.github/workflows/11ty-publish.yaml new file mode 100644 index 0000000000..35ca84577b --- /dev/null +++ b/.github/workflows/11ty-publish.yaml @@ -0,0 +1,44 @@ +name: CI + +# Reference documentation: https://docs.github.com/en/actions/reference + +# See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestbranchestags +on: + push: + branches: [main] + +jobs: + main: + name: deploy (11ty) + runs-on: ubuntu-20.04 + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + - name: Checkout gh-pages + uses: actions/checkout@v4 + with: + ref: gh-pages + path: _site + - name: Install Node.js and dependencies + uses: actions/setup-node@v4 + with: + cache: npm + node-version-file: '.nvmrc' + - name: Build + env: + WCAG_MODE: editors + run: | + npm i + npm run build + cp guidelines/guidelines.css guidelines/relative-luminance.html _site/guidelines/22 + curl https://labs.w3.org/spec-generator/?type=respec"&"url=https://raw.githack.com/$GITHUB_REPOSITORY/main/guidelines/index.html -o _site/guidelines/22/index.html -f --retry 3 + curl https://labs.w3.org/spec-generator/?type=respec"&"url=https://raw.githack.com/$GITHUB_REPOSITORY/main/requirements/22/index.html -o _site/requirements/22/index.html -f --retry 3 + curl https://labs.w3.org/spec-generator/?type=respec"&"url=https://raw.githack.com/$GITHUB_REPOSITORY/main/conformance-challenges/index.html -o _site/conformance-challenges/index.html -f --retry 3 + - name: Push + working-directory: _site + run: | + git config user.email 87540780+w3cgruntbot@users.noreply.github.com + git config user.name w3cgruntbot + git add -A . + git commit -m ":robot: Deploy to GitHub Pages: $GITHUB_SHA from branch $GITHUB_REF" + git push origin gh-pages diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml deleted file mode 100644 index f5eadf897c..0000000000 --- a/.github/workflows/manual-publish.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: CI - -# Reference documentation: https://docs.github.com/en/actions/reference - -# See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestbranchestags -on: - push: - branches: [main] - -jobs: - main: - name: deploy to - runs-on: ubuntu-20.04 - env: - GH_REF: github.com/w3c/wcag.git - steps: - - name: Checkout the repository - uses: actions/checkout@v2 - - name: Setup Java - # see https://github.com/actions/setup-java#supported-distributions - # note that this also deploys ant - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '11' - - name: before_install - run: | - tar -xzvf lib/apache-ant-1.10.6-bin.tar.gz - export PATH=`pwd`/apache-ant-1.10.6/bin:$PATH - - name: script - run: | - mkdir output - git clone --depth=1 --branch=gh-pages https://github.com/w3c/wcag.git output - curl https://labs.w3.org/spec-generator/?type=respec"&"url=https://raw.githack.com/w3c/wcag/main/guidelines/index.html -o output/guidelines/22/index.html -f --retry 3 - curl https://labs.w3.org/spec-generator/?type=respec"&"url=https://raw.githack.com/w3c/wcag/main/requirements/22/index.html -o output/requirements/22/index.html -f --retry 3 - curl https://labs.w3.org/spec-generator/?type=respec"&"url=https://raw.githack.com/w3c/wcag/main/conformance-challenges/index.html -o output/conformance-challenges/index.html -f --retry 3 - ant deploy - - name: deploy - run: .github/scripts/deploy.sh - env: - BRANCH: gh-pages - LOCAL_DIR: output - GITHUB_TOKEN: ${{ secrets.W3CGRUNTBOT_TOKEN }} - diff --git a/11ty/README.md b/11ty/README.md index 35b9ca85d8..048dab7cb4 100644 --- a/11ty/README.md +++ b/11ty/README.md @@ -40,16 +40,16 @@ Indicates top-level path of W3C CVS checkout, for WAI site updates (via `publish ### `WCAG_VERSION` -**Usage context:** `publish-w3c` script only; -this should currently not be changed, pending future improvements to `21` support. +**Usage context:** currently this should not be changed, pending future improvements to `21` support -Indicates WCAG version being built, in `XY` format (i.e. no `.`) +Indicates WCAG version being built, in `XY` format (i.e. no `.`). +Influences base URLs for links to guidelines, techniques, and understanding pages. **Default:** `22` ### `WCAG_MODE` -**Usage context:** should not need to be used manually except in specific testing scenarios +**Usage context:** should not need to be set manually except in specific testing scenarios Influences base URLs for links to guidelines, techniques, and understanding pages. Typically set by specific npm scripts or CI processes. @@ -60,6 +60,15 @@ Possible values: - `editors` - Sets base URLs appropriate for `gh-pages` publishing; used by deploy action - `publication` - Sets base URLs appropriate for WAI site publishing; used by `publish-w3c` script +### `GITHUB_REPOSITORY` + +**Usage context:** Automatically set during GitHub workflows; should not need to be set manually + +Influences base URLs for links to guidelines, techniques, and understanding pages, +when `WCAG_MODE=editors` is also set. + +**Default:** `w3c/wcag` + ## Other points of interest - The main configuration can be found in top-level `eleventy.config.ts` diff --git a/11ty/cp-cvs.ts b/11ty/cp-cvs.ts index 2bae08c4e7..3adf39bb7c 100644 --- a/11ty/cp-cvs.ts +++ b/11ty/cp-cvs.ts @@ -1,14 +1,17 @@ /** @fileoverview script to copy already-built output to CVS subfolders */ -import { copyFile, unlink } from "fs/promises"; import { glob } from "glob"; import { mkdirp } from "mkdirp"; +import { copyFile, unlink } from "fs/promises"; import { dirname, join } from "path"; +import { assertIsWcagVersion } from "./guidelines"; + const outputBase = "_site"; const cvsBase = process.env.WCAG_CVSDIR || "../../../w3ccvs"; const wcagVersion = process.env.WCAG_VERSION || "22"; +assertIsWcagVersion(wcagVersion); const wcagBase = `${cvsBase}/WWW/WAI/WCAG${wcagVersion}`; // Map (git) sources to (CVS) destinations, since some don't match case-sensitively diff --git a/11ty/guidelines.ts b/11ty/guidelines.ts index 5b081fc6bb..7d68a3bb4a 100644 --- a/11ty/guidelines.ts +++ b/11ty/guidelines.ts @@ -8,7 +8,7 @@ import { flattenDomFromFile, load } from "./cheerio"; import { generateId } from "./common"; export type WcagVersion = "20" | "21" | "22"; -function assertIsWcagVersion(v: string): asserts v is WcagVersion { +export function assertIsWcagVersion(v: string): asserts v is WcagVersion { if (!/^2[012]$/.test(v)) throw new Error(`Unexpected version found: ${v}`); } diff --git a/eleventy.config.ts b/eleventy.config.ts index 9528e68a12..205e253948 100644 --- a/eleventy.config.ts +++ b/eleventy.config.ts @@ -3,7 +3,7 @@ import compact from "lodash-es/compact"; import { copyFile } from "fs/promises"; import { CustomLiquid } from "11ty/CustomLiquid"; -import { actRules, getFlatGuidelines, getPrinciples } from "11ty/guidelines"; +import { actRules, assertIsWcagVersion, getFlatGuidelines, getPrinciples } from "11ty/guidelines"; import { getFlatTechniques, getTechniqueAssociations, @@ -15,7 +15,8 @@ import { generateUnderstandingNavMap, getUnderstandingDocs } from "11ty/understa import type { EleventyContext, EleventyData, EleventyEvent } from "11ty/types"; /** Version of WCAG to build */ -const version = "22"; +const version = process.env.WCAG_VERSION || "22"; +assertIsWcagVersion(version); const principles = await getPrinciples(); const flatGuidelines = getFlatGuidelines(principles); @@ -45,6 +46,8 @@ export type GlobalData = EleventyData & isUnderstanding?: boolean; }; +const [GH_ORG, GH_REPO] = (process.env.GITHUB_REPOSITORY || "w3c/wcag").split("/"); + const baseUrls = { guidelines: `https://www.w3.org/TR/WCAG${version}/`, techniques: "/techniques/", @@ -53,9 +56,11 @@ const baseUrls = { if (process.env.WCAG_MODE === "editors") { // For pushing to gh-pages - baseUrls.guidelines = "https://w3c.github.io/wcag/guidelines/"; - baseUrls.techniques = "https://w3c.github.io/wcag/techniques/"; - baseUrls.understanding = "https://w3c.github.io/wcag/understanding/"; + baseUrls.guidelines = `https://${GH_ORG}.github.io/${GH_REPO}/guidelines/${ + version === "21" ? "" : `${version}/` + }`; + baseUrls.techniques = `https://${GH_ORG}.github.io/${GH_REPO}/techniques/`; + baseUrls.understanding = `https://${GH_ORG}.github.io/${GH_REPO}/understanding/`; } else if (process.env.WCAG_MODE === "publication") { // For pushing to W3C site baseUrls.guidelines = `https://www.w3.org/TR/WCAG${version}/`; From 0440c75c71059fa18eb22dc56476cd57d9d1b576 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Wed, 17 Jul 2024 14:43:02 -0400 Subject: [PATCH 11/25] Fix token for Eleventy deploy workflow (#3972) --- .github/workflows/11ty-publish.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/11ty-publish.yaml b/.github/workflows/11ty-publish.yaml index 35ca84577b..adbb274be0 100644 --- a/.github/workflows/11ty-publish.yaml +++ b/.github/workflows/11ty-publish.yaml @@ -19,6 +19,7 @@ jobs: with: ref: gh-pages path: _site + token: ${{ secrets.W3CGRUNTBOT_TOKEN }} - name: Install Node.js and dependencies uses: actions/setup-node@v4 with: From e1f139fca722ccc60fed90494f95d3e136258d9b Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Mon, 22 Jul 2024 11:14:34 -0400 Subject: [PATCH 12/25] Don't force term names to lowercase in definition lists (#3978) Fixes a couple of issues in the build system: - A few term names (e.g. ASCII art) have uppercase letters, which the build was forcing to lowercase (this was apparently also true of the XSLT build) - Any linked terms containing newlines would not be recognized --- 11ty/CustomLiquid.ts | 8 ++++++-- 11ty/guidelines.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/11ty/CustomLiquid.ts b/11ty/CustomLiquid.ts index 49e071fd7a..9f8da18338 100644 --- a/11ty/CustomLiquid.ts +++ b/11ty/CustomLiquid.ts @@ -393,7 +393,7 @@ export class CustomLiquid extends Liquid { // where we have access to global data and the about box's HTML const $termLinks = $(termLinkSelector); const extractTermName = ($el: Cheerio) => { - const name = $el.text().trim().toLowerCase(); + const name = $el.text().toLowerCase().trim().replace(/\s*\n+\s*/, " "); const term = termsMap[name]; if (!term) { console.warn(`${scope.page.inputPath}: Term not found: ${name}`); @@ -433,7 +433,11 @@ export class CustomLiquid extends Liquid { } // Iterate over sorted names to populate alphabetized Key Terms definition list - termNames.sort(); + termNames.sort((a, b) => { + if (a.toLowerCase() < b.toLowerCase()) return -1; + if (a.toLowerCase() > b.toLowerCase()) return 1; + return 0; + }); for (const name of termNames) { const term = termsMap[name]; // Already verified existence in the earlier loop $termsList.append( diff --git a/11ty/guidelines.ts b/11ty/guidelines.ts index 7d68a3bb4a..def71cc02f 100644 --- a/11ty/guidelines.ts +++ b/11ty/guidelines.ts @@ -208,11 +208,15 @@ export async function getTermsMap() { // but the XSLT process generates id from the element's text which is not always the same id: `dfn-${generateId($el.text())}`, definition: getContentHtml($el.parent().next()), - name: $el.text().toLowerCase(), + name: $el.text(), trId: el.attribs.id, }; - const names = [term.name].concat((el.attribs["data-lt"] || "").toLowerCase().split("|")); + // Include both original and all-lowercase version to simplify lookups + // (since most synonyms are lowercase) while preserving case in name + const names = [term.name, term.name.toLowerCase()].concat( + (el.attribs["data-lt"] || "").toLowerCase().split("|") + ); for (const name of names) terms[name] = term; }); From 588d0c007babd19f7aa80611a0dd14cb798c9687 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Thu, 25 Jul 2024 01:27:58 +0100 Subject: [PATCH 13/25] Fix broken description paragraph for SM6 (#3932) Closes https://github.com/w3c/wcag/issues/3919 --- techniques/smil/SM6.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/techniques/smil/SM6.html b/techniques/smil/SM6.html index ade2f38f7a..51c9d0ecc0 100644 --- a/techniques/smil/SM6.html +++ b/techniques/smil/SM6.html @@ -18,10 +18,10 @@

    When to Use

    Description

    The objective of this technique is to provide a way for people who are blind - able to access the material. With this technique a description of the video or otherwise have trouble seeing the video in audio-visual material to be - in the audio-visual material.

    + able to access the material. With this technique a description of the video is provided via audio description that will fit into the gaps in the dialogue + in the audio-visual material.

    Examples

    From 945ab1dffc39e23deac41ab451ae2d101ab19fc9 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Thu, 25 Jul 2024 01:28:18 +0100 Subject: [PATCH 14/25] Update link in 3.2.6 understanding (#3929) Closes https://github.com/w3c/wcag/issues/3921 --- understanding/22/consistent-help.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/understanding/22/consistent-help.html b/understanding/22/consistent-help.html index f4bcbf851a..f0bb522d29 100644 --- a/understanding/22/consistent-help.html +++ b/understanding/22/consistent-help.html @@ -150,8 +150,7 @@

    Resources

    From 47a54da7e0bb2a575c6798e40904fa5caedf3f54 Mon Sep 17 00:00:00 2001 From: Momdo Nakamura Date: Thu, 25 Jul 2024 09:28:39 +0900 Subject: [PATCH 15/25] Fix link in Timing Adjustable (#3944) Fixes a broken link where the `href` had `http://https://` instead of `https://` --- understanding/20/timing-adjustable.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/understanding/20/timing-adjustable.html b/understanding/20/timing-adjustable.html index 8889700473..088983cb0a 100644 --- a/understanding/20/timing-adjustable.html +++ b/understanding/20/timing-adjustable.html @@ -45,7 +45,7 @@

    Intent of Timing Adjustable

    It also includes content that is advancing or updating at a rate beyond the user's ability to read and/or understand it. In other words, animated, moving or scrolling content introduces a time limit on a users ability to read content.

    -

    This success criterion is generally not applicable when the content repeats or is synchronized with other content, so long as the information and data is adjustable or otherwise under the control of the end user. Examples of time limits for which this success criterion is not applicable include scrolling text that repeats, captioning, and carousels. These are situations which do include time limits, but the content is still available to the user because it has controls for accessing it, as specified in 2.2.2 Pause, Stop, Hide.

    +

    This success criterion is generally not applicable when the content repeats or is synchronized with other content, so long as the information and data is adjustable or otherwise under the control of the end user. Examples of time limits for which this success criterion is not applicable include scrolling text that repeats, captioning, and carousels. These are situations which do include time limits, but the content is still available to the user because it has controls for accessing it, as specified in 2.2.2 Pause, Stop, Hide.

    In some cases, however, it is not possible to change the time limit (for example, for an auction or other real-time event) and exceptions are therefore provided for those cases.

    From 2f24a2f2f229e3b0cb8900e1bdddf6da1f80e3a3 Mon Sep 17 00:00:00 2001 From: Giacomo Petri Date: Thu, 25 Jul 2024 02:29:32 +0200 Subject: [PATCH 16/25] Technique F110 - Added examples for sticky header and sticky footer that covers completely elements receiving focus (#3775) Closes: #3764 Description: - Added examples showcasing the failure of Technique F110. - Updated F110 by incorporating links to the failing examples. Note: The examples contain extensive content (utilizing "Lorem Ipsum" text) to ensure that regardless of the viewport, zoom level, or user preferences, at least one element receiving focus is consistently obscured by the sticky element. Edit: Demos available here: - Sticky header: https://codepen.io/Giacomo-Petri/full/OJGQOBy - Sticky footer: https://codepen.io/Giacomo-Petri/full/KKYQyrq --- techniques/failures/F110.html | 8 +- .../sticky-footer.html | 160 +++++++++++++++++ .../sticky-header.html | 162 ++++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 working-examples/sticky-elements-hiding-focused-elements/sticky-footer.html create mode 100644 working-examples/sticky-elements-hiding-focused-elements/sticky-header.html diff --git a/techniques/failures/F110.html b/techniques/failures/F110.html index 8ed761cb64..6327f0b4ae 100644 --- a/techniques/failures/F110.html +++ b/techniques/failures/F110.html @@ -25,11 +25,17 @@

    Examples

    Sticky footer

    A Web page has a sticky footer, an element that stays visible at the bottom of the viewport as the user scrolls the page. The footer is tall enough to completely cover the element in focus as a user tabs down the page.

    +

    + Failure example of sticky footer that completely cover elements in focus as a user tabs down the page. +

    Sticky header

    A Web page has a sticky header, an element that stays visible at the top of the viewport as the user scrolls the page. The header is tall enough to completely cover the element in focus as a user tabs up the page.

    -
    +

    + Failure example of sticky header that completely cover elements in focus as a user tabs up the page. +

    +

    Tests

    diff --git a/working-examples/sticky-elements-hiding-focused-elements/sticky-footer.html b/working-examples/sticky-elements-hiding-focused-elements/sticky-footer.html new file mode 100644 index 0000000000..5a29bf2824 --- /dev/null +++ b/working-examples/sticky-elements-hiding-focused-elements/sticky-footer.html @@ -0,0 +1,160 @@ + + + + Sticky footer is hiding focused elements + + + +
    +

    Sticky footer is hiding focused elements

    +
    +

    This demo page is crafted to illustrate Example 1: Sticky footer of the failing technique F110: Failure of Success Criterion 2.4.11 due to a sticky footers or headers hiding focused elements.

    +

    All links and buttons on this page are non-functional; links merely serve as focusable elements and anchor directing to the top of the page; buttons are simply not functional.

    +

    To identify the problem, please navigate through the page using the TAB key until one of the elements receiving focus becomes obscured by the sticky element.

    +
    +
    +

    You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday, and my first task is to assure my dear sister of my welfare and increasing confidence in the success of my undertaking.

    +

    I am already far north of London, and as I walk in the streets of Petersburgh, I feel a cold northern breeze play upon my cheeks, which braces my nerves and fills me with delight. Do you understand this feeling? This breeze, which has travelled from the regions towards which I am advancing, gives me a foretaste of those icy climes. Inspirited by this wind of promise, my daydreams become more fervent and vivid. I try in vain to be persuaded that the pole is the seat of frost and desolation; it ever presents itself to my imagination as the region of beauty and delight. There, Margaret, the sun is for ever visible, its broad disk just skirting the horizon and diffusing a perpetual splendour. There—for with your leave, my sister, I will put some trust in preceding navigators—there snow and frost are banished; and, sailing over a calm sea, we may be wafted to a land surpassing in wonders and in beauty every region hitherto discovered on the habitable globe. Its productions and features may be without example, as the phenomena of the heavenly bodies undoubtedly are in those undiscovered solitudes. What may not be expected in a country of eternal light? I may there discover the wondrous power which attracts the needle and may regulate a thousand celestial observations that require only this voyage to render their seeming eccentricities consistent for ever. I shall satiate my ardent curiosity with the sight of a part of the world never before visited, and may tread a land never before imprinted by the foot of man. These are my enticements, and they are sufficient to conquer all fear of danger or death and to induce me to commence this laborious voyage with the joy a child feels when he embarks in a little boat, with his holiday mates, on an expedition of discovery up his native river. But supposing all these conjectures to be false, you cannot contest the inestimable benefit which I shall confer on all mankind, to the last generation, by discovering a passage near the pole to those countries, to reach which at present so many months are requisite; or by ascertaining the secret of the magnet, which, if at all possible, can only be effected by an undertaking such as mine.

    +

    These reflections have dispelled the agitation with which I began my letter, and I feel my heart glow with an enthusiasm which elevates me to heaven, for nothing contributes so much to tranquillise the mind as a steady purpose—a point on which the soul may fix its intellectual eye. This expedition has been the favourite dream of my early years. I have read with ardour the accounts of the various voyages which have been made in the prospect of arriving at the North Pacific Ocean through the seas which surround the pole. You may remember that a history of all the voyages made for purposes of discovery composed the whole of our good Uncle Thomas’ library. My education was neglected, yet I was passionately fond of reading. These volumes were my study day and night, and my familiarity with them increased that regret which I had felt, as a child, on learning that my father’s dying injunction had forbidden my uncle to allow me to embark in a seafaring life.

    +

    These visions faded when I perused, for the first time, those poets whose effusions entranced my soul and lifted it to heaven. I also became a poet and for one year lived in a paradise of my own creation; I imagined that I also might obtain a niche in the temple where the names of Homer and Shakespeare are consecrated. You are well acquainted with my failure and how heavily I bore the disappointment. But just at that time I inherited the fortune of my cousin, and my thoughts were turned into the channel of their earlier bent.

    +

    Six years have passed since I resolved on my present undertaking. I can, even now, remember the hour from which I dedicated myself to this great enterprise. I commenced by inuring my body to hardship. I accompanied the whale-fishers on several expeditions to the North Sea; I voluntarily endured cold, famine, thirst, and want of sleep; I often worked harder than the common sailors during the day and devoted my nights to the study of mathematics, the theory of medicine, and those branches of physical science from which a naval adventurer might derive the greatest practical advantage. Twice I actually hired myself as an under-mate in a Greenland whaler, and acquitted myself to admiration. I must own I felt a little proud when my captain offered me the second dignity in the vessel and entreated me to remain with the greatest earnestness, so valuable did he consider my services.

    +

    And now, dear Margaret, do I not deserve to accomplish some great purpose? My life might have been passed in ease and luxury, but I preferred glory to every enticement that wealth placed in my path. Oh, that some encouraging voice would answer in the affirmative! My courage and my resolution is firm; but my hopes fluctuate, and my spirits are often depressed. I am about to proceed on a long and difficult voyage, the emergencies of which will demand all my fortitude: I am required not only to raise the spirits of others, but sometimes to sustain my own, when theirs are failing.

    +

    This is the most favourable period for travelling in Russia. They fly quickly over the snow in their sledges; the motion is pleasant, and, in my opinion, far more agreeable than that of an English stagecoach. The cold is not excessive, if you are wrapped in furs—a dress which I have already adopted, for there is a great difference between walking the deck and remaining seated motionless for hours, when no exercise prevents the blood from actually freezing in your veins. I have no ambition to lose my life on the post-road between St. Petersburgh and Archangel.

    +

    I shall depart for the latter town in a fortnight or three weeks; and my intention is to hire a ship there, which can easily be done by paying the insurance for the owner, and to engage as many sailors as I think necessary among those who are accustomed to the whale-fishing. I do not intend to sail until the month of June; and when shall I return? Ah, dear sister, how can I answer this question? If I succeed, many, many months, perhaps years, will pass before you and I may meet. If I fail, you will see me again soon, or never.

    +

    Farewell, my dear, excellent Margaret. Heaven shower down blessings on you, and save me, that I may again and again testify my gratitude for all your love and kindness.

    +
    + +
    +
    + + +
    + + diff --git a/working-examples/sticky-elements-hiding-focused-elements/sticky-header.html b/working-examples/sticky-elements-hiding-focused-elements/sticky-header.html new file mode 100644 index 0000000000..0b119e2e89 --- /dev/null +++ b/working-examples/sticky-elements-hiding-focused-elements/sticky-header.html @@ -0,0 +1,162 @@ + + + + Sticky header is hiding focused elements + + + + + +
    +

    Sticky header is hiding focused elements

    +
    +

    This demo page is crafted to illustrate Example 1: Sticky header of the failing technique F110: Failure of Success Criterion 2.4.11 due to a sticky footers or headers hiding focused elements.

    +

    All links and buttons on this page are non-functional; links merely serve as focusable elements and anchor directing to the top of the page; buttons are simply not functional.

    +

    To identify the problem, please navigate through the page using the TAB key until you reach the bottom, then return to the top using SHIFT + TAB.

    +
    +
    +

    You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday, and my first task is to assure my dear sister of my welfare and increasing confidence in the success of my undertaking.

    +

    I am already far north of London, and as I walk in the streets of Petersburgh, I feel a cold northern breeze play upon my cheeks, which braces my nerves and fills me with delight. Do you understand this feeling? This breeze, which has travelled from the regions towards which I am advancing, gives me a foretaste of those icy climes. Inspirited by this wind of promise, my daydreams become more fervent and vivid. I try in vain to be persuaded that the pole is the seat of frost and desolation; it ever presents itself to my imagination as the region of beauty and delight. There, Margaret, the sun is for ever visible, its broad disk just skirting the horizon and diffusing a perpetual splendour. There—for with your leave, my sister, I will put some trust in preceding navigators—there snow and frost are banished; and, sailing over a calm sea, we may be wafted to a land surpassing in wonders and in beauty every region hitherto discovered on the habitable globe. Its productions and features may be without example, as the phenomena of the heavenly bodies undoubtedly are in those undiscovered solitudes. What may not be expected in a country of eternal light? I may there discover the wondrous power which attracts the needle and may regulate a thousand celestial observations that require only this voyage to render their seeming eccentricities consistent for ever. I shall satiate my ardent curiosity with the sight of a part of the world never before visited, and may tread a land never before imprinted by the foot of man. These are my enticements, and they are sufficient to conquer all fear of danger or death and to induce me to commence this laborious voyage with the joy a child feels when he embarks in a little boat, with his holiday mates, on an expedition of discovery up his native river. But supposing all these conjectures to be false, you cannot contest the inestimable benefit which I shall confer on all mankind, to the last generation, by discovering a passage near the pole to those countries, to reach which at present so many months are requisite; or by ascertaining the secret of the magnet, which, if at all possible, can only be effected by an undertaking such as mine.

    +

    These reflections have dispelled the agitation with which I began my letter, and I feel my heart glow with an enthusiasm which elevates me to heaven, for nothing contributes so much to tranquillise the mind as a steady purpose—a point on which the soul may fix its intellectual eye. This expedition has been the favourite dream of my early years. I have read with ardour the accounts of the various voyages which have been made in the prospect of arriving at the North Pacific Ocean through the seas which surround the pole. You may remember that a history of all the voyages made for purposes of discovery composed the whole of our good Uncle Thomas’ library. My education was neglected, yet I was passionately fond of reading. These volumes were my study day and night, and my familiarity with them increased that regret which I had felt, as a child, on learning that my father’s dying injunction had forbidden my uncle to allow me to embark in a seafaring life.

    +

    These visions faded when I perused, for the first time, those poets whose effusions entranced my soul and lifted it to heaven. I also became a poet and for one year lived in a paradise of my own creation; I imagined that I also might obtain a niche in the temple where the names of Homer and Shakespeare are consecrated. You are well acquainted with my failure and how heavily I bore the disappointment. But just at that time I inherited the fortune of my cousin, and my thoughts were turned into the channel of their earlier bent.

    +

    Six years have passed since I resolved on my present undertaking. I can, even now, remember the hour from which I dedicated myself to this great enterprise. I commenced by inuring my body to hardship. I accompanied the whale-fishers on several expeditions to the North Sea; I voluntarily endured cold, famine, thirst, and want of sleep; I often worked harder than the common sailors during the day and devoted my nights to the study of mathematics, the theory of medicine, and those branches of physical science from which a naval adventurer might derive the greatest practical advantage. Twice I actually hired myself as an under-mate in a Greenland whaler, and acquitted myself to admiration. I must own I felt a little proud when my captain offered me the second dignity in the vessel and entreated me to remain with the greatest earnestness, so valuable did he consider my services.

    +

    And now, dear Margaret, do I not deserve to accomplish some great purpose? My life might have been passed in ease and luxury, but I preferred glory to every enticement that wealth placed in my path. Oh, that some encouraging voice would answer in the affirmative! My courage and my resolution is firm; but my hopes fluctuate, and my spirits are often depressed. I am about to proceed on a long and difficult voyage, the emergencies of which will demand all my fortitude: I am required not only to raise the spirits of others, but sometimes to sustain my own, when theirs are failing.

    +

    This is the most favourable period for travelling in Russia. They fly quickly over the snow in their sledges; the motion is pleasant, and, in my opinion, far more agreeable than that of an English stagecoach. The cold is not excessive, if you are wrapped in furs—a dress which I have already adopted, for there is a great difference between walking the deck and remaining seated motionless for hours, when no exercise prevents the blood from actually freezing in your veins. I have no ambition to lose my life on the post-road between St. Petersburgh and Archangel.

    +

    I shall depart for the latter town in a fortnight or three weeks; and my intention is to hire a ship there, which can easily be done by paying the insurance for the owner, and to engage as many sailors as I think necessary among those who are accustomed to the whale-fishing. I do not intend to sail until the month of June; and when shall I return? Ah, dear sister, how can I answer this question? If I succeed, many, many months, perhaps years, will pass before you and I may meet. If I fail, you will see me again soon, or never.

    +

    Farewell, my dear, excellent Margaret. Heaven shower down blessings on you, and save me, that I may again and again testify my gratitude for all your love and kindness.

    +
    + +
    + + + From 5d51bfb9f5ee1534e724a4a88dc56c061f78cd64 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Thu, 25 Jul 2024 01:30:10 +0100 Subject: [PATCH 17/25] Simplify/correct definition links (#3930) Closes https://github.com/w3c/wcag/issues/3920 --------- Co-authored-by: Mike Gower --- techniques/aria/ARIA22.html | 4 ++-- techniques/client-side-script/SCR19.html | 2 +- techniques/failures/F103.html | 2 +- techniques/failures/F109.html | 2 +- techniques/failures/F61.html | 2 +- techniques/general/G212.html | 4 ++-- techniques/server-side-script/SVR3.html | 2 +- understanding/20/name-role-value.html | 3 +-- .../20/three-flashes-or-below-threshold.html | 2 +- understanding/22/focus-appearance.html | 13 +++++-------- understanding/intro.html | 2 +- 11 files changed, 17 insertions(+), 21 deletions(-) diff --git a/techniques/aria/ARIA22.html b/techniques/aria/ARIA22.html index b26a3f3024..13b5c45310 100644 --- a/techniques/aria/ARIA22.html +++ b/techniques/aria/ARIA22.html @@ -18,7 +18,7 @@

    When to Use

    Description

    - This technique uses the status role from the ARIA specification to notify Assistive Technologies (AT) when content has been updated with information about the user's or application's status. This is done by adding role="status" to the element that contains the status message. The aria live region role of status has an implicit aria-live value of polite, which allows a user to be notified via AT (such as a screen reader) when status messages are added. The role of status also has a default aria-atomic value of true, so that updates to the container marked with a role of status will result in the AT presenting the entire contents of the container to the user, including any author-defined labels (or additional nested elements). Such additional context can be critical where the status message text alone will not provide an equivalent to the visual experience. The content of the aria-live container is automatically read by the AT, without the AT having to focus on the place where the text is displayed. See WAI-ARIA status (role) for more details.

    + This technique uses the status role from the ARIA specification to notify Assistive Technologies (AT) when content has been updated with information about the user's or application's status. This is done by adding role="status" to the element that contains the status message. The aria live region role of status has an implicit aria-live value of polite, which allows a user to be notified via AT (such as a screen reader) when status messages are added. The role of status also has a default aria-atomic value of true, so that updates to the container marked with a role of status will result in the AT presenting the entire contents of the container to the user, including any author-defined labels (or additional nested elements). Such additional context can be critical where the status message text alone will not provide an equivalent to the visual experience. The content of the aria-live container is automatically read by the AT, without the AT having to focus on the place where the text is displayed. See WAI-ARIA status (role) for more details.

    @@ -45,7 +45,7 @@

    Updating the shopping cart status

    Tests

    Procedure

    -

    For each status message:

    +

    For each status message:

    1. Check that the container destined to hold the status message has a role attribute with a value of status before the status message occurs.
    2. Check that when the status message is triggered, it is inside the container.
    3. diff --git a/techniques/client-side-script/SCR19.html b/techniques/client-side-script/SCR19.html index dc15b63f50..4e479cc70e 100644 --- a/techniques/client-side-script/SCR19.html +++ b/techniques/client-side-script/SCR19.html @@ -119,7 +119,7 @@ Accessible Forms using WCAG 2.0
    4. - Change of context definition + Change of context definition
    5. diff --git a/techniques/failures/F103.html b/techniques/failures/F103.html index c519d2aa52..aabec5461e 100644 --- a/techniques/failures/F103.html +++ b/techniques/failures/F103.html @@ -29,7 +29,7 @@

      Description

    6. the new content does not take focus (does not change context);
    7. the new content provides information to the user on the outcome of an action, the state of an application, the progress of a process, or the existence of errors.
    - Where updated content does not conform to the definition of status message, a failure of 4.1.3 has not taken place.

    + Where updated content does not conform to the definition of status messages, a failure of 4.1.3 has not taken place.

    The second step in this failure technique involves examining code. Where dynamic content meets the definition of a status message, its container can be examined for an appropriate WAI-ARIA role or property which allows it to be programmatically determinable as a status message. Currently there are only a small number of techniques available to indicate status messages to assistive technologies. They are:

    • the HTML output element
    • diff --git a/techniques/failures/F109.html b/techniques/failures/F109.html index 55148c97dc..39f9fccf8c 100644 --- a/techniques/failures/F109.html +++ b/techniques/failures/F109.html @@ -23,7 +23,7 @@

      Description

      Requiring users to authenticate by entering a password or code in a different format from which it was originally created is a failure to meet Success Criteria 3.3.8 and 3.3.9 (unless alternative authentication methods are available). The string to be entered could include a password, verification code, or any string of characters the user has to remember or record to authenticate.

      -

      If a user is required to enter individual characters across multiple fields in a way that prevents pasting the password in a single action, it prevents use of a password manager or pasting from local copy of the password. This means users cannot avoid transcription, resulting in a cognitive function test. This applies irrespective of whether users are required to enter all characters in the string, or just a subset.

      +

      If a user is required to enter individual characters across multiple fields in a way that prevents pasting the password in a single action, it prevents use of a password manager or pasting from local copy of the password. This means users cannot avoid transcription, resulting in a cognitive function test. This applies irrespective of whether users are required to enter all characters in the string, or just a subset.

    diff --git a/techniques/failures/F61.html b/techniques/failures/F61.html index 9de1b0738b..334cd528c5 100644 --- a/techniques/failures/F61.html +++ b/techniques/failures/F61.html @@ -3,7 +3,7 @@ automatic update that the user cannot disable from within the content

    ID: F61

    Technology: failures

    Type: Failure

    When to Use

    General

    Description

    -

    This document describes a failure that occurs when the content in the main viewport viewport is automatically updated, and there is no option for a user to disable this behavior.

    +

    This document describes a failure that occurs when the content in the main viewport is automatically updated, and there is no option for a user to disable this behavior.

    Two procedures are presented below to test for the existence of a failure against Success Criterion 3.2.5. Procedure 1 is the preferred procedure and assumes that content authors have access to the code that generates the viewport content.

    However there may be instances where this may not be possible (eg: in certain content management systems, application environments such as django or ruby-on-rails, or content generated through scripting languages such as AJAX or PHP that are generated by third parties.) To that end, the second procedure is supplied to allow testing in these instances. Note that timeframes are indicative only, and that any change after any amount of time should be treated as a failure if the test otherwise does not pass the other step evaluations.

    Examples

    diff --git a/techniques/general/G212.html b/techniques/general/G212.html index 55bff4f304..425bd1a83b 100644 --- a/techniques/general/G212.html +++ b/techniques/general/G212.html @@ -38,7 +38,7 @@

    Using a native button or link in HTML

    Using a native button in iOS or Android

    In native buttons in iOS and Android onclick events are triggered on the up-event by default.

    -

    The WCAG standard itself applies to web pages at a URL, and therefore this example is provided as helpful supplementary advice for those looking to implement the WCAG2ICT for native applications.

    +

    The WCAG standard itself applies to web pages, and therefore this example is provided as helpful supplementary advice for those looking to implement the WCAG2ICT for native applications.

    @@ -50,7 +50,7 @@

    Procedure

    1. Activate the down-event then move the pointer outside the target before triggering the up-event, and then release the pointer to trigger the up-event.
    2. Check that the action was not triggered when the pointer is released outside of the hit area for the target.
    3. -
    4. If the action is triggered, check that the action is reversible.
    5. +
    6. If the action is triggered, check that the action is reversible.
    diff --git a/techniques/server-side-script/SVR3.html b/techniques/server-side-script/SVR3.html index 2c5d2273fc..eb93d8bb19 100644 --- a/techniques/server-side-script/SVR3.html +++ b/techniques/server-side-script/SVR3.html @@ -17,7 +17,7 @@

    When to Use

    Description

    The objective of this technique is to ensure that users can obtain an accessible version of content where both non-conforming and conforming versions are provided.

    -

    Conformance Requirement 1 allows non-conforming pages to be included within the scope of conformance as long as they have a "conforming alternate version". It is not always possible for authors to include accessibility supported links to conforming content from within non-conforming content. Therefore, authors may need to rely on the use of Server Side Scripting technologies (ex. PHP, ASP, JSP) to ensure that the non-conforming version can only be reached from a conforming page.

    +

    Conformance Requirement 1 allows non-conforming pages to be included within the scope of conformance as long as they have a conforming alternate version. It is not always possible for authors to include accessibility supported links to conforming content from within non-conforming content. Therefore, authors may need to rely on the use of Server Side Scripting technologies (ex. PHP, ASP, JSP) to ensure that the non-conforming version can only be reached from a conforming page.

    This technique describes how to use information provided by the HTTP referer to ensure that non-conforming content can only be reached from a conforming page. The HTTP referer header is set by the user agent and contains the URI of the page (if any) which referred the user agent to the non-conforming page.

    To implement this technique, an author identifies the URI for the conforming version of the content, for each non-conforming page. When a request for the non-conforming version of a page is received, the server compares the value of the HTTP referer header against the URI of the conforming version to determine whether the link to the non-conforming version came from the conforming version. The non-conforming version is only served if the HTTP referer matches the URI of the non-conforming version. Otherwise, the user is redirected to the conforming version of the content. Note that when comparing the URI in the HTTP referer header, non-relevant variations in the URI, such as in the query and target, should be taken into account.

    diff --git a/understanding/20/name-role-value.html b/understanding/20/name-role-value.html index 18e09ad1ad..e0db538bbb 100644 --- a/understanding/20/name-role-value.html +++ b/understanding/20/name-role-value.html @@ -42,8 +42,7 @@

    Intent of Name, Role, Value

    on what the control represents. Specifics about such information are defined by other specifications, such as WAI-ARIA, or the relevant platform standards. Another factor to consider is whether there is sufficient - accessibility support - with assistive technologies to convey the information as specified. + accessibility support with assistive technologies to convey the information as specified.

    A particularly important state of a user interface control is whether or not it has diff --git a/understanding/20/three-flashes-or-below-threshold.html b/understanding/20/three-flashes-or-below-threshold.html index e0de2ab4f2..9fa345e15f 100644 --- a/understanding/20/three-flashes-or-below-threshold.html +++ b/understanding/20/three-flashes-or-below-threshold.html @@ -53,7 +53,7 @@

    Intent of Three Flashes or Below Threshold

    represents the central vision portion of the eye, where people are most susceptible to photo stimuli.)

    -

    With the proliferation of devices of varying screen sizes (from small hand-helds to large living room displays), as well as the adoption of CSS pixels as a density-independent unit of measurement, the prior assessment criteria may seem outdated. However, an image of a consistent size uses up relatively the same percentage of a user's visual field on any device. On a large screen, the image takes up less size, but the large screen takes up a larger part of the visual field. On a mobile screen, the image may take up most or all of the screen; however, the mobile screen itself takes up a smaller portion of the user's visual field. So the same dimension of the flashing content, represented in CSS pixels can still provide a consistent means of assessment. Substituting CSS pixels for the original pixel block means that the combined area of flashing becomes 341 x 256 CSS pixels, or a flashing area of 87,296 CSS pixels.

    +

    With the proliferation of devices of varying screen sizes (from small hand-helds to large living room displays), as well as the adoption of CSS pixels as a density-independent unit of measurement, the prior assessment criteria may seem outdated. However, an image of a consistent size uses up relatively the same percentage of a user's visual field on any device. On a large screen, the image takes up less size, but the large screen takes up a larger part of the visual field. On a mobile screen, the image may take up most or all of the screen; however, the mobile screen itself takes up a smaller portion of the user's visual field. So the same dimension of the flashing content, represented in CSS pixels can still provide a consistent means of assessment. Substituting CSS pixels for the original pixel block means that the combined area of flashing becomes 341 x 256 CSS pixels, or a flashing area of 87,296 CSS pixels.

    Content should be analyzed at the largest scale at which a user may view the content, and at the standard zoom level of the user agent. For example, with a video that may play in an area of a web page and also at full screen, the video should be analyzed for risks at full screen.

    diff --git a/understanding/22/focus-appearance.html b/understanding/22/focus-appearance.html index 55f6a535a8..5426fd0a9e 100644 --- a/understanding/22/focus-appearance.html +++ b/understanding/22/focus-appearance.html @@ -38,9 +38,8 @@

    Intent of Focus Appearance

    adequate contrast against the background in each of its states, Focus Appearance requires sufficient contrast for the focus indicator itself.

    -

    For sighted people with mobility impairments who use a keyboard or a device that utilizes the keyboard interface (such as a switch or - voice input), knowing the current point of focus is very important. Visible focus must also meet the needs +

    For sighted people with mobility impairments who use a keyboard or a device that utilizes the keyboard interface + (such as a switch or voice input), knowing the current point of focus is very important. Visible focus must also meet the needs of users with low vision, who may also rely on the keyboard.

    A keyboard focus indicator can take different forms. This Success Criterion encourages the use of a solid @@ -594,9 +593,8 @@

    Focus indicator around only the subcomponent

    Where something with focus is not a user interface component

    Some pages contain very large editing regions, such as web implementations of word processors and code editors. Unlike a textarea element, which is a user interface component, these large - editing regions do not typically meet the definition of user interface - components; they are not "perceived by users as a single control for a distinct function." + editing regions do not typically meet the definition of user interface components; + they are not "perceived by users as a single control for a distinct function." Providing focus indicators around such editing regions may still be beneficial to some; however, where the region is not perceived as a single control, it is not covered by this Success Criterion. The web page will still need to provide a insertion point (caret indicator) in such editing regions in order to @@ -675,8 +673,7 @@

    Modifying the focus indicator background

    Altering the body element's background-color attribute is one way of altering the pixels directly adjacent to the indicator in most implementations. However, specifying a value of white (#FFFFFF) does not nullify this - exception since, as established in the third note of the contrast ratio definition, the + exception since, as established in the third note of the contrast ratio definition, the default ("unspecified") color is assumed to be white.

    diff --git a/understanding/intro.html b/understanding/intro.html index 0ccbf90ac6..89fccfbabc 100644 --- a/understanding/intro.html +++ b/understanding/intro.html @@ -89,7 +89,7 @@

    The Guidelines

    Success Criteria

    -

    Under each guideline, there are success criteria that describe specifically what must be achieved in order to conform to this standard. They are similar to the "checkpoints" in WCAG 1.0. Each success criterion is written as a statement that will be either true or false when specific Web content is tested against it. The success criteria are written to be technology neutral.

    +

    Under each guideline, there are success criteria that describe specifically what must be achieved in order to conform to this standard. They are similar to the "checkpoints" in WCAG 1.0. Each success criterion is written as a statement that will be either true or false when specific Web content is tested against it. The success criteria are written to be technology neutral.

    All WCAG 2 success criteria are written as testable criteria for objectively determining if content satisfies the success criteria. While some of the testing can be automated using software evaluation programs, others require human testers for part or all of the test.

    Although content may satisfy the success criteria, the content may not always be usable by people with a wide variety of disabilities. Professional reviews utilizing recognized qualitative heuristics are important in achieving accessibility for some audiences. In addition, usability testing is recommended. Usability testing aims to determine how well people can use the content for its intended purpose.

    The content should be tested by those who understand how people with different types of disabilities use the Web. It is recommended that users with disabilities be included in test groups when performing human testing.

    From deb91ea8f8a9cf9fb86bbd0f537d76a0a35fa590 Mon Sep 17 00:00:00 2001 From: Hidde de Vries <160571138+hidde-logius@users.noreply.github.com> Date: Thu, 25 Jul 2024 02:30:27 +0200 Subject: [PATCH 18/25] Update broken link, remove double link in F47 (#3701) MDN no longer has a page on the blink element, replacing it with a link to Wikipedia for historical reference. The second link went to the same place, also removed that as it was redundant. --- techniques/failures/F47.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/techniques/failures/F47.html b/techniques/failures/F47.html index e6f50b0be3..f65fd7e5a6 100644 --- a/techniques/failures/F47.html +++ b/techniques/failures/F47.html @@ -25,12 +25,9 @@
    - \ No newline at end of file + From d3857ba76aa46f7481373e16003d4061afa03389 Mon Sep 17 00:00:00 2001 From: Francis Storr Date: Wed, 24 Jul 2024 17:30:46 -0700 Subject: [PATCH 19/25] Updating broken and outdated links in ARIA6 (#2358) Closes #317 --- techniques/aria/ARIA6.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/techniques/aria/ARIA6.html b/techniques/aria/ARIA6.html index 929da3a2b4..d9a17917ca 100644 --- a/techniques/aria/ARIA6.html +++ b/techniques/aria/ARIA6.html @@ -2,12 +2,12 @@

    Technologies that support Accessible Rich Internet Applications (WAI-ARIA).

    Description

    The purpose of this technique is to provide a label for objects that can be read by assistive technology. The aria-label attribute provides the text label for an object, such as a button. When a screen reader encounters the object, the aria-label text is read so that the user will know what it is.

    -

    Authors should be aware that aria-label may be disregarded by assistive technologies in situations where aria-labelledby is used for the same object. For more information on the naming hierarchy please consult the ARIA specification and the accessible name and description calculation in the HTML to Platform Accessibility APIs Implementation Guide. Authors should be aware that use of aria-label will override any native naming such as alt on images or label associated with a form field using the for attribute.

    +

    Authors should be aware that aria-label may be disregarded by assistive technologies in situations where aria-labelledby is used for the same object. For more information on the naming hierarchy please consult the accessible name and description computation section of the Accessible Name And Description Computation recommendation. Authors should be aware that use of aria-label will override any native naming such as alt on images or label associated with a form field using the for attribute.

    Examples

    Distinguishing navigation landmarks

    -

    The following example shows how aria-label could be used to distinguish two navigation landmarks in a HTML4 and XHTML 1.0 document, where there are more than two of the same type of landmark on the same page, and there is no existing text on the page that can be referenced as the label.

    +

    The following example shows how aria-label could be used to distinguish two navigation landmarks in an HTML document, where there are more than two of the same type of landmark on the same page, and there is no existing text on the page that can be referenced as the label.

    <div role="navigation" aria-label="Primary">
       <ul>
         <li>...a list of links here ...</li>
    
    From 77dfb51ec43f57e2f9ef3bd4d43115c7b9e414fc Mon Sep 17 00:00:00 2001
    From: Hidde de Vries <160571138+hidde-logius@users.noreply.github.com>
    Date: Thu, 25 Jul 2024 02:31:01 +0200
    Subject: [PATCH 20/25] Replace broken link in G56 (#3702)
    
    The page this links no longer exists.
    
    ---------
    
    Co-authored-by: Patrick H. Lauke 
    ---
     techniques/general/G56.html | 8 ++++----
     1 file changed, 4 insertions(+), 4 deletions(-)
    
    diff --git a/techniques/general/G56.html b/techniques/general/G56.html
    index 9a3603138b..8373f2cece 100644
    --- a/techniques/general/G56.html
    +++ b/techniques/general/G56.html
    @@ -78,12 +78,12 @@ 

    Visual example of the failure

  • #5 is true
  • -

    Resources

    - +

    Resources

    + - +
    From 9ce41e5a48ed40df6e956264be86320b1623dff9 Mon Sep 17 00:00:00 2001 From: Andrew Kirkpatrick Date: Mon, 29 Jul 2024 12:08:37 -0400 Subject: [PATCH 21/25] Updating example in On Input Understanding within note (#2984) Updating in response to discussion on #2982 --- understanding/20/on-input.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/understanding/20/on-input.html b/understanding/20/on-input.html index 53df875028..66223c97b6 100644 --- a/understanding/20/on-input.html +++ b/understanding/20/on-input.html @@ -37,7 +37,7 @@

    Intent of On Input

    This Success Criterion covers changes in context due to changing the setting of a - control. Clicking on links or tabs in a tab control is activating the control, not + control. Clicking on links or buttons is activating a control, not changing the setting of that control.

    From e1ffee1f2756ea2ef8e14d02dbec4e8e24e0b365 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Mon, 29 Jul 2024 13:00:21 -0400 Subject: [PATCH 22/25] Use git modified time for footer date in publishes (#3959) This change configures Eleventy to populate its `page.date` value from git modified time when building with the `WCAG_MODE` environment variable set to any truthy value (which is the case when using the Eleventy build system both for gh-pages and for the WAI site). --- 11ty/README.md | 3 +++ _includes/wai-site-footer.html | 2 +- eleventy.config.ts | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/11ty/README.md b/11ty/README.md index 048dab7cb4..cc5df18287 100644 --- a/11ty/README.md +++ b/11ty/README.md @@ -54,6 +54,9 @@ Influences base URLs for links to guidelines, techniques, and understanding page Influences base URLs for links to guidelines, techniques, and understanding pages. Typically set by specific npm scripts or CI processes. +Note that setting `WCAG_MODE` to any non-empty value (even one not listed below) will also result +in page footers referencing last modified times based on git, rather than the local filesystem. + Possible values: - Unset **(default)** - Sets base URLs appropriate for local testing diff --git a/_includes/wai-site-footer.html b/_includes/wai-site-footer.html index 8c24e359a1..fe7fabd0f1 100644 --- a/_includes/wai-site-footer.html +++ b/_includes/wai-site-footer.html @@ -1,6 +1,6 @@
  • +
  • + + Ensuring that + Web pages are well-formed + + +
  • +