From a0b0850b5000c420d8d584a403cd732d4325c3f7 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 17 Jul 2024 16:29:49 -0400 Subject: [PATCH 1/9] Initial commit with fixed translation files and templates --- web/modules/weather_i18n/translations/en.po | 11 ++++++++--- .../block--weathergov-current-conditions.html.twig | 2 +- .../templates/partials/gov-banner.html.twig | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/web/modules/weather_i18n/translations/en.po b/web/modules/weather_i18n/translations/en.po index 95723c6be..9eecc1f4e 100644 --- a/web/modules/weather_i18n/translations/en.po +++ b/web/modules/weather_i18n/translations/en.po @@ -62,6 +62,7 @@ msgstr "" #: uswds-banner.gov.description msgid "Official websites use .gov" +msgstr "" #: uswds-banner.gov.description_html msgid "A .gov\n website belongs to an official government organization in the\n United States." @@ -84,7 +85,7 @@ msgid "Locked padlock icon" msgstr "" #: uswds-banner.gov.safely-connected_html -msgid "or https://\n means youve safely connected to the .gov website. Share\n sensitive information only on official, secure websites." +msgid "or https://\n means you've safely connected to the .gov website. Share\n sensitive information only on official, secure websites." msgstr "" #: hourly-table.aria.scroll-left @@ -262,7 +263,11 @@ msgid "Weather as of" msgstr "" #: forecast.current.aria.feels-like -msgid "It feels like @feelsLike ." +msgid "It feels like @feelsLike ℉." +msgstr "" + +#: forecast.current.weather-temp-description +msgid "The weather in @place, is @conditions.\n Temperature is @temperature ℉." msgstr "" #: forecast.current.feels-like @@ -378,5 +383,5 @@ msgid "SSO Cancel Path" msgstr "" #: backend.login.cancel-text -msgid "Cancel Text +msgid "Cancel Text" msgstr "" diff --git a/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig b/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig index 76f27285d..5d05e5561 100644 --- a/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig +++ b/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig @@ -30,7 +30,7 @@ }) }} {% if content.feels_like != content.temperature %} - {{ " It feels like @feelsLike ℉." | + {{ "It feels like @feelsLike ℉." | t({ "@feelsLike": content.feels_like }) diff --git a/web/themes/new_weather_theme/templates/partials/gov-banner.html.twig b/web/themes/new_weather_theme/templates/partials/gov-banner.html.twig index 58ee83f41..b925a998a 100644 --- a/web/themes/new_weather_theme/templates/partials/gov-banner.html.twig +++ b/web/themes/new_weather_theme/templates/partials/gov-banner.html.twig @@ -89,7 +89,7 @@ ) {{ "or https:// - means you’ve safely connected to the .gov website. Share + means you've safely connected to the .gov website. Share sensitive information only on official, secure websites." | t @@ -100,4 +100,4 @@ - \ No newline at end of file + From 8b30b38443305be3a3e66ec4444e0a96c47b6f48 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 17 Jul 2024 16:30:21 -0400 Subject: [PATCH 2/9] Initial commit of script files --- gettextExtraction.js | 91 ++++++++++++++++++++++++++++++ main.js | 43 +++++++++++++++ translationExtraction.js | 116 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 gettextExtraction.js create mode 100644 main.js create mode 100644 translationExtraction.js diff --git a/gettextExtraction.js b/gettextExtraction.js new file mode 100644 index 000000000..020273d62 --- /dev/null +++ b/gettextExtraction.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const path = require('path'); +const { globSync } = require('glob'); + +const MSG_ID_RX = /msgid\s+\"(.+)\"/m; +const MSG_STR_RX = /msgstr\s+\"(.*)\"/m; + +/** + * Parse out the individual blocks of + * relevant gettext entries from some + * source string. + * Here a 'block' is any series of contiguous + * lines of text that are not just an empty string/newline. + */ +const parseGettextBlocks = str => { + // We ignore the first block, which is just + // the gettext header information + return str.split("\n\n").slice(1); +}; + +const parseGettextSource = str => { + const results = []; + const blocks = parseGettextBlocks(str); + + blocks.forEach(block => { + const comments = block.split("\n").filter(line => { + return line.startsWith("#"); + }); + let msgidString, msgstrString, msgid, msgstr = null; + + const msgidMatch = block.match(MSG_ID_RX); + if(msgidMatch){ + msgidString = msgidMatch[0]; + msgid = msgidMatch[1].replace(/\\n/g, "\n"); + } + + const msgstrMatch = block.match(MSG_STR_RX); + if(msgstrMatch){ + msgstrString = msgstrMatch[0]; + msgstr = msgstrMatch[1]; + } + + if(!msgstrMatch || !msgidMatch){ + throw new Error(`Parse Error: Missing id or str pattern in block: ${block}`); + } + + results.push({ + comments, + msgid, + msgstr, + msgidString, + msgstrString + }); + }); + + return results; +}; + +/** + * Get all the individual paths for the translation + * files relative to some source directory. + */ +const getTranslationPaths = sourceDir => { + const globPattern = path.resolve(sourceDir, "**/*.po"); + return globSync(globPattern); +}; + +/** + * For a given list of translation file paths, + * respond with a dictionary mapping filenames + * to match information for the gettext values + */ +const getTranslationMatchInfo = sourcePath => { + const translationPaths = getTranslationPaths(path.resolve(__dirname, sourcePath)); + const lookup = {}; + + translationPaths.forEach(filePath => { + const languageCode = path.basename(filePath).split(".")[0]; + const langLookup = {}; + const source = fs.readFileSync(filePath).toString(); + const parsed = parseGettextSource(source); + parsed.forEach(entry => langLookup[entry.msgid] = entry); + lookup[languageCode] = langLookup; + }); + + return lookup; +}; + +module.exports = { + getTranslationMatchInfo +}; diff --git a/main.js b/main.js new file mode 100644 index 000000000..8434d9d69 --- /dev/null +++ b/main.js @@ -0,0 +1,43 @@ +const { getTemplateMatchInfo } = require("./translationExtraction"); +const { getTranslationMatchInfo } = require("./gettextExtraction"); + +const reportUsage = () => { + const output = `Usage: ${__filename.split("/").pop()} [PATH_TO_TEMPLATE_DIR] [PATH_TO_TRANSLATIONS_DIR]`; + console.log(output); +} + +// Parse out translation tags/filters from the templates +// and get an initial lookup dictionary about the matches. +const TEMPLATE_PATH = process.argv[2]; +const TRANS_PATH = process.argv[3]; +if(!TEMPLATE_PATH || !TRANS_PATH){ + reportUsage(); + process.exit(-1); +} + +const templateLookup = getTemplateMatchInfo(TEMPLATE_PATH); +const translationLookup = getTranslationMatchInfo(TRANS_PATH); +const languages = Object.keys(translationLookup); + +languages.forEach(langCode => { + // First get any translations defined in templates + // that are missing from the translation file for this + // language + const translations = translationLookup[langCode]; + const translationTerms = Object.keys(translations); + const templateTerms = Object.keys(templateLookup); + const missing = Object.keys(templateLookup).filter(key => { + return !translationTerms.includes(key); + }); + + if(missing.length){ + console.log(`Missing [${missing.length}] translations in the ${langCode} translations file:`); + missing.forEach(key => { + const entry = templateLookup[key]; + if(entry){ + console.log(JSON.stringify(key)); + console.log(entry); + } + }); + } +}); diff --git a/translationExtraction.js b/translationExtraction.js new file mode 100644 index 000000000..da8f9219f --- /dev/null +++ b/translationExtraction.js @@ -0,0 +1,116 @@ +const fs = require('fs'); +const path = require('path'); +const { globSync } = require('glob'); + + +const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; +const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}\n/sg; +const PHP_T_FUNCTION_RX = /-\>t\(.*\)/sg; + +/** + * Get all the individual paths for template files + * in our custom theme + */ +const getTemplatePaths = sourceDir => { + const globPattern = path.resolve(sourceDir, "**/*.twig"); + return globSync(globPattern); +}; + +/** + * For a given template file, extract all of the + * translation matches and return information about + * the match line number and string + */ +const extractTemplateTranslations = filePath => { + const source = fs.readFileSync(filePath).toString(); + let result = []; + const functionMatches = source.matchAll(TWIG_T_FUNCTION_RX); + if(functionMatches){ + result = result.concat(Array.from( + functionMatches, + match => { + return { + filename: path.basename(filePath), + matchedString: match[0], + extracted: match[1], + extractedArgs: match[3] | null, + lineNumber: getLineNumberForPosition(source, match.index) + }; + })); + } + const filterMatches = source.matchAll(TWIG_T_FILTER_RX); + if(filterMatches){ + result = result.concat(Array.from( + filterMatches, + match => { + return { + filename: path.basename(filePath), + matchedString: match[0], + extracted: match[1], + extractedArgs: match[3] || null, + lineNumber: getLineNumberForPosition(source, match.index) + }; + })); + } + + return result; +}; + +/** + * For a given source string and an index into that + * string, determine which line number of the source + * the position appears at. + */ +const getLineNumberForPosition = (source, position) => { + let cursor = 0; + const lines = source.split("\n"); + for(let i = 0; i < lines.length; i++){ + const currentLine = lines[i]; + cursor += currentLine.length + 1; // Add the newline char + if(position <= cursor){ + return i + 1; // Editors use index-1 for line counting + } + } + + return -1; +}; + +/** + * Appends the value to the lookup dictionary's + * key. Because keys map to arrays, if there is not + * yet an entry for the key, it creates the initial array + * value and sets the passed-in value as the first element. + */ +const appendToLookup = (lookup, key, val) => { + if(!Object.keys(lookup).includes(key)){ + lookup[key] = [val]; + } else { + lookup[key].push(val); + } +}; + +/** + * Given a source path of templates, return a lookup + * dictionary that maps string to be translated to + * arrays of match information. + */ +const getTemplateMatchInfo = dirPath => { + const lookupByTerm = {}; + const sourcesPath = path.resolve(__dirname, dirPath); + const templates = getTemplatePaths(sourcesPath); + + templates.forEach(filePath => { + const parsed = extractTemplateTranslations(filePath); + if(parsed.length){ + parsed.forEach(translateMatch => { + appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); + }); + } + }); + + return lookupByTerm; +}; + +module.exports = { + getTemplateMatchInfo +}; From 085a78a1f283b7dadece1427864488a3c32c2444 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 17 Jul 2024 17:37:46 -0400 Subject: [PATCH 3/9] Saving progress --Note Not picking up nearly all of the instances across templates. This is probably because of the regex(es) I have made. Need to revisit. Test case is the points page, which is not being picked up at all --- main.js | 54 ++++++++++++--- translationExtraction.js | 67 +++++++++++++++++-- ...k--weathergov-current-conditions.html.twig | 10 ++- 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/main.js b/main.js index 8434d9d69..199163bad 100644 --- a/main.js +++ b/main.js @@ -1,43 +1,75 @@ -const { getTemplateMatchInfo } = require("./translationExtraction"); +const { getFileMatchInfo, getPHPPaths } = require("./translationExtraction"); const { getTranslationMatchInfo } = require("./gettextExtraction"); const reportUsage = () => { - const output = `Usage: ${__filename.split("/").pop()} [PATH_TO_TEMPLATE_DIR] [PATH_TO_TRANSLATIONS_DIR]`; + const output = `Usage: ${__filename.split("/").pop()} [PATH_TO_TEMPLATE_DIR] [PATH_TO_PHP_FILES_DIR] [PATH_TO_TRANSLATIONS_DIR]`; console.log(output); } // Parse out translation tags/filters from the templates // and get an initial lookup dictionary about the matches. const TEMPLATE_PATH = process.argv[2]; -const TRANS_PATH = process.argv[3]; -if(!TEMPLATE_PATH || !TRANS_PATH){ +const PHP_PATH = process.argv[3]; +const TRANS_PATH = process.argv[4]; +if(!TEMPLATE_PATH || !TRANS_PATH || !PHP_PATH){ reportUsage(); process.exit(-1); } -const templateLookup = getTemplateMatchInfo(TEMPLATE_PATH); +const templateLookup = getFileMatchInfo(TEMPLATE_PATH, PHP_PATH); const translationLookup = getTranslationMatchInfo(TRANS_PATH); const languages = Object.keys(translationLookup); +let errorsSummary = []; languages.forEach(langCode => { + console.log(`Checking translation integrity for: ${langCode}`); // First get any translations defined in templates // that are missing from the translation file for this // language const translations = translationLookup[langCode]; const translationTerms = Object.keys(translations); const templateTerms = Object.keys(templateLookup); - const missing = Object.keys(templateLookup).filter(key => { + + let fileNames = new Set(); + templateTerms.forEach(key => { + templateLookup[key].forEach(phrase => { + fileNames.add(phrase.filename); + }); + }); + + console.log(Array.from(fileNames).sort()); + + console.log("Checking for missing translations"); + const missing = templateTerms.filter(key => { return !translationTerms.includes(key); }); if(missing.length){ - console.log(`Missing [${missing.length}] translations in the ${langCode} translations file:`); + const errString = `Missing [${missing.length}] translations in the ${langCode} translations file`; + errorsSummary.push(errString); + console.error(errString + ":"); missing.forEach(key => { - const entry = templateLookup[key]; - if(entry){ - console.log(JSON.stringify(key)); - console.log(entry); + const entryList = templateLookup[key]; + if(entryList){ + const fileLocations = entryList.map(entry => { + return `${entry.filename}:${entry.lineNumber}`; + }); + const serialized = JSON.stringify(entryList, null, 2); + console.error(`${fileLocations.join("\n")}\n${serialized}`); } }); } + + console.log("Checking for stale translations"); + const stale = translationTerms.filter(key => { + return !templateTerms.includes(key); + }); + + if(stale.length){ + console.error(`Found ${stale.length} stale translations in the ${langCode} file`); + } + + console.log( + templateTerms.find(term => term.startsWith("There")) + ); }); diff --git a/translationExtraction.js b/translationExtraction.js index da8f9219f..52db6c368 100644 --- a/translationExtraction.js +++ b/translationExtraction.js @@ -4,8 +4,9 @@ const { globSync } = require('glob'); const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; -const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}\n/sg; -const PHP_T_FUNCTION_RX = /-\>t\(.*\)/sg; +const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}\n/mg; +const ALT_RX = /\{\{\s*["'].*['"]\s*\|\s*t\s*\}\}/mg; +const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; /** * Get all the individual paths for template files @@ -16,6 +17,48 @@ const getTemplatePaths = sourceDir => { return globSync(globPattern); }; +/** + * Get all the individual paths for PHP code files + * recursively under the provided source directory. + * Note that we check both the php and module extensions + * for drupal compatibility + */ +const getPHPPaths = sourceDir => { + const phpPattern = path.resolve(sourceDir, "**/*.php"); + const modulePattern = path.resolve(sourceDir, "**/*.php"); + const phpPaths = globSync(phpPattern); + const modulePaths = globSync(modulePattern); + return phpPaths.concat(modulePaths); +}; + +/** + * For a given PHP file, extract all of the translation matches + * and return information about the match line number + * and matched / extracted strings + */ +const extractPHPTranslations = filePath => { + const source = fs.readFileSync(filePath).toString(); + let result = []; + + const matches = source.matchAll(PHP_T_FUNCTION_RX); + if(matches){ + result = result.concat(Array.from( + matches, + match => { + return { + filename: path.basename(filePath), + matchedString: match[0], + extracted: match[1], + extractedArgs: match[3] | null, + lineNumber: getLineNumberForPosition(source, match.index) + }; + } + )); + } + + return result; +}; + /** * For a given template file, extract all of the * translation matches and return information about @@ -94,10 +137,12 @@ const appendToLookup = (lookup, key, val) => { * dictionary that maps string to be translated to * arrays of match information. */ -const getTemplateMatchInfo = dirPath => { +const getFileMatchInfo = (templatePath, phpPath) => { const lookupByTerm = {}; - const sourcesPath = path.resolve(__dirname, dirPath); - const templates = getTemplatePaths(sourcesPath); + const templateSourcesPath = path.resolve(__dirname, templatePath); + const phpSourcesPath = path.resolve(__dirname, phpPath); + const templates = getTemplatePaths(templateSourcesPath); + const php = getPHPPaths(phpPath); templates.forEach(filePath => { const parsed = extractTemplateTranslations(filePath); @@ -108,9 +153,19 @@ const getTemplateMatchInfo = dirPath => { } }); + php.forEach(filePath => { + const parsed = extractPHPTranslations(filePath); + if(parsed.length){ + parsed.forEach(translateMatch => { + appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); + }); + } + }); + return lookupByTerm; }; module.exports = { - getTemplateMatchInfo + getFileMatchInfo, + getPHPPaths }; diff --git a/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig b/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig index 5d05e5561..36e01b4d1 100644 --- a/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig +++ b/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig @@ -38,6 +38,8 @@ {% endif %} + {{ "Heya boo boo" | t }} + {# We hide from screenreaders and use the weather narrative (above) instead #} + {{ "Heya boo boo" | t }} +
{% if content.icon.icon %}
From cc38db51e1affc00e8b29d03aacd64c7661d9b79 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Thu, 18 Jul 2024 09:41:31 -0400 Subject: [PATCH 4/9] Adding separate package, tests, and moving files --- .../translations/gettextExtraction.js | 0 main.js => tests/translations/main.js | 0 tests/translations/package-lock.json | 1391 +++++++++++++++++ tests/translations/package.json | 18 + .../tests/fixtures/t.filter.basic.twig | 6 + .../tests/fixtures/t.filter.html.twig | 14 + .../translations/tests/twig-t-filter.spec.js | 79 + tests/translations/translationExtraction.js | 172 ++ translationExtraction.js | 7 +- 9 files changed, 1684 insertions(+), 3 deletions(-) rename gettextExtraction.js => tests/translations/gettextExtraction.js (100%) rename main.js => tests/translations/main.js (100%) create mode 100644 tests/translations/package-lock.json create mode 100644 tests/translations/package.json create mode 100644 tests/translations/tests/fixtures/t.filter.basic.twig create mode 100644 tests/translations/tests/fixtures/t.filter.html.twig create mode 100644 tests/translations/tests/twig-t-filter.spec.js create mode 100644 tests/translations/translationExtraction.js diff --git a/gettextExtraction.js b/tests/translations/gettextExtraction.js similarity index 100% rename from gettextExtraction.js rename to tests/translations/gettextExtraction.js diff --git a/main.js b/tests/translations/main.js similarity index 100% rename from main.js rename to tests/translations/main.js diff --git a/tests/translations/package-lock.json b/tests/translations/package-lock.json new file mode 100644 index 000000000..9a23c2265 --- /dev/null +++ b/tests/translations/package-lock.json @@ -0,0 +1,1391 @@ +{ + "name": "wx-i18n-tests", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wx-i18n-tests", + "version": "0.0.1", + "license": "CC0-1.0", + "dependencies": { + "glob": "^11.0.0" + }, + "devDependencies": { + "chai": "^5.1.1", + "mocha": "^10.6.0" + } + }, + "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/@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/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/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==", + "dev": true + }, + "node_modules/cliui/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/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==", + "dev": true, + "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/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/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/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/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==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.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/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/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "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==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "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==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "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==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", + "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "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/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "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/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-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true + }, + "node_modules/yargs/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tests/translations/package.json b/tests/translations/package.json new file mode 100644 index 000000000..1cc248ee6 --- /dev/null +++ b/tests/translations/package.json @@ -0,0 +1,18 @@ +{ + "name": "wx-i18n-tests", + "version": "0.0.1", + "description": "Consistency tests for i18n on beta.weather.gov", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "CC0-1.0", + "dependencies": { + "glob": "^11.0.0" + }, + "devDependencies": { + "chai": "^5.1.1", + "mocha": "^10.6.0" + } +} diff --git a/tests/translations/tests/fixtures/t.filter.basic.twig b/tests/translations/tests/fixtures/t.filter.basic.twig new file mode 100644 index 000000000..7a60bdde9 --- /dev/null +++ b/tests/translations/tests/fixtures/t.filter.basic.twig @@ -0,0 +1,6 @@ +Some beginning text +{{ "With double quotes" | t }} +{{ 'With single quotes' | t }} +{{ "Is potentially a multi +line string" | t }} +Some other text \ No newline at end of file diff --git a/tests/translations/tests/fixtures/t.filter.html.twig b/tests/translations/tests/fixtures/t.filter.html.twig new file mode 100644 index 000000000..7f25772fd --- /dev/null +++ b/tests/translations/tests/fixtures/t.filter.html.twig @@ -0,0 +1,14 @@ + {% if period.probabilityOfPrecipitation and period.probabilityOfPrecipitation > 1 %} +

+ {{period.probabilityOfPrecipitation}}% {{ "chance of precipitation" | t }} +

+ {% endif %} + +
+
{{ "Feels like" | t }}
+
+

+ {{ content.feels_like }}°F +

+
+
diff --git a/tests/translations/tests/twig-t-filter.spec.js b/tests/translations/tests/twig-t-filter.spec.js new file mode 100644 index 000000000..8bca6ded4 --- /dev/null +++ b/tests/translations/tests/twig-t-filter.spec.js @@ -0,0 +1,79 @@ +const {TWIG_T_FILTER_RX} = require("../translationExtraction.js"); +const fs = require("fs"); +const path = require("path"); +let assert, expect, should; +import("chai").then((module) => { + assert = module.assert; + expect = module.expect; + should = module.should; +}); + +describe("Twig t filter regex tests", () => { + describe("Basic cases", () => { + let source; + before(() => { + source = fs.readFileSync(path.resolve(__dirname, "fixtures", "t.filter.basic.twig")).toString(); + }); + + it("Matches the correct number of translations", () => { + const matches = Array.from( + source.matchAll(TWIG_T_FILTER_RX) + ); + + expect(matches).to.have.lengthOf(3); + }); + + it("Matches the double quotes example", () => { + const matches = Array.from(source.matchAll(TWIG_T_FILTER_RX)); + + expect(matches[0][1]).to.equal("With double quotes"); + }); + + it("Matches the single quotes example", () => { + const matches = Array.from(source.matchAll(TWIG_T_FILTER_RX)); + + expect(matches[1][1]).to.equal("With single quotes"); + }); + + it("Matches the multiline string", () => { + const matches = Array.from(source.matchAll(TWIG_T_FILTER_RX)); + + expect(matches[2][1]).to.equal("Is potentially a multi\nline string"); + }); + }); + + describe("Embedded HTML cases", () => { + let source; + before(() => { + source = fs.readFileSync(path.resolve(__dirname, "fixtures", "t.filter.html.twig")).toString(); + }); + + it("Has the correct number of matches", () => { + const matches = Array.from( + source.matchAll(TWIG_T_FILTER_RX) + ); + + expect(matches).to.have.lengthOf(2); + }); + + it("Matches the first example", () => { + const matches = Array.from( + source.matchAll(TWIG_T_FILTER_RX) + ); + const expected = "chance of precipitation"; + const actual = matches[0][1]; + + expect(actual).to.equal(expected); + }); + + it("MAtches the second example", () => { + const matches = Array.from( + source.matchAll(TWIG_T_FILTER_RX) + ); + const expected = "Feels like"; + const actual = matches[1][1]; + + expect(actual).to.equal(expected); + }); + }); +}); diff --git a/tests/translations/translationExtraction.js b/tests/translations/translationExtraction.js new file mode 100644 index 000000000..b4c923680 --- /dev/null +++ b/tests/translations/translationExtraction.js @@ -0,0 +1,172 @@ +const fs = require('fs'); +const path = require('path'); +const { globSync } = require('glob'); + + +const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; +const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; +//const TWIG_T_FILTER_RX = /\{\{\s*["'](.*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/mg; +const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; + +/** + * Get all the individual paths for template files + * in our custom theme + */ +const getTemplatePaths = sourceDir => { + const globPattern = path.resolve(sourceDir, "**/*.twig"); + return globSync(globPattern); +}; + +/** + * Get all the individual paths for PHP code files + * recursively under the provided source directory. + * Note that we check both the php and module extensions + * for drupal compatibility + */ +const getPHPPaths = sourceDir => { + const phpPattern = path.resolve(sourceDir, "**/*.php"); + const modulePattern = path.resolve(sourceDir, "**/*.php"); + const phpPaths = globSync(phpPattern); + const modulePaths = globSync(modulePattern); + return phpPaths.concat(modulePaths); +}; + +/** + * For a given PHP file, extract all of the translation matches + * and return information about the match line number + * and matched / extracted strings + */ +const extractPHPTranslations = filePath => { + const source = fs.readFileSync(filePath).toString(); + let result = []; + + const matches = source.matchAll(PHP_T_FUNCTION_RX); + if(matches){ + result = result.concat(Array.from( + matches, + match => { + return { + filename: path.basename(filePath), + matchedString: match[0], + extracted: match[1], + extractedArgs: match[3] | null, + lineNumber: getLineNumberForPosition(source, match.index) + }; + } + )); + } + + return result; +}; + +/** + * For a given template file, extract all of the + * translation matches and return information about + * the match line number and string + */ +const extractTemplateTranslations = filePath => { + const source = fs.readFileSync(filePath).toString(); + let result = []; + const functionMatches = source.matchAll(TWIG_T_FUNCTION_RX); + if(functionMatches){ + result = result.concat(Array.from( + functionMatches, + match => { + return { + filename: path.basename(filePath), + matchedString: match[0], + extracted: match[1], + extractedArgs: match[3] | null, + lineNumber: getLineNumberForPosition(source, match.index) + }; + })); + } + const filterMatches = source.matchAll(TWIG_T_FILTER_RX); + if(filterMatches){ + result = result.concat(Array.from( + filterMatches, + match => { + return { + filename: path.basename(filePath), + matchedString: match[0], + extracted: match[1], + extractedArgs: match[3] || null, + lineNumber: getLineNumberForPosition(source, match.index) + }; + })); + } + + return result; +}; + +/** + * For a given source string and an index into that + * string, determine which line number of the source + * the position appears at. + */ +const getLineNumberForPosition = (source, position) => { + let cursor = 0; + const lines = source.split("\n"); + for(let i = 0; i < lines.length; i++){ + const currentLine = lines[i]; + cursor += currentLine.length + 1; // Add the newline char + if(position <= cursor){ + return i + 1; // Editors use index-1 for line counting + } + } + + return -1; +}; + +/** + * Appends the value to the lookup dictionary's + * key. Because keys map to arrays, if there is not + * yet an entry for the key, it creates the initial array + * value and sets the passed-in value as the first element. + */ +const appendToLookup = (lookup, key, val) => { + if(!Object.keys(lookup).includes(key)){ + lookup[key] = [val]; + } else { + lookup[key].push(val); + } +}; + +/** + * Given a source path of templates, return a lookup + * dictionary that maps string to be translated to + * arrays of match information. + */ +const getFileMatchInfo = (templatePath, phpPath) => { + const lookupByTerm = {}; + const templateSourcesPath = path.resolve(__dirname, templatePath); + const phpSourcesPath = path.resolve(__dirname, phpPath); + const templates = getTemplatePaths(templateSourcesPath); + const php = getPHPPaths(phpPath); + + templates.forEach(filePath => { + const parsed = extractTemplateTranslations(filePath); + if(parsed.length){ + parsed.forEach(translateMatch => { + appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); + }); + } + }); + + php.forEach(filePath => { + const parsed = extractPHPTranslations(filePath); + if(parsed.length){ + parsed.forEach(translateMatch => { + appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); + }); + } + }); + + return lookupByTerm; +}; + +module.exports = { + getFileMatchInfo, + getPHPPaths, + TWIG_T_FILTER_RX +}; diff --git a/translationExtraction.js b/translationExtraction.js index 52db6c368..b4c923680 100644 --- a/translationExtraction.js +++ b/translationExtraction.js @@ -4,8 +4,8 @@ const { globSync } = require('glob'); const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; -const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}\n/mg; -const ALT_RX = /\{\{\s*["'].*['"]\s*\|\s*t\s*\}\}/mg; +const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; +//const TWIG_T_FILTER_RX = /\{\{\s*["'](.*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/mg; const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; /** @@ -167,5 +167,6 @@ const getFileMatchInfo = (templatePath, phpPath) => { module.exports = { getFileMatchInfo, - getPHPPaths + getPHPPaths, + TWIG_T_FILTER_RX }; From 23c758e16080041c11ecb0bfeed1f7c0d7be1445 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Thu, 18 Jul 2024 09:42:07 -0400 Subject: [PATCH 5/9] Finalizing JS check script Initial php side structure (attempt) Updating JS tests for translations Removing PHP side attempt code Moving package dependencies and updating Makefile --- Makefile | 5 + package-lock.json | 583 ++++++- package.json | 7 +- phpunit.xml | 1 + tests/translations/config.js | 41 + tests/translations/gettextExtraction.js | 14 +- tests/translations/main.js | 53 +- tests/translations/package-lock.json | 1391 ----------------- tests/translations/package.json | 18 - .../tests/fixtures/t.variable.twig | 10 + .../translations/tests/twig-t-filter.spec.js | 59 +- tests/translations/translationExtraction.js | 84 +- translationExtraction.js | 172 -- web/modules/weather_i18n/translations/en.po | 2 +- ...k--weathergov-current-conditions.html.twig | 4 - .../partials/alerts-in-hourly-table.html.twig | 4 +- .../templates/partials/hourly-table.html.twig | 2 +- .../templates/partials/wind.html.twig | 2 +- 18 files changed, 749 insertions(+), 1703 deletions(-) create mode 100644 tests/translations/config.js delete mode 100644 tests/translations/package-lock.json delete mode 100644 tests/translations/package.json create mode 100644 tests/translations/tests/fixtures/t.variable.twig delete mode 100644 translationExtraction.js diff --git a/Makefile b/Makefile index 74c87cb4f..d9e734e94 100644 --- a/Makefile +++ b/Makefile @@ -148,6 +148,11 @@ php-format: ## Format your PHP code according to the Drupal PHP language standar style-format: ## Format your Sass code according to our style code. npm run style-format +### Other checks +ct: check-translations +check-translations: ## Check the consistency of translations + npm run check-translations + ### Composer management ci: composer-install composer-install: ## Installs dependencies from lock file diff --git a/package-lock.json b/package-lock.json index f83f8e1b9..116d38a67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "@prettier/plugin-php": "^0.22.2", "mariadb": "^3.3.1", - "shapefile": "^0.6.6" + "shapefile": "^0.6.6", + "twing": "^7.0.0-beta.3" }, "devDependencies": { "@axe-core/playwright": "^4.9.1", @@ -28,6 +29,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-cypress": "^3.3.0", "eslint-plugin-import": "^2.29.1", + "glob": "^11.0.0", "jsdom": "^24.1.1", "jsdom-global": "^3.0.2", "mocha": "^10.7.0", @@ -406,6 +408,96 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "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==", + "dev": true, + "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/@isaacs/cliui/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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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==", + "dev": true, + "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/@isaacs/cliui/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==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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==", + "dev": true, + "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/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -441,6 +533,16 @@ "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==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { "version": "1.45.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz", @@ -1201,6 +1303,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/capitalize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/capitalize/-/capitalize-1.0.0.tgz", + "integrity": "sha512-ZvPF27zRh4ZiRbWYfSktO+t7xi4RPfqL9w6gfPAWGT5pT9TjB0rlP8cKHmKWHYYCR8QHKDDebX3HVHcfw6K3GQ==" + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1320,6 +1427,15 @@ "node": ">=8" } }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -1383,6 +1499,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1487,6 +1611,18 @@ } } }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1810,6 +1946,17 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -1894,6 +2041,12 @@ "node": ">=6.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1908,8 +2061,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -2586,6 +2738,14 @@ "node": ">=4.0" } }, + "node_modules/esrever": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/esrever/-/esrever-0.2.0.tgz", + "integrity": "sha512-1e9YJt6yQkyekt2BUjTky7LZWWVyC2cIpgdnsTAvMcnzXIZvlW/fTMPkxBcZoYhgih4d+EC+iw+yv9GIkz7vrw==", + "bin": { + "esrever": "bin/esrever" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -2863,6 +3023,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/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==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3045,20 +3233,23 @@ } }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3086,15 +3277,18 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/global-dirs": { @@ -3327,6 +3521,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3372,6 +3579,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/htmlspecialchars": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/htmlspecialchars/-/htmlspecialchars-1.0.5.tgz", + "integrity": "sha512-gRSdRUTthlrkxtvTz3s98ly/OCU9guJTDm0EtQ5pMhmGYew5x/zwcDrt9KvEu+jy9bXNppAmzLzUTmCSJytNKQ==" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3509,8 +3721,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -4029,6 +4240,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -4049,6 +4268,24 @@ "set-function-name": "^2.0.1" } }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4286,6 +4523,14 @@ "node": "> 0.8" } }, + "node_modules/levenshtein": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/levenshtein/-/levenshtein-1.0.5.tgz", + "integrity": "sha512-UQf1nnmxjl7O0+snDXj2YF2r74Gkya8ZpnegrUBYN9tikh2dtxV/ey8e07BO5wwo0i76yjOvbDhFHdcPEiH9aA==", + "engines": [ + "node >=0.2.0" + ] + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4352,11 +4597,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/locutus": { + "version": "2.0.32", + "resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.32.tgz", + "integrity": "sha512-fr7OCpbE4xeefhHqfh6hM2/l9ZB3XvClHgtgFnQNImrM/nqL950o6FO98vmUH8GysfQRCcyBYtZ4C8GcY52Edw==", + "engines": { + "node": ">= 10", + "yarn": ">= 1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.get": { "version": "4.4.2", @@ -4469,6 +4722,11 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, "node_modules/lru-cache": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", @@ -4477,6 +4735,14 @@ "node": "14 || >=16.14" } }, + "node_modules/luxon": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "engines": { + "node": "*" + } + }, "node_modules/mariadb": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.3.1.tgz", @@ -4502,6 +4768,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -4599,6 +4875,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mocha": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", @@ -4737,6 +5022,26 @@ "balanced-match": "^1.0.0" } }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -4798,6 +5103,14 @@ "path-to-regexp": "^6.2.1" } }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5064,6 +5377,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "node_modules/pad": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pad/-/pad-2.3.0.tgz", + "integrity": "sha512-lxrgnOG5AXmzMRT1O5urWtYFxHnFSE+QntgTHij1nvS4W+ubhQLmQRHmZXDeEvk9I00itAixLqU9Q6fE0gW3sw==", + "dependencies": { + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5139,6 +5469,31 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-source": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/path-source/-/path-source-0.1.3.tgz", @@ -5530,6 +5885,19 @@ "dev": true, "peer": true }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5564,6 +5932,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -5707,6 +6080,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", @@ -5736,6 +6118,14 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runes": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/runes/-/runes-0.4.3.tgz", + "integrity": "sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -5767,7 +6157,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -5870,6 +6259,18 @@ "node": ">= 0.4" } }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shapefile": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/shapefile/-/shapefile-0.6.6.tgz", @@ -5995,6 +6396,22 @@ "resolved": "https://registry.npmjs.org/slice-source/-/slice-source-0.4.1.tgz", "integrity": "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==" }, + "node_modules/snake-case": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==", + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -6047,6 +6464,14 @@ "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", "integrity": "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6061,6 +6486,27 @@ "node": ">=8" } }, + "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==", + "dev": true, + "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/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==", + "dev": true + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6166,6 +6612,19 @@ "node": ">=8" } }, + "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==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6682,6 +7141,49 @@ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, + "node_modules/twig-lexer": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/twig-lexer/-/twig-lexer-0.9.2.tgz", + "integrity": "sha512-jqW70ktyWR1alB7q7n6urBWewuF1iXwm2A9FMq0fFwBp+Gn9a1ORWyZ7AyQYqf33xNNl+pAbgNebWbnBtxDtXQ==" + }, + "node_modules/twing": { + "version": "7.0.0-beta.3", + "resolved": "https://registry.npmjs.org/twing/-/twing-7.0.0-beta.3.tgz", + "integrity": "sha512-X3E7rxfWZ7qkr7Wt31B618S8djcVubQwZOPb3T9AcZR4h+8QyJItRaE2mh49Tc9tipiIkCQLmsN9hK01GLkI2A==", + "dependencies": { + "capitalize": "^1.0.0", + "create-hash": "^1.2.0", + "esrever": "^0.2.0", + "htmlspecialchars": "^1.0.5", + "iconv-lite": "^0.6.3", + "is-plain-object": "^2.0.4", + "isobject": "^3.0.1", + "levenshtein": "^1.0.5", + "locutus": "^2.0.31", + "luxon": "^1.19.3", + "pad": "^2.0.3", + "regex-parser": "^2.2.8", + "runes": "^0.4.3", + "snake-case": "^2.1.0", + "source-map": "^0.6.1", + "twig-lexer": "^0.9.0", + "utf8-binary-cutter": "^0.9.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/twing/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6845,11 +7347,18 @@ "requires-port": "^1.0.0" } }, + "node_modules/utf8-binary-cutter": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/utf8-binary-cutter/-/utf8-binary-cutter-0.9.2.tgz", + "integrity": "sha512-lS/2TaA9idsyafus4+WaB+C/AfL3JD85C/sgMJBpplZay1G5SwTQcxmd4jiJLI1VxSJr6a3yuNicBxD+iU2MKQ==", + "dependencies": { + "lodash": "^4.17.10" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { "version": "8.3.2", @@ -6886,6 +7395,14 @@ "node": ">=18" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -7063,6 +7580,24 @@ "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==", + "dev": true, + "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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 4ce4f6e59..9cfb10565 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-cypress": "^3.3.0", "eslint-plugin-import": "^2.29.1", "jsdom": "^24.1.1", + "glob": "^11.0.0", "jsdom-global": "^3.0.2", "mocha": "^10.7.0", "mocha-cli": "^1.0.1", @@ -61,11 +62,13 @@ "php-format": "npx prettier -w 'web/**/*.php' 'web/**/*.php.test' 'web/**/*.theme' 'web/**/*.module'", "style-format": "npx prettier -w 'web/themes/**/*.scss'", "style-lint": "stylelint '**/*.scss'", - "compile-svg": "node ./scripts/compile-svg-sprite.js ./web/themes/new_weather_theme/assets/images/weather/icons/*.svg ./web/themes/new_weather_theme/assets/images/weather/icons/conditions/*.svg" + "compile-svg": "node ./scripts/compile-svg-sprite.js ./web/themes/new_weather_theme/assets/images/weather/icons/*.svg ./web/themes/new_weather_theme/assets/images/weather/icons/conditions/*.svg", + "check-translations": "node ./tests/translations/main.js" }, "dependencies": { "@prettier/plugin-php": "^0.22.2", "mariadb": "^3.3.1", - "shapefile": "^0.6.6" + "shapefile": "^0.6.6", + "twing": "^7.0.0-beta.3" } } diff --git a/phpunit.xml b/phpunit.xml index e2b783992..6cfb3bfe9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,6 +6,7 @@ web/themes/weathergov_admin web/modules/weather_blocks web/modules/weather_data + web/modules/weather_i18n { return results; }; -/** - * Get all the individual paths for the translation - * files relative to some source directory. - */ -const getTranslationPaths = sourceDir => { - const globPattern = path.resolve(sourceDir, "**/*.po"); - return globSync(globPattern); -}; - /** * For a given list of translation file paths, * respond with a dictionary mapping filenames * to match information for the gettext values */ -const getTranslationMatchInfo = sourcePath => { - const translationPaths = getTranslationPaths(path.resolve(__dirname, sourcePath)); +const getTranslationMatchInfo = sourcePaths => { const lookup = {}; - translationPaths.forEach(filePath => { + sourcePaths.forEach(filePath => { const languageCode = path.basename(filePath).split(".")[0]; const langLookup = {}; const source = fs.readFileSync(filePath).toString(); diff --git a/tests/translations/main.js b/tests/translations/main.js index 199163bad..a09595191 100644 --- a/tests/translations/main.js +++ b/tests/translations/main.js @@ -1,23 +1,34 @@ -const { getFileMatchInfo, getPHPPaths } = require("./translationExtraction"); +const { globSync } = require("glob"); +const path = require("path"); +const { getFileMatchInfo } = require("./translationExtraction"); const { getTranslationMatchInfo } = require("./gettextExtraction"); +const config = require("./config.js"); -const reportUsage = () => { - const output = `Usage: ${__filename.split("/").pop()} [PATH_TO_TEMPLATE_DIR] [PATH_TO_PHP_FILES_DIR] [PATH_TO_TRANSLATIONS_DIR]`; - console.log(output); -} -// Parse out translation tags/filters from the templates -// and get an initial lookup dictionary about the matches. -const TEMPLATE_PATH = process.argv[2]; -const PHP_PATH = process.argv[3]; -const TRANS_PATH = process.argv[4]; -if(!TEMPLATE_PATH || !TRANS_PATH || !PHP_PATH){ - reportUsage(); - process.exit(-1); -} +/** + * Get all of the template and php paths as flat arrays + */ +const templatePaths = config.templates.include.reduce((prev, current) => { + const relativeGlob = path.resolve(__dirname, current); + const filePaths = globSync(relativeGlob); + return prev.concat(filePaths); +},[]).filter(filePath => { + const fileName = path.basename(filePath); + return !config.templates.exclude.includes(fileName); +}); +const phpPaths = config.php.include.reduce((prev, current) => { + const relativeGlob = path.resolve(__dirname, current); + const filePaths = globSync(relativeGlob); + return prev.concat(filePaths); +}, []); +const translationPaths = config.translations.include.reduce((prev, current) => { + const relativeGlob = path.resolve(__dirname, current); + const filePaths = globSync(relativeGlob); + return prev.concat(filePaths); +}, []); -const templateLookup = getFileMatchInfo(TEMPLATE_PATH, PHP_PATH); -const translationLookup = getTranslationMatchInfo(TRANS_PATH); +const templateLookup = getFileMatchInfo(templatePaths, phpPaths); +const translationLookup = getTranslationMatchInfo(translationPaths); const languages = Object.keys(translationLookup); let errorsSummary = []; @@ -37,8 +48,6 @@ languages.forEach(langCode => { }); }); - console.log(Array.from(fileNames).sort()); - console.log("Checking for missing translations"); const missing = templateTerms.filter(key => { return !translationTerms.includes(key); @@ -58,6 +67,7 @@ languages.forEach(langCode => { console.error(`${fileLocations.join("\n")}\n${serialized}`); } }); + process.exit(-1); } console.log("Checking for stale translations"); @@ -66,10 +76,7 @@ languages.forEach(langCode => { }); if(stale.length){ - console.error(`Found ${stale.length} stale translations in the ${langCode} file`); + console.warn(`Found ${stale.length} potentially stale translations in the ${langCode} file`); + console.log(stale); } - - console.log( - templateTerms.find(term => term.startsWith("There")) - ); }); diff --git a/tests/translations/package-lock.json b/tests/translations/package-lock.json deleted file mode 100644 index 9a23c2265..000000000 --- a/tests/translations/package-lock.json +++ /dev/null @@ -1,1391 +0,0 @@ -{ - "name": "wx-i18n-tests", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "wx-i18n-tests", - "version": "0.0.1", - "license": "CC0-1.0", - "dependencies": { - "glob": "^11.0.0" - }, - "devDependencies": { - "chai": "^5.1.1", - "mocha": "^10.6.0" - } - }, - "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/@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/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "dev": true - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "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/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", - "dev": true, - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "engines": { - "node": ">= 16" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "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/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/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==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/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==", - "dev": true - }, - "node_modules/cliui/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==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/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==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/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==", - "dev": true, - "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/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/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/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/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==", - "dev": true - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.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/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/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "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==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "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==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "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==", - "dev": true - }, - "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==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mocha": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", - "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dev": true - }, - "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", - "dev": true, - "engines": { - "node": ">= 14.16" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "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/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "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/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-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "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/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true - }, - "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==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs/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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/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==", - "dev": true - }, - "node_modules/yargs/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==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/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==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/tests/translations/package.json b/tests/translations/package.json deleted file mode 100644 index 1cc248ee6..000000000 --- a/tests/translations/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "wx-i18n-tests", - "version": "0.0.1", - "description": "Consistency tests for i18n on beta.weather.gov", - "main": "main.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "CC0-1.0", - "dependencies": { - "glob": "^11.0.0" - }, - "devDependencies": { - "chai": "^5.1.1", - "mocha": "^10.6.0" - } -} diff --git a/tests/translations/tests/fixtures/t.variable.twig b/tests/translations/tests/fixtures/t.variable.twig new file mode 100644 index 000000000..3da9b8e4d --- /dev/null +++ b/tests/translations/tests/fixtures/t.variable.twig @@ -0,0 +1,10 @@ +{% if content.error %} + {% set message = "There was an error loading the current conditions." | t %} + {% include '@new_weather_theme/partials/uswds-alert.html.twig' with { 'level': "error", body: message } %} +{% endif %} +{% if period.isOvernight %} + {% set label = "Overnight" | t %} + {% set lineColor = "midnight-indigo" %} + {% set tabletCol = 12 %} + {% set tabletMarginUnit = 3 %} +{% endif %} diff --git a/tests/translations/tests/twig-t-filter.spec.js b/tests/translations/tests/twig-t-filter.spec.js index 8bca6ded4..0a0a04b07 100644 --- a/tests/translations/tests/twig-t-filter.spec.js +++ b/tests/translations/tests/twig-t-filter.spec.js @@ -1,4 +1,4 @@ -const {TWIG_T_FILTER_RX} = require("../translationExtraction.js"); +const { matchTranslationFilters } = require("../translationExtraction.js"); const fs = require("fs"); const path = require("path"); let assert, expect, should; @@ -8,7 +8,7 @@ import("chai").then((module) => { should = module.should; }); -describe("Twig t filter regex tests", () => { +describe("Twig translation regex tests", () => { describe("Basic cases", () => { let source; before(() => { @@ -16,27 +16,25 @@ describe("Twig t filter regex tests", () => { }); it("Matches the correct number of translations", () => { - const matches = Array.from( - source.matchAll(TWIG_T_FILTER_RX) - ); + const matches = matchTranslationFilters(source); expect(matches).to.have.lengthOf(3); }); it("Matches the double quotes example", () => { - const matches = Array.from(source.matchAll(TWIG_T_FILTER_RX)); + const matches = matchTranslationFilters(source); expect(matches[0][1]).to.equal("With double quotes"); }); it("Matches the single quotes example", () => { - const matches = Array.from(source.matchAll(TWIG_T_FILTER_RX)); + const matches = matchTranslationFilters(source); expect(matches[1][1]).to.equal("With single quotes"); }); it("Matches the multiline string", () => { - const matches = Array.from(source.matchAll(TWIG_T_FILTER_RX)); + const matches = matchTranslationFilters(source); expect(matches[2][1]).to.equal("Is potentially a multi\nline string"); }); @@ -49,31 +47,56 @@ describe("Twig t filter regex tests", () => { }); it("Has the correct number of matches", () => { - const matches = Array.from( - source.matchAll(TWIG_T_FILTER_RX) - ); + const matches = matchTranslationFilters(source); expect(matches).to.have.lengthOf(2); }); it("Matches the first example", () => { - const matches = Array.from( - source.matchAll(TWIG_T_FILTER_RX) - ); + const matches = matchTranslationFilters(source); const expected = "chance of precipitation"; const actual = matches[0][1]; + console.log(actual); + expect(actual).to.equal(expected); }); - it("MAtches the second example", () => { - const matches = Array.from( - source.matchAll(TWIG_T_FILTER_RX) - ); + it("Matches the second example", () => { + const matches = matchTranslationFilters(source); const expected = "Feels like"; const actual = matches[1][1]; expect(actual).to.equal(expected); }); }); + + describe("Variable setting cases", () => { + let source; + before(() => { + source = fs.readFileSync(path.resolve(__dirname, "fixtures", "t.variable.twig")).toString(); + }); + + it("Has the correct number of matches", () => { + const matches = matchTranslationFilters(source); + + expect(matches).to.have.lengthOf(2); + }); + + it("Matches the first example", () => { + const matches = matchTranslationFilters(source); + const expected = "There was an error loading the current conditions."; + const actual = matches[0][1]; + + expect(actual).to.equal(expected); + }); + + it("Matches the second example", () => { + const matches = matchTranslationFilters(source); + const expected = "Overnight"; + const actual = matches[1][1]; + + expect(actual).to.equal(expected); + }); + }); }); diff --git a/tests/translations/translationExtraction.js b/tests/translations/translationExtraction.js index b4c923680..acb7b12df 100644 --- a/tests/translations/translationExtraction.js +++ b/tests/translations/translationExtraction.js @@ -4,31 +4,45 @@ const { globSync } = require('glob'); const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; -const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; -//const TWIG_T_FILTER_RX = /\{\{\s*["'](.*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/mg; +const TWIG_T_FILTER_SINGLE_RX = /\{\{\s*[']([^']+)[']\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; +const TWIG_T_FILTER_DOUBLE_RX = /\{\{\s*["]([^"]+)["]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; +const TWIG_T_VARIABLE_SET_RX = /\{\%\s*set\s*[A-Za-z_0-9]+\s*\=\s*["]([^"]+)["]\s*\|\s*t\s*\%\}/sg; +const TWIG_T_DICT_SET_RX = /\:\s*['"]([^'"]+)['"]\s*\|\s*t/sg; const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; /** - * Get all the individual paths for template files - * in our custom theme + * Given a source string, provide an array + * of matches that match either the single + * or double quoted variant of the T_FILTER + * regex, and other variants */ -const getTemplatePaths = sourceDir => { - const globPattern = path.resolve(sourceDir, "**/*.twig"); - return globSync(globPattern); -}; +const matchTranslationFilters = source => { + let result = []; + const doubleQuoted = source.matchAll(TWIG_T_FILTER_DOUBLE_RX); + if(doubleQuoted){ + result = result.concat(Array.from(doubleQuoted)); + } + const singleQuoted = source.matchAll(TWIG_T_FILTER_SINGLE_RX); + if(singleQuoted){ + result = result.concat(Array.from(singleQuoted)); + } + const variableBased = source.matchAll(TWIG_T_VARIABLE_SET_RX); + if(variableBased){ + result = result.concat(Array.from(variableBased)); + } -/** - * Get all the individual paths for PHP code files - * recursively under the provided source directory. - * Note that we check both the php and module extensions - * for drupal compatibility - */ -const getPHPPaths = sourceDir => { - const phpPattern = path.resolve(sourceDir, "**/*.php"); - const modulePattern = path.resolve(sourceDir, "**/*.php"); - const phpPaths = globSync(phpPattern); - const modulePaths = globSync(modulePattern); - return phpPaths.concat(modulePaths); + const dictBased = source.matchAll(TWIG_T_DICT_SET_RX); + if(dictBased){ + result = result.concat(Array.from(dictBased)); + } + + return result.sort((a, b) => { + if(a.index < b.index){ + return -1; + } else { + return 0; + } + }); }; /** @@ -77,12 +91,13 @@ const extractTemplateTranslations = filePath => { matchedString: match[0], extracted: match[1], extractedArgs: match[3] | null, - lineNumber: getLineNumberForPosition(source, match.index) + lineNumber: getLineNumberForPosition(source, match.index), + index: match.index }; })); } - const filterMatches = source.matchAll(TWIG_T_FILTER_RX); - if(filterMatches){ + const filterMatches = matchTranslationFilters(source); + if(filterMatches.length){ result = result.concat(Array.from( filterMatches, match => { @@ -91,12 +106,18 @@ const extractTemplateTranslations = filePath => { matchedString: match[0], extracted: match[1], extractedArgs: match[3] || null, - lineNumber: getLineNumberForPosition(source, match.index) + lineNumber: getLineNumberForPosition(source, match.index), + index: match.index }; })); } - return result; + return result.sort((a, b) => { + if(a < b){ + return -1; + } + return 0; + }); }; /** @@ -137,14 +158,10 @@ const appendToLookup = (lookup, key, val) => { * dictionary that maps string to be translated to * arrays of match information. */ -const getFileMatchInfo = (templatePath, phpPath) => { +const getFileMatchInfo = (templatePaths, phpPaths) => { const lookupByTerm = {}; - const templateSourcesPath = path.resolve(__dirname, templatePath); - const phpSourcesPath = path.resolve(__dirname, phpPath); - const templates = getTemplatePaths(templateSourcesPath); - const php = getPHPPaths(phpPath); - templates.forEach(filePath => { + templatePaths.forEach(filePath => { const parsed = extractTemplateTranslations(filePath); if(parsed.length){ parsed.forEach(translateMatch => { @@ -153,7 +170,7 @@ const getFileMatchInfo = (templatePath, phpPath) => { } }); - php.forEach(filePath => { + phpPaths.forEach(filePath => { const parsed = extractPHPTranslations(filePath); if(parsed.length){ parsed.forEach(translateMatch => { @@ -167,6 +184,5 @@ const getFileMatchInfo = (templatePath, phpPath) => { module.exports = { getFileMatchInfo, - getPHPPaths, - TWIG_T_FILTER_RX + matchTranslationFilters }; diff --git a/translationExtraction.js b/translationExtraction.js deleted file mode 100644 index b4c923680..000000000 --- a/translationExtraction.js +++ /dev/null @@ -1,172 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const { globSync } = require('glob'); - - -const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; -const TWIG_T_FILTER_RX = /\{\{\s*['"]([^'"]*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; -//const TWIG_T_FILTER_RX = /\{\{\s*["'](.*)['"]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/mg; -const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; - -/** - * Get all the individual paths for template files - * in our custom theme - */ -const getTemplatePaths = sourceDir => { - const globPattern = path.resolve(sourceDir, "**/*.twig"); - return globSync(globPattern); -}; - -/** - * Get all the individual paths for PHP code files - * recursively under the provided source directory. - * Note that we check both the php and module extensions - * for drupal compatibility - */ -const getPHPPaths = sourceDir => { - const phpPattern = path.resolve(sourceDir, "**/*.php"); - const modulePattern = path.resolve(sourceDir, "**/*.php"); - const phpPaths = globSync(phpPattern); - const modulePaths = globSync(modulePattern); - return phpPaths.concat(modulePaths); -}; - -/** - * For a given PHP file, extract all of the translation matches - * and return information about the match line number - * and matched / extracted strings - */ -const extractPHPTranslations = filePath => { - const source = fs.readFileSync(filePath).toString(); - let result = []; - - const matches = source.matchAll(PHP_T_FUNCTION_RX); - if(matches){ - result = result.concat(Array.from( - matches, - match => { - return { - filename: path.basename(filePath), - matchedString: match[0], - extracted: match[1], - extractedArgs: match[3] | null, - lineNumber: getLineNumberForPosition(source, match.index) - }; - } - )); - } - - return result; -}; - -/** - * For a given template file, extract all of the - * translation matches and return information about - * the match line number and string - */ -const extractTemplateTranslations = filePath => { - const source = fs.readFileSync(filePath).toString(); - let result = []; - const functionMatches = source.matchAll(TWIG_T_FUNCTION_RX); - if(functionMatches){ - result = result.concat(Array.from( - functionMatches, - match => { - return { - filename: path.basename(filePath), - matchedString: match[0], - extracted: match[1], - extractedArgs: match[3] | null, - lineNumber: getLineNumberForPosition(source, match.index) - }; - })); - } - const filterMatches = source.matchAll(TWIG_T_FILTER_RX); - if(filterMatches){ - result = result.concat(Array.from( - filterMatches, - match => { - return { - filename: path.basename(filePath), - matchedString: match[0], - extracted: match[1], - extractedArgs: match[3] || null, - lineNumber: getLineNumberForPosition(source, match.index) - }; - })); - } - - return result; -}; - -/** - * For a given source string and an index into that - * string, determine which line number of the source - * the position appears at. - */ -const getLineNumberForPosition = (source, position) => { - let cursor = 0; - const lines = source.split("\n"); - for(let i = 0; i < lines.length; i++){ - const currentLine = lines[i]; - cursor += currentLine.length + 1; // Add the newline char - if(position <= cursor){ - return i + 1; // Editors use index-1 for line counting - } - } - - return -1; -}; - -/** - * Appends the value to the lookup dictionary's - * key. Because keys map to arrays, if there is not - * yet an entry for the key, it creates the initial array - * value and sets the passed-in value as the first element. - */ -const appendToLookup = (lookup, key, val) => { - if(!Object.keys(lookup).includes(key)){ - lookup[key] = [val]; - } else { - lookup[key].push(val); - } -}; - -/** - * Given a source path of templates, return a lookup - * dictionary that maps string to be translated to - * arrays of match information. - */ -const getFileMatchInfo = (templatePath, phpPath) => { - const lookupByTerm = {}; - const templateSourcesPath = path.resolve(__dirname, templatePath); - const phpSourcesPath = path.resolve(__dirname, phpPath); - const templates = getTemplatePaths(templateSourcesPath); - const php = getPHPPaths(phpPath); - - templates.forEach(filePath => { - const parsed = extractTemplateTranslations(filePath); - if(parsed.length){ - parsed.forEach(translateMatch => { - appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); - }); - } - }); - - php.forEach(filePath => { - const parsed = extractPHPTranslations(filePath); - if(parsed.length){ - parsed.forEach(translateMatch => { - appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); - }); - } - }); - - return lookupByTerm; -}; - -module.exports = { - getFileMatchInfo, - getPHPPaths, - TWIG_T_FILTER_RX -}; diff --git a/web/modules/weather_i18n/translations/en.po b/web/modules/weather_i18n/translations/en.po index 9eecc1f4e..212f7ae77 100644 --- a/web/modules/weather_i18n/translations/en.po +++ b/web/modules/weather_i18n/translations/en.po @@ -57,7 +57,7 @@ msgid "An official website of the United States government" msgstr "" #: uswds-banner.how-you-know -msgid "Heres how you know" +msgid "Here’s how you know" msgstr "" #: uswds-banner.gov.description diff --git a/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig b/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig index 36e01b4d1..a0b1073c9 100644 --- a/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig +++ b/web/themes/new_weather_theme/templates/block/block--weathergov-current-conditions.html.twig @@ -38,8 +38,6 @@ {% endif %}
- {{ "Heya boo boo" | t }} - {# We hide from screenreaders and use the weather narrative (above) instead #}
- - {{ "Heya boo boo" | t }}
{% if content.icon.icon %} diff --git a/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig b/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig index 28d8229bf..a027f0fed 100644 --- a/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig +++ b/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig @@ -10,8 +10,8 @@ announced when testing using VO. #} {% if loop.first %} - {% if alertPeriods | length > 1 %} {% set plural="s" %} {% endif %} - + {% if alertPeriods | length > 1 %} {% set alertLabel="Alerts" | t %}{% else %}{% set alertLabel="Alert" | t }{% endif %} + {% endif %} {{ "Alert" | t }} diff --git a/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig b/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig index 9b9e2aa1e..8f2859b42 100644 --- a/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig +++ b/web/themes/new_weather_theme/templates/partials/hourly-table.html.twig @@ -122,7 +122,7 @@ } %} {% if period.windGust is not same as (null) %} -
{{ "gusting to @gustSpeed mph " | t({ "@gustSpeed": period.windGust }) }}
+
{{ "gusting to @gustSpeed mph" | t({ "@gustSpeed": period.windGust }) }}
{% endif %} {% endfor %} diff --git a/web/themes/new_weather_theme/templates/partials/wind.html.twig b/web/themes/new_weather_theme/templates/partials/wind.html.twig index f060c3726..1b568b57b 100644 --- a/web/themes/new_weather_theme/templates/partials/wind.html.twig +++ b/web/themes/new_weather_theme/templates/partials/wind.html.twig @@ -19,7 +19,7 @@ By putting all of the content into a single span, we can get VoiceOver to read it in a more natural way. #} - {{ "@speed mph from the @direction " | t({"@speed":wind.speed,"@direction": wind.direction.long }) }} + {{ "@speed mph from the @direction" | t({"@speed":wind.speed,"@direction": wind.direction.long }) }} {% else %} {{ "N/A" | t }} {% endif %} From fa969967c7524469596b6f07007d57c5052de7ad Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 24 Jul 2024 14:19:20 -0400 Subject: [PATCH 6/9] Fixing exclusion filtering on translations and php code files --- tests/translations/main.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/translations/main.js b/tests/translations/main.js index a09595191..b40aa7518 100644 --- a/tests/translations/main.js +++ b/tests/translations/main.js @@ -20,12 +20,18 @@ const phpPaths = config.php.include.reduce((prev, current) => { const relativeGlob = path.resolve(__dirname, current); const filePaths = globSync(relativeGlob); return prev.concat(filePaths); -}, []); +}, []).filter(filePath => { + const fileName = path.basename(filePath); + return !config.php.exclude.includes(fileName); +}); const translationPaths = config.translations.include.reduce((prev, current) => { const relativeGlob = path.resolve(__dirname, current); const filePaths = globSync(relativeGlob); return prev.concat(filePaths); -}, []); +}, []).filter(filePath => { + const fileName = path.basename(filePath); + return !config.translations.exclude.includes(fileName); +}); const templateLookup = getFileMatchInfo(templatePaths, phpPaths); const translationLookup = getTranslationMatchInfo(translationPaths); From 51cdf4ba7d4bd10a8bfc94e06460c27050b4e7d6 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 24 Jul 2024 14:20:31 -0400 Subject: [PATCH 7/9] formatting --- tests/translations/config.js | 36 +++---- tests/translations/gettextExtraction.js | 39 ++++--- tests/translations/main.js | 79 +++++++------- .../translations/tests/twig-t-filter.spec.js | 14 ++- tests/translations/translationExtraction.js | 101 +++++++++--------- 5 files changed, 143 insertions(+), 126 deletions(-) diff --git a/tests/translations/config.js b/tests/translations/config.js index 7beaad869..ece0d0308 100644 --- a/tests/translations/config.js +++ b/tests/translations/config.js @@ -6,20 +6,18 @@ module.exports = { templates: { - include: [ - "../../web/themes/new_weather_theme/templates/**/*.twig" - ], + include: ["../../web/themes/new_weather_theme/templates/**/*.twig"], exclude: [ - 'views-mini-pager.html.twig', - 'pager.html.twig', - 'menu-local-tasks.html.twig', - 'breadcrumb.html.twig', - 'maintenance-page.html.twig', - 'field--comment.html.twig', - 'filter-tips.html.twig', - 'mark.html.twig', - 'block--local-tasks-block.html.twig' - ] + "views-mini-pager.html.twig", + "pager.html.twig", + "menu-local-tasks.html.twig", + "breadcrumb.html.twig", + "maintenance-page.html.twig", + "field--comment.html.twig", + "filter-tips.html.twig", + "mark.html.twig", + "block--local-tasks-block.html.twig", + ], }, php: { @@ -27,15 +25,13 @@ module.exports = { "../../web/modules/weather_cms/**/*.php", "../../web/modules/weather_cms/**/*.module", "../../web/modules/weather_data/**/*.php", - "../../web/modules/weather_data/**/*.module" + "../../web/modules/weather_data/**/*.module", ], - exclude: [] + exclude: [], }, translations: { - include: [ - "../../web/modules/weather_i18n/translations/*.po" - ], - exclude: [] - } + include: ["../../web/modules/weather_i18n/translations/*.po"], + exclude: [], + }, }; diff --git a/tests/translations/gettextExtraction.js b/tests/translations/gettextExtraction.js index 3eb2ddb18..a375db409 100644 --- a/tests/translations/gettextExtraction.js +++ b/tests/translations/gettextExtraction.js @@ -1,6 +1,6 @@ -const fs = require('fs'); -const path = require('path'); -const { globSync } = require('glob'); +const fs = require("fs"); +const path = require("path"); +const { globSync } = require("glob"); const MSG_ID_RX = /msgid\s+\"(.+)\"/m; const MSG_STR_RX = /msgstr\s+\"(.*)\"/m; @@ -12,36 +12,41 @@ const MSG_STR_RX = /msgstr\s+\"(.*)\"/m; * Here a 'block' is any series of contiguous * lines of text that are not just an empty string/newline. */ -const parseGettextBlocks = str => { +const parseGettextBlocks = (str) => { // We ignore the first block, which is just // the gettext header information return str.split("\n\n").slice(1); }; -const parseGettextSource = str => { +const parseGettextSource = (str) => { const results = []; const blocks = parseGettextBlocks(str); - blocks.forEach(block => { - const comments = block.split("\n").filter(line => { + blocks.forEach((block) => { + const comments = block.split("\n").filter((line) => { return line.startsWith("#"); }); - let msgidString, msgstrString, msgid, msgstr = null; + let msgidString, + msgstrString, + msgid, + msgstr = null; const msgidMatch = block.match(MSG_ID_RX); - if(msgidMatch){ + if (msgidMatch) { msgidString = msgidMatch[0]; msgid = msgidMatch[1].replace(/\\n/g, "\n"); } const msgstrMatch = block.match(MSG_STR_RX); - if(msgstrMatch){ + if (msgstrMatch) { msgstrString = msgstrMatch[0]; msgstr = msgstrMatch[1]; } - if(!msgstrMatch || !msgidMatch){ - throw new Error(`Parse Error: Missing id or str pattern in block: ${block}`); + if (!msgstrMatch || !msgidMatch) { + throw new Error( + `Parse Error: Missing id or str pattern in block: ${block}`, + ); } results.push({ @@ -49,7 +54,7 @@ const parseGettextSource = str => { msgid, msgstr, msgidString, - msgstrString + msgstrString, }); }); @@ -61,15 +66,15 @@ const parseGettextSource = str => { * respond with a dictionary mapping filenames * to match information for the gettext values */ -const getTranslationMatchInfo = sourcePaths => { +const getTranslationMatchInfo = (sourcePaths) => { const lookup = {}; - sourcePaths.forEach(filePath => { + sourcePaths.forEach((filePath) => { const languageCode = path.basename(filePath).split(".")[0]; const langLookup = {}; const source = fs.readFileSync(filePath).toString(); const parsed = parseGettextSource(source); - parsed.forEach(entry => langLookup[entry.msgid] = entry); + parsed.forEach((entry) => (langLookup[entry.msgid] = entry)); lookup[languageCode] = langLookup; }); @@ -77,5 +82,5 @@ const getTranslationMatchInfo = sourcePaths => { }; module.exports = { - getTranslationMatchInfo + getTranslationMatchInfo, }; diff --git a/tests/translations/main.js b/tests/translations/main.js index b40aa7518..01f9ae4ee 100644 --- a/tests/translations/main.js +++ b/tests/translations/main.js @@ -4,41 +4,46 @@ const { getFileMatchInfo } = require("./translationExtraction"); const { getTranslationMatchInfo } = require("./gettextExtraction"); const config = require("./config.js"); - /** * Get all of the template and php paths as flat arrays */ -const templatePaths = config.templates.include.reduce((prev, current) => { - const relativeGlob = path.resolve(__dirname, current); - const filePaths = globSync(relativeGlob); - return prev.concat(filePaths); -},[]).filter(filePath => { - const fileName = path.basename(filePath); - return !config.templates.exclude.includes(fileName); -}); -const phpPaths = config.php.include.reduce((prev, current) => { - const relativeGlob = path.resolve(__dirname, current); - const filePaths = globSync(relativeGlob); - return prev.concat(filePaths); -}, []).filter(filePath => { - const fileName = path.basename(filePath); - return !config.php.exclude.includes(fileName); -}); -const translationPaths = config.translations.include.reduce((prev, current) => { - const relativeGlob = path.resolve(__dirname, current); - const filePaths = globSync(relativeGlob); - return prev.concat(filePaths); -}, []).filter(filePath => { - const fileName = path.basename(filePath); - return !config.translations.exclude.includes(fileName); -}); +const templatePaths = config.templates.include + .reduce((prev, current) => { + const relativeGlob = path.resolve(__dirname, current); + const filePaths = globSync(relativeGlob); + return prev.concat(filePaths); + }, []) + .filter((filePath) => { + const fileName = path.basename(filePath); + return !config.templates.exclude.includes(fileName); + }); +const phpPaths = config.php.include + .reduce((prev, current) => { + const relativeGlob = path.resolve(__dirname, current); + const filePaths = globSync(relativeGlob); + return prev.concat(filePaths); + }, []) + .filter((filePath) => { + const fileName = path.basename(filePath); + return !config.php.exclude.includes(fileName); + }); +const translationPaths = config.translations.include + .reduce((prev, current) => { + const relativeGlob = path.resolve(__dirname, current); + const filePaths = globSync(relativeGlob); + return prev.concat(filePaths); + }, []) + .filter((filePath) => { + const fileName = path.basename(filePath); + return !config.translations.exclude.includes(fileName); + }); const templateLookup = getFileMatchInfo(templatePaths, phpPaths); const translationLookup = getTranslationMatchInfo(translationPaths); const languages = Object.keys(translationLookup); let errorsSummary = []; -languages.forEach(langCode => { +languages.forEach((langCode) => { console.log(`Checking translation integrity for: ${langCode}`); // First get any translations defined in templates // that are missing from the translation file for this @@ -48,25 +53,25 @@ languages.forEach(langCode => { const templateTerms = Object.keys(templateLookup); let fileNames = new Set(); - templateTerms.forEach(key => { - templateLookup[key].forEach(phrase => { + templateTerms.forEach((key) => { + templateLookup[key].forEach((phrase) => { fileNames.add(phrase.filename); }); }); console.log("Checking for missing translations"); - const missing = templateTerms.filter(key => { + const missing = templateTerms.filter((key) => { return !translationTerms.includes(key); }); - if(missing.length){ + if (missing.length) { const errString = `Missing [${missing.length}] translations in the ${langCode} translations file`; errorsSummary.push(errString); console.error(errString + ":"); - missing.forEach(key => { + missing.forEach((key) => { const entryList = templateLookup[key]; - if(entryList){ - const fileLocations = entryList.map(entry => { + if (entryList) { + const fileLocations = entryList.map((entry) => { return `${entry.filename}:${entry.lineNumber}`; }); const serialized = JSON.stringify(entryList, null, 2); @@ -77,12 +82,14 @@ languages.forEach(langCode => { } console.log("Checking for stale translations"); - const stale = translationTerms.filter(key => { + const stale = translationTerms.filter((key) => { return !templateTerms.includes(key); }); - if(stale.length){ - console.warn(`Found ${stale.length} potentially stale translations in the ${langCode} file`); + if (stale.length) { + console.warn( + `Found ${stale.length} potentially stale translations in the ${langCode} file`, + ); console.log(stale); } }); diff --git a/tests/translations/tests/twig-t-filter.spec.js b/tests/translations/tests/twig-t-filter.spec.js index 0a0a04b07..01ce2fea2 100644 --- a/tests/translations/tests/twig-t-filter.spec.js +++ b/tests/translations/tests/twig-t-filter.spec.js @@ -12,7 +12,11 @@ describe("Twig translation regex tests", () => { describe("Basic cases", () => { let source; before(() => { - source = fs.readFileSync(path.resolve(__dirname, "fixtures", "t.filter.basic.twig")).toString(); + source = fs + .readFileSync( + path.resolve(__dirname, "fixtures", "t.filter.basic.twig"), + ) + .toString(); }); it("Matches the correct number of translations", () => { @@ -43,7 +47,9 @@ describe("Twig translation regex tests", () => { describe("Embedded HTML cases", () => { let source; before(() => { - source = fs.readFileSync(path.resolve(__dirname, "fixtures", "t.filter.html.twig")).toString(); + source = fs + .readFileSync(path.resolve(__dirname, "fixtures", "t.filter.html.twig")) + .toString(); }); it("Has the correct number of matches", () => { @@ -74,7 +80,9 @@ describe("Twig translation regex tests", () => { describe("Variable setting cases", () => { let source; before(() => { - source = fs.readFileSync(path.resolve(__dirname, "fixtures", "t.variable.twig")).toString(); + source = fs + .readFileSync(path.resolve(__dirname, "fixtures", "t.variable.twig")) + .toString(); }); it("Has the correct number of matches", () => { diff --git a/tests/translations/translationExtraction.js b/tests/translations/translationExtraction.js index acb7b12df..f5c6a4522 100644 --- a/tests/translations/translationExtraction.js +++ b/tests/translations/translationExtraction.js @@ -1,14 +1,16 @@ -const fs = require('fs'); -const path = require('path'); -const { globSync } = require('glob'); - - -const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/sg; -const TWIG_T_FILTER_SINGLE_RX = /\{\{\s*[']([^']+)[']\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; -const TWIG_T_FILTER_DOUBLE_RX = /\{\{\s*["]([^"]+)["]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/sg; -const TWIG_T_VARIABLE_SET_RX = /\{\%\s*set\s*[A-Za-z_0-9]+\s*\=\s*["]([^"]+)["]\s*\|\s*t\s*\%\}/sg; -const TWIG_T_DICT_SET_RX = /\:\s*['"]([^'"]+)['"]\s*\|\s*t/sg; -const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; +const fs = require("fs"); +const path = require("path"); +const { globSync } = require("glob"); + +const TWIG_T_FUNCTION_RX = /\{\{\s*t\(['"][^'"]*['"].*\)\}\}/gs; +const TWIG_T_FILTER_SINGLE_RX = + /\{\{\s*[']([^']+)[']\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/gs; +const TWIG_T_FILTER_DOUBLE_RX = + /\{\{\s*["]([^"]+)["]\s*\|\s*t(\(\s*(\{[^}]+\})\s*\))?\s*\}\}/gs; +const TWIG_T_VARIABLE_SET_RX = + /\{\%\s*set\s*[A-Za-z_0-9]+\s*\=\s*["]([^"]+)["]\s*\|\s*t\s*\%\}/gs; +const TWIG_T_DICT_SET_RX = /\:\s*['"]([^'"]+)['"]\s*\|\s*t/gs; +const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/gs; /** * Given a source string, provide an array @@ -16,28 +18,28 @@ const PHP_T_FUNCTION_RX = /-\>t\(['"]([^'"]+)['"]\)/sg; * or double quoted variant of the T_FILTER * regex, and other variants */ -const matchTranslationFilters = source => { +const matchTranslationFilters = (source) => { let result = []; const doubleQuoted = source.matchAll(TWIG_T_FILTER_DOUBLE_RX); - if(doubleQuoted){ + if (doubleQuoted) { result = result.concat(Array.from(doubleQuoted)); } const singleQuoted = source.matchAll(TWIG_T_FILTER_SINGLE_RX); - if(singleQuoted){ + if (singleQuoted) { result = result.concat(Array.from(singleQuoted)); } const variableBased = source.matchAll(TWIG_T_VARIABLE_SET_RX); - if(variableBased){ + if (variableBased) { result = result.concat(Array.from(variableBased)); } const dictBased = source.matchAll(TWIG_T_DICT_SET_RX); - if(dictBased){ + if (dictBased) { result = result.concat(Array.from(dictBased)); } return result.sort((a, b) => { - if(a.index < b.index){ + if (a.index < b.index) { return -1; } else { return 0; @@ -50,24 +52,23 @@ const matchTranslationFilters = source => { * and return information about the match line number * and matched / extracted strings */ -const extractPHPTranslations = filePath => { +const extractPHPTranslations = (filePath) => { const source = fs.readFileSync(filePath).toString(); let result = []; const matches = source.matchAll(PHP_T_FUNCTION_RX); - if(matches){ - result = result.concat(Array.from( - matches, - match => { + if (matches) { + result = result.concat( + Array.from(matches, (match) => { return { filename: path.basename(filePath), matchedString: match[0], extracted: match[1], extractedArgs: match[3] | null, - lineNumber: getLineNumberForPosition(source, match.index) + lineNumber: getLineNumberForPosition(source, match.index), }; - } - )); + }), + ); } return result; @@ -78,42 +79,42 @@ const extractPHPTranslations = filePath => { * translation matches and return information about * the match line number and string */ -const extractTemplateTranslations = filePath => { +const extractTemplateTranslations = (filePath) => { const source = fs.readFileSync(filePath).toString(); let result = []; const functionMatches = source.matchAll(TWIG_T_FUNCTION_RX); - if(functionMatches){ - result = result.concat(Array.from( - functionMatches, - match => { + if (functionMatches) { + result = result.concat( + Array.from(functionMatches, (match) => { return { filename: path.basename(filePath), matchedString: match[0], extracted: match[1], extractedArgs: match[3] | null, lineNumber: getLineNumberForPosition(source, match.index), - index: match.index + index: match.index, }; - })); + }), + ); } const filterMatches = matchTranslationFilters(source); - if(filterMatches.length){ - result = result.concat(Array.from( - filterMatches, - match => { + if (filterMatches.length) { + result = result.concat( + Array.from(filterMatches, (match) => { return { filename: path.basename(filePath), matchedString: match[0], extracted: match[1], extractedArgs: match[3] || null, lineNumber: getLineNumberForPosition(source, match.index), - index: match.index + index: match.index, }; - })); + }), + ); } return result.sort((a, b) => { - if(a < b){ + if (a < b) { return -1; } return 0; @@ -128,10 +129,10 @@ const extractTemplateTranslations = filePath => { const getLineNumberForPosition = (source, position) => { let cursor = 0; const lines = source.split("\n"); - for(let i = 0; i < lines.length; i++){ + for (let i = 0; i < lines.length; i++) { const currentLine = lines[i]; cursor += currentLine.length + 1; // Add the newline char - if(position <= cursor){ + if (position <= cursor) { return i + 1; // Editors use index-1 for line counting } } @@ -146,7 +147,7 @@ const getLineNumberForPosition = (source, position) => { * value and sets the passed-in value as the first element. */ const appendToLookup = (lookup, key, val) => { - if(!Object.keys(lookup).includes(key)){ + if (!Object.keys(lookup).includes(key)) { lookup[key] = [val]; } else { lookup[key].push(val); @@ -161,28 +162,28 @@ const appendToLookup = (lookup, key, val) => { const getFileMatchInfo = (templatePaths, phpPaths) => { const lookupByTerm = {}; - templatePaths.forEach(filePath => { + templatePaths.forEach((filePath) => { const parsed = extractTemplateTranslations(filePath); - if(parsed.length){ - parsed.forEach(translateMatch => { + if (parsed.length) { + parsed.forEach((translateMatch) => { appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); }); } }); - phpPaths.forEach(filePath => { + phpPaths.forEach((filePath) => { const parsed = extractPHPTranslations(filePath); - if(parsed.length){ - parsed.forEach(translateMatch => { + if (parsed.length) { + parsed.forEach((translateMatch) => { appendToLookup(lookupByTerm, translateMatch.extracted, translateMatch); }); } }); - + return lookupByTerm; }; module.exports = { getFileMatchInfo, - matchTranslationFilters + matchTranslationFilters, }; From d9a6eb9a4bc07b3378e9a5c0b75199126c5af043 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 24 Jul 2024 14:24:56 -0400 Subject: [PATCH 8/9] Formatting and disabling linting on these files --- .eslintrc.js | 5 ++++- tests/translations/gettextExtraction.js | 18 +++++++-------- tests/translations/main.js | 18 +++++---------- .../translations/tests/twig-t-filter.spec.js | 5 +++-- tests/translations/translationExtraction.js | 22 +++++++------------ 5 files changed, 29 insertions(+), 39 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 44070261b..e43884175 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,9 @@ module.exports = { extends: ["airbnb-base", "prettier"], - ignorePatterns: ["uswds*.js"], + ignorePatterns: [ + "tests/translations/**/*.js", + "uswds*.js" + ], rules: { // For imports in the browser, file extensions are always required. "import/extensions": ["error", "always"], diff --git a/tests/translations/gettextExtraction.js b/tests/translations/gettextExtraction.js index a375db409..f0ec514d9 100644 --- a/tests/translations/gettextExtraction.js +++ b/tests/translations/gettextExtraction.js @@ -12,24 +12,22 @@ const MSG_STR_RX = /msgstr\s+\"(.*)\"/m; * Here a 'block' is any series of contiguous * lines of text that are not just an empty string/newline. */ -const parseGettextBlocks = (str) => { +const parseGettextBlocks = (str) => // We ignore the first block, which is just // the gettext header information - return str.split("\n\n").slice(1); -}; + str.split("\n\n").slice(1) +; const parseGettextSource = (str) => { const results = []; const blocks = parseGettextBlocks(str); blocks.forEach((block) => { - const comments = block.split("\n").filter((line) => { - return line.startsWith("#"); - }); - let msgidString, - msgstrString, - msgid, - msgstr = null; + const comments = block.split("\n").filter((line) => line.startsWith("#")); + let msgidString; + let msgstrString; + let msgid; + let msgstr = null; const msgidMatch = block.match(MSG_ID_RX); if (msgidMatch) { diff --git a/tests/translations/main.js b/tests/translations/main.js index 01f9ae4ee..d11f1ef12 100644 --- a/tests/translations/main.js +++ b/tests/translations/main.js @@ -41,7 +41,7 @@ const translationPaths = config.translations.include const templateLookup = getFileMatchInfo(templatePaths, phpPaths); const translationLookup = getTranslationMatchInfo(translationPaths); const languages = Object.keys(translationLookup); -let errorsSummary = []; +const errorsSummary = []; languages.forEach((langCode) => { console.log(`Checking translation integrity for: ${langCode}`); @@ -52,7 +52,7 @@ languages.forEach((langCode) => { const translationTerms = Object.keys(translations); const templateTerms = Object.keys(templateLookup); - let fileNames = new Set(); + const fileNames = new Set(); templateTerms.forEach((key) => { templateLookup[key].forEach((phrase) => { fileNames.add(phrase.filename); @@ -60,20 +60,16 @@ languages.forEach((langCode) => { }); console.log("Checking for missing translations"); - const missing = templateTerms.filter((key) => { - return !translationTerms.includes(key); - }); + const missing = templateTerms.filter((key) => !translationTerms.includes(key)); if (missing.length) { const errString = `Missing [${missing.length}] translations in the ${langCode} translations file`; errorsSummary.push(errString); - console.error(errString + ":"); + console.error(`${errString }:`); missing.forEach((key) => { const entryList = templateLookup[key]; if (entryList) { - const fileLocations = entryList.map((entry) => { - return `${entry.filename}:${entry.lineNumber}`; - }); + const fileLocations = entryList.map((entry) => `${entry.filename}:${entry.lineNumber}`); const serialized = JSON.stringify(entryList, null, 2); console.error(`${fileLocations.join("\n")}\n${serialized}`); } @@ -82,9 +78,7 @@ languages.forEach((langCode) => { } console.log("Checking for stale translations"); - const stale = translationTerms.filter((key) => { - return !templateTerms.includes(key); - }); + const stale = translationTerms.filter((key) => !templateTerms.includes(key)); if (stale.length) { console.warn( diff --git a/tests/translations/tests/twig-t-filter.spec.js b/tests/translations/tests/twig-t-filter.spec.js index 01ce2fea2..c5a6420ed 100644 --- a/tests/translations/tests/twig-t-filter.spec.js +++ b/tests/translations/tests/twig-t-filter.spec.js @@ -1,7 +1,8 @@ -const { matchTranslationFilters } = require("../translationExtraction.js"); const fs = require("fs"); const path = require("path"); -let assert, expect, should; +const { matchTranslationFilters } = require("../translationExtraction.js"); + +let assert; let expect; let should; import("chai").then((module) => { assert = module.assert; expect = module.expect; diff --git a/tests/translations/translationExtraction.js b/tests/translations/translationExtraction.js index f5c6a4522..18d4136b6 100644 --- a/tests/translations/translationExtraction.js +++ b/tests/translations/translationExtraction.js @@ -41,9 +41,9 @@ const matchTranslationFilters = (source) => { return result.sort((a, b) => { if (a.index < b.index) { return -1; - } else { + } return 0; - } + }); }; @@ -59,15 +59,13 @@ const extractPHPTranslations = (filePath) => { const matches = source.matchAll(PHP_T_FUNCTION_RX); if (matches) { result = result.concat( - Array.from(matches, (match) => { - return { + Array.from(matches, (match) => ({ filename: path.basename(filePath), matchedString: match[0], extracted: match[1], extractedArgs: match[3] | null, lineNumber: getLineNumberForPosition(source, match.index), - }; - }), + })), ); } @@ -85,31 +83,27 @@ const extractTemplateTranslations = (filePath) => { const functionMatches = source.matchAll(TWIG_T_FUNCTION_RX); if (functionMatches) { result = result.concat( - Array.from(functionMatches, (match) => { - return { + Array.from(functionMatches, (match) => ({ filename: path.basename(filePath), matchedString: match[0], extracted: match[1], extractedArgs: match[3] | null, lineNumber: getLineNumberForPosition(source, match.index), index: match.index, - }; - }), + })), ); } const filterMatches = matchTranslationFilters(source); if (filterMatches.length) { result = result.concat( - Array.from(filterMatches, (match) => { - return { + Array.from(filterMatches, (match) => ({ filename: path.basename(filePath), matchedString: match[0], extracted: match[1], extractedArgs: match[3] || null, lineNumber: getLineNumberForPosition(source, match.index), index: match.index, - }; - }), + })), ); } From dc8156b63429eeaee7c52140799c55fd8ec59691 Mon Sep 17 00:00:00 2001 From: eric-gade Date: Wed, 24 Jul 2024 15:45:53 -0400 Subject: [PATCH 9/9] Typographic Error --- .../templates/partials/alerts-in-hourly-table.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig b/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig index a027f0fed..6e937dcfb 100644 --- a/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig +++ b/web/themes/new_weather_theme/templates/partials/alerts-in-hourly-table.html.twig @@ -10,7 +10,7 @@ announced when testing using VO. #} {% if loop.first %} - {% if alertPeriods | length > 1 %} {% set alertLabel="Alerts" | t %}{% else %}{% set alertLabel="Alert" | t }{% endif %} + {% if alertPeriods | length > 1 %} {% set alertLabel="Alerts" | t %}{% else %}{% set alertLabel="Alert" | t %}{% endif %} {% endif %} {{ "Alert" | t }}