Automate L10n extraction locally and in CI (#12800)

* Extract locales automatically in CI

* TMP: minor fixes
This commit is contained in:
Kevin Meinhardt 2024-07-15 19:41:32 +02:00 коммит произвёл GitHub
Родитель 4c517c1b7c
Коммит 4e1d5f6691
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
12 изменённых файлов: 422 добавлений и 315 удалений

114
.github/actions/context/action.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,114 @@
name: 'Dump Context'
description: 'Display context for action run'
outputs:
# All github action outputs are strings, even if set to "true"
# so when using these values always assert against strings or convert from json
# \$\{{ needs.context.outputs.is_fork == 'true' }} // true
# \$\{{ fromJson(needs.context.outputs.is_fork) == false }} // true
# \$\{{ needs.context.outputs.is_fork == true }} // false
# \$\{{ needs.context.outputs.is_fork }} // false
is_fork:
description: ""
value: ${{ steps.context.outputs.is_fork }}
is_default_branch:
description: ""
value: ${{ steps.context.outputs.is_default_branch }}
is_release_master:
description: ""
value: ${{ steps.context.outputs.is_release_master }}
is_release_tag:
description: ""
value: ${{ steps.context.outputs.is_release_tag }}
runs:
using: 'composite'
steps:
- name: Dump GitHub context
shell: bash
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Dump job context
shell: bash
env:
JOB_CONTEXT: ${{ toJson(job) }}
run: echo "$JOB_CONTEXT"
- name: Dump steps context
shell: bash
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
run: echo "$STEPS_CONTEXT"
- name: Dump runner context
shell: bash
env:
RUNNER_CONTEXT: ${{ toJson(runner) }}
run: echo "$RUNNER_CONTEXT"
- name: Dump env context
shell: bash
env:
ENV_CONTEXT: ${{ toJson(env) }}
run: |
echo "$ENV_CONTEXT"
- name: Dump inputs context
shell: bash
env:
INPUTS_CONTEXT: ${{ toJson(inputs) }}
run: |
echo "$INPUTS_CONTEXT"
- name: Set context
id: context
env:
# The default branch of the repository, in this case "master"
default_branch: ${{ github.event.repository.default_branch }}
shell: bash
run: |
event_name="${{ github.event_name }}"
event_action="${{ github.event.action }}"
# Stable check for if the workflow is running on the default branch
# https://stackoverflow.com/questions/64781462/github-actions-default-branch-variable
is_default_branch="${{ format('refs/heads/{0}', env.default_branch) == github.ref }}"
# In most events, the epository refers to the head which would be the fork
is_fork="${{ github.event.repository.fork }}"
# This is different in a pull_request where we need to check the head explicitly
if [[ "${{ github.event_name }}" == 'pull_request' ]]; then
# repository on a pull request refers to the base which is always mozilla/addons-server
is_head_fork="${{ github.event.pull_request.head.repo.fork }}"
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
is_dependabot="${{ github.actor == 'dependabot[bot]' }}"
# If the head repository is a fork or if the PR is opened by dependabot
# we consider the run to be a fork. Dependabot and proper forks are treated
# the same in terms of limited read only github token scope
if [[ "$is_head_fork" == 'true' || "$is_dependabot" == 'true' ]]; then
is_fork="true"
fi
fi
is_release_master="false"
is_release_tag="false"
# Releases can only happen if we are NOT on a fork
if [[ "$is_fork" == 'false' ]]; then
# A master release occurs on a push to the default branch of the origin repository
if [[ "$event_name" == 'push' && "$is_default_branch" == 'true' ]]; then
is_release_master="true"
fi
# A tag release occurs when a release is published
if [[ "$event_name" == 'release' && "$event_action" == 'publish' ]]; then
is_release_tag="true"
fi
fi
echo "is_default_branch=$is_default_branch" >> $GITHUB_OUTPUT
echo "is_fork=$is_fork" >> $GITHUB_OUTPUT
echo "is_release_master=$is_release_master" >> $GITHUB_OUTPUT
echo "is_release_tag=$is_release_tag" >> $GITHUB_OUTPUT
echo "event_name: $event_name"
cat $GITHUB_OUTPUT

68
.github/workflows/ci.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,68 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
context:
runs-on: ubuntu-latest
outputs:
is_fork: ${{ steps.context.outputs.is_fork }}
steps:
- uses: actions/checkout@v4
- id: context
uses: ./.github/actions/context
locales:
needs: context
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'
- name: Install gettext
run: sudo apt-get install gettext
- name: Yarn install
run: yarn install --frozen-lockfile --prefer-offline
- name: Extract locales
run: yarn extract-locales
- name: Push Locales
run: |
event_name="${{ github.event_name }}"
is_fork="${{ needs.context.outputs.is_fork }}"
if [[ "$is_fork" == 'true' ]]; then
cat <<'EOF'
Github actions are not authorized to push from workflows triggered by forks.
We cannot verify if the l10n extraction push will work or not.
Please submit a PR from the base repository if you are modifying l10n extraction scripts.
EOF
exit 0
fi
ARGS=""
if [[ "$event_name" == 'pull_request' ]]; then
ARGS="--dry-run"
fi
./bin/push-locales $ARGS

Просмотреть файл

@ -2,6 +2,7 @@
*.*
# exclude these files
Dockerfile
src/fonts/LICENSE
# exclude these directories
/assets/
/bin/

41
babel.config.locales.js Normal file
Просмотреть файл

@ -0,0 +1,41 @@
// Create UTC creation date in the correct format.
const potCreationDate = new Date()
.toISOString()
.replace('T', ' ')
.replace(/:\d{2}.\d{3}Z/, '+0000');
module.exports = {
extends: './babel.config.js',
plugins: [
[
'module:babel-gettext-extractor',
{
headers: {
'Project-Id-Version': 'amo',
'Report-Msgid-Bugs-To': 'EMAIL@ADDRESS',
'POT-Creation-Date': potCreationDate,
'PO-Revision-Date': 'YEAR-MO-DA HO:MI+ZONE',
'Last-Translator': 'FULL NAME <EMAIL@ADDRESS>',
'Language-Team': 'LANGUAGE <LL@li.org>',
'MIME-Version': '1.0',
'Content-Type': 'text/plain; charset=utf-8',
'Content-Transfer-Encoding': '8bit',
'plural-forms': 'nplurals=2; plural=(n!=1);',
},
functionNames: {
gettext: ['msgid'],
dgettext: ['domain', 'msgid'],
ngettext: ['msgid', 'msgid_plural', 'count'],
dngettext: ['domain', 'msgid', 'msgid_plural', 'count'],
pgettext: ['msgctxt', 'msgid'],
dpgettext: ['domain', 'msgctxt', 'msgid'],
npgettext: ['msgctxt', 'msgid', 'msgid_plural', 'count'],
dnpgettext: ['domain', 'msgctxt', 'msgid', 'msgid_plural', 'count'],
},
fileName: './locale/templates/LC_MESSAGES/amo.pot',
baseDirectory: process.cwd(),
stripTemplateLiteralIndent: true,
},
],
],
};

Просмотреть файл

@ -1,3 +1,59 @@
#!/usr/bin/env sh
#!/usr/bin/env zx
import {$, path, echo, within, glob} from 'zx';
const root = path.join(__dirname, '..');
const localeDir = path.join(root, 'locale');
const templateFile = path.join(localeDir, '/templates/LC_MESSAGES/amo.pot');
within(async () => {
echo('Extracting locales...');
const sourceDir = path.join(root, 'src', 'amo');
const outputDir = path.join(root, 'dist', 'locales');
const localesConfig = path.join(root, 'babel.config.locales.js');
await $`babel ${sourceDir} \
--out-dir ${outputDir} \
--config-file ${localesConfig} \
--verbose \
`;
const {stdout: output} = await $`git diff --numstat -- ${templateFile}`;
// git diff --numstat returns the number of insertions and deletions for each file
// this regex extracts the numbers from the output
const regex = /([0-9]+).*([0-9]+)/;
const [, insertions = 0, deletions = 0] = output.match(regex) || [];
const isLocaleClean = insertions < 2 && deletions < 2;
if (isLocaleClean) {
return echo('No locale changes, nothing to update, ending process');
}
echo(`Found ${insertions} insertions and ${deletions} deletions in ${templateFile}.`);
const poFiles = await glob(`${localeDir}/**/amo.po`);
echo(`Merging ${poFiles.length} translation files.`);
for await (const poFile of poFiles) {
const dir = path.dirname(poFile);
const stem = path.basename(poFile, '.po');
const tempFile = path.join(dir, `${stem}.po.tmp`);
echo(`merging: ${poFile}`);
try {
await $`msgmerge --no-fuzzy-matching -q -o ${tempFile} ${poFile} ${templateFile}`
await $`mv ${tempFile} ${poFile}`
} catch (error) {
await $`rm ${tempFile}`;
throw new Error(`Error merging ${poFile}`);
}
}
return true;
});
yarn extract-locales

Просмотреть файл

@ -1,24 +0,0 @@
#!/usr/bin/env node
/* eslint-disable no-console */
require('@babel/register');
const { globSync } = require('glob');
const path = require('path');
const shell = require('shelljs');
const localeDir = path.join(__dirname, '../locale');
const poFiles = globSync(`${localeDir}/**/amo.po`);
const template =
path.join(localeDir, `templates/LC_MESSAGES/amo.pot`);
poFiles.forEach((poFile) => {
const dir = path.dirname(poFile);
const stem = path.basename(poFile, '.po');
const tempFile = path.join(dir, `${stem}.po.tmp`);
shell.exec(
`msgmerge --no-fuzzy-matching -q -o ${tempFile} ${poFile} ${template}`
);
shell.mv(tempFile, poFile);
});

58
bin/push-locales Executable file
Просмотреть файл

@ -0,0 +1,58 @@
#! /bin/bash
# Exit immediately when a command fails.
set -e
# Make sure exit code are respected in a pipeline.
set -o pipefail
# Treat unset variables as an error an exit immediately.
set -u
info() {
local message="$1"
echo ""
echo "INFO: $message"
echo ""
}
ROBOT_EMAIL="addons-dev-automation+github@mozilla.com"
ROBOT_NAME="Mozilla Add-ons Robot"
# Set git committer/author to the robot.
export GIT_AUTHOR_NAME="$ROBOT_NAME"
export GIT_AUTHOR_EMAIL="$ROBOT_EMAIL"
export GIT_COMMITTER_NAME="$ROBOT_NAME"
export GIT_COMMITTER_EMAIL="$ROBOT_EMAIL"
DATE=$(date -u +%Y-%m-%d)
REV=$(git rev-parse --short HEAD)
MESSAGE="Extracted l10n messages from $DATE at $REV"
DIFF_WITH_ONE_LINE_CHANGE="2 files changed, 2 insertions(+), 2 deletions(-)"
git_diff_stat=$(git diff --shortstat locale/templates/LC_MESSAGES)
info "git_diff_stat: $git_diff_stat"
# IF there are no uncommitted local changes, exit early.
if [[ -z "$git_diff_stat" ]] || [[ "$git_diff_stat" == *"$DIFF_WITH_ONE_LINE_CHANGE"* ]]; then
info """
No substantial changes to l10n strings found. Exiting the process.
"""
exit 0
fi
info """
GIT_AUTHOR_NAME: $GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL: $GIT_AUTHOR_EMAIL
GIT_COMMITTER_NAME: $GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL: $GIT_COMMITTER_EMAIL
This script passes arguments directly to Git commands. We can pass --dry-mode to test this script
Without actually committing or pushing. Make sure to only pass arguments supported on both commit and push..
ARGS: $@
"""
git commit -am "$MESSAGE" "$@"
git push "$@"

Просмотреть файл

@ -1,100 +0,0 @@
#!/usr/bin/env bash
# Exit immediately when a command fails.
set -e
# Make sure exit code are respected in a pipeline.
set -o pipefail
# Treat unset variables as an error an exit immediately.
set -u
INITIAL_GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
GIT_CHANGES=$(git status --porcelain)
DIFF_WITH_ONE_LINE_CHANGE="1 file changed, 1 insertion(+), 1 deletion(-)"
GIT_REMOTE="${GIT_REMOTE:-git@github.com:mozilla/addons-frontend.git}"
LOG_FILE="l10n-extraction.log"
info() {
local message="$1"
echo ""
echo "INFO: $message"
echo ""
}
error() {
local message="$1"
echo "ERROR: $message"
exit 1
}
run_l10n_extraction() {
local branch="amo-locales"
info "Starting l10n extraction"
# Detect local (uncommitted) changes.
if [[ ! -z "$GIT_CHANGES" ]]; then
error "You have local changes, therefore this script cannot continue."
fi
# Switch to the `master` branch if we are not on it already.
if [[ "$INITIAL_GIT_BRANCH" != "master" ]]; then
git checkout master
fi
# Make sure the 'master' branch is up-to-date.
git pull "$GIT_REMOTE" master
# Update dependencies
yarn >> "$LOG_FILE" 2>&1
# Ensure the branch to extract the locales is clean.
if [[ $(git branch --list "$branch") ]]; then
info "Deleting branch '$branch' because it already exists"
git branch -D "$branch"
fi
info "Creating and switching to branch '$branch'"
git checkout -b "$branch"
info "Extracting locales (this can take several minutes)"
bin/extract-locales >> "$LOG_FILE" 2>&1
local git_diff_stat
git_diff_stat=$(git diff --shortstat)
if [[ -z "$git_diff_stat" ]] || [[ "$git_diff_stat" == *"$DIFF_WITH_ONE_LINE_CHANGE"* ]]; then
info "No locale changes, nothing to update, ending process"
git checkout -- .
git checkout "$INITIAL_GIT_BRANCH"
git branch -d "$branch"
return
fi
git commit -a -m "Extract locales" --no-verify
info "Merging locales"
bin/merge-locales >> "$LOG_FILE" 2>&1
git commit -a -m "Merge locales" --no-verify
# We use force-push in case the branch already exists.
git push -f "$GIT_REMOTE" "$branch"
git checkout "$INITIAL_GIT_BRANCH"
git branch -D "$branch"
echo ""
echo "----------------------------------------------------------------------"
echo ""
echo " You can now open the link below to submit your Pull Request:"
echo " https://github.com/mozilla/addons-frontend/pull/new/$branch"
echo ""
echo "----------------------------------------------------------------------"
}
# Clear log file
echo "" > "$LOG_FILE"
run_l10n_extraction

Просмотреть файл

@ -20,94 +20,19 @@ NODE_PATH='./:./src' bin/create-locales
## Updating locales
TL;DR: run the following script from the `master` branch: `./bin/run-l10n-extraction`
Locales are updated automatically as a part of our CI. On every push to master `yarn extract-locales` is run which extracts locale strings from our codebase, merges any changes to the source language files and commits the changes.
### The long story
You can run this command manually on your local environment any time to check the output strings.
Once a week right after the forthcoming release [is tagged](http://addons.readthedocs.io/en/latest/server/push-duty.html), the locales for each app must be generated.
Github actions internally prevent infinite loops by default.
This is a semi-automated process: a team member must create a pull request with the following commits:
1. A commit containing the extraction of newly added strings
2. A commit containing a merge of localizations
Each one of these steps are detailed in the sections below. Let's begin...
#### Extracting newly added strings
Start the process by creating a git branch and extracting the locales.
```
git checkout master
git pull
git checkout -b amo-locales
bin/extract-locales
```
This extracts all strings wrapped with `i18n.gettext()` or any other function supported by [Jed][jed] (the library we use in JavaScript to carry out replacements for the string keys in the current locale).
The strings are extracted using a babel plugin via webpack. Extracted strings are added to a pot template file. This file is used to seed the po for each locale with the strings needing translating when merging locales.
Run `git diff` to see what the extraction did. **If no strings were updated then you do not have to continue creating the pull request. You can revert the changes made to the `pot` timestamp.** Here is an example of a diff where no strings were changed. It just shows a single change to the timestamp:
```diff
diff --git a/locale/templates/LC_MESSAGES/amo.pot b/locale/templates/LC_MESSAGES/amo.pot
index 31e113f2..c7da4e34 100644
--- a/locale/templates/LC_MESSAGES/amo.pot
+++ b/locale/templates/LC_MESSAGES/amo.pot
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: amo\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2017-06-08 14:01+0000\n"
+"POT-Creation-Date: 2017-06-08 14:43+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
```
When the application is under active development it's more likely that you will see a diff containing new strings or at least strings that have shifted to different line numbers in the source. If so, commit your change and continue to the next step:
```
git commit -a -m "Extract AMO locales"
```
#### Merging locale files
After extracting new strings, you have to merge them into the existing locale files. Do this in your branch and commit:
```
bin/merge-locales
```
Keep an eye out for [fuzzy strings](https://www.gnu.org/software/gettext/manual/html_node/Fuzzy-Entries.html) by running `git diff` and searching for a comment that looks like `# fuzzy`. This comment means the localization may not exactly match the source text; a localizer needs to review it. As per our configuration, the application will not display fuzzy translations. These strings will fall back to English.
In some rare cases you may wish to remove the `fuzzy` marker to prevent falling back to English. Discuss it with a team member before removing `fuzzy` markers.
Commit and continue to the next step:
```
git commit -a -m "Merged AMO locales"
```
#### Finalizing the extract/merge process
Now that you have extracted and merged locales for one application, it's time to create a pull request for your branch. For example:
```
git push origin amo-locales
```
If the pull request passes all of our CI tests it is likely good to merge. You don't need to ask for a review unless you're unsure of something because often locale updates will be thousands of lines of minor diffs that can't be reasonably reviewed by a human. 🙂 If the pull request passes all of our CI tests it is likely good to merge.
#### Building the JS locale files
### Building the JS locale files
This command creates the JSON files which are then built into JS bundles by webpack when the build step is run. This happens automatically as part of the deployment process.
Since dist files are created when needed you only need to build and commit the JSON to the repo.
Since dist files are created when needed you only need to build and commit the JSON to the repo:
```
# build the JSON.
```bash
bin/build-locales
```

Просмотреть файл

@ -92,7 +92,7 @@
}
},
"extract-locales": {
"command": "webpack --progress --color --config webpack.l10n.config.babel.js",
"command": "zx ./bin/extract-locales",
"env": {
"NODE_ENV": "production",
"NODE_ICU_DATA": "./node_modules/full-icu",
@ -253,6 +253,7 @@
"webpack-isomorphic-tools": "4.0.0"
},
"devDependencies": {
"@babel/cli": "^7.23.4",
"@babel/core": "^7.24.7",
"@babel/eslint-parser": "^7.24.7",
"@babel/preset-env": "^7.24.7",
@ -329,7 +330,8 @@
"webpack-cli": "^4.0.0",
"webpack-dev-middleware": "^6.1.2",
"webpack-hot-middleware": "^2.26.1",
"webpack-subresource-integrity": "5.1.0"
"webpack-subresource-integrity": "5.1.0",
"zx": "^8.1.4"
},
"bundlewatch": [
{

Просмотреть файл

@ -1,75 +0,0 @@
/* eslint-disable no-console, import/no-extraneous-dependencies */
import chalk from 'chalk';
import webpack from 'webpack';
import { getRules } from './webpack-common';
import webpackConfig from './webpack.prod.config.babel';
import { WEBPACK_ENTRYPOINT } from './src/amo/constants';
if (process.env.NODE_ENV !== 'production') {
console.log(chalk.red('This should be run with NODE_ENV="production"'));
process.exit(1);
}
const babelConfig = require('./babel.config');
const babelPlugins = babelConfig.plugins || [];
// Create UTC creation date in the correct format.
const potCreationDate = new Date()
.toISOString()
.replace('T', ' ')
.replace(/:\d{2}.\d{3}Z/, '+0000');
const babelL10nPlugins = [
[
'module:babel-gettext-extractor',
{
headers: {
'Project-Id-Version': 'amo',
'Report-Msgid-Bugs-To': 'EMAIL@ADDRESS',
'POT-Creation-Date': potCreationDate,
'PO-Revision-Date': 'YEAR-MO-DA HO:MI+ZONE',
'Last-Translator': 'FULL NAME <EMAIL@ADDRESS>',
'Language-Team': 'LANGUAGE <LL@li.org>',
'MIME-Version': '1.0',
'Content-Type': 'text/plain; charset=utf-8',
'Content-Transfer-Encoding': '8bit',
'plural-forms': 'nplurals=2; plural=(n!=1);',
},
functionNames: {
gettext: ['msgid'],
dgettext: ['domain', 'msgid'],
ngettext: ['msgid', 'msgid_plural', 'count'],
dngettext: ['domain', 'msgid', 'msgid_plural', 'count'],
pgettext: ['msgctxt', 'msgid'],
dpgettext: ['domain', 'msgctxt', 'msgid'],
npgettext: ['msgctxt', 'msgid', 'msgid_plural', 'count'],
dnpgettext: ['domain', 'msgctxt', 'msgid', 'msgid_plural', 'count'],
},
fileName: `locale/templates/LC_MESSAGES/amo.pot`,
baseDirectory: process.cwd(),
stripTemplateLiteralIndent: true,
},
],
];
const babelOptions = {
...babelConfig,
plugins: babelPlugins.concat(babelL10nPlugins),
};
export default {
...webpackConfig,
entry: { [WEBPACK_ENTRYPOINT]: 'amo/client' },
module: {
rules: getRules({ babelOptions }),
},
plugins: [
// Don't generate modules for locale files.
new webpack.IgnorePlugin({
resourceRegExp: /locale\/.*\/amo\.js$/,
}),
...webpackConfig.plugins,
],
};

103
yarn.lock
Просмотреть файл

@ -20,6 +20,22 @@
"@jridgewell/gen-mapping" "^0.1.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@babel/cli@^7.23.4":
version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.24.8.tgz#79eaa55a69c77cafbea3e87537fd1df5a5a2edf8"
integrity sha512-isdp+G6DpRyKc+3Gqxy2rjzgF7Zj9K0mzLNnxz+E/fgeag8qT3vVulX4gY9dGO1q0y+0lUv6V3a+uhUzMzrwXg==
dependencies:
"@jridgewell/trace-mapping" "^0.3.25"
commander "^6.2.0"
convert-source-map "^2.0.0"
fs-readdir-recursive "^1.1.0"
glob "^7.2.0"
make-dir "^2.1.0"
slash "^2.0.0"
optionalDependencies:
"@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3"
chokidar "^3.4.0"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
@ -1479,6 +1495,11 @@
resolved "https://registry.yarnpkg.com/@mozilla-protocol/tokens/-/tokens-5.0.5.tgz#92bc2e5fe61e9706c95f9f3546bed556526dba2a"
integrity sha512-VJ2fYJs09m0x1yWHVGhbqp2ZBOfEZy8vbOGYqNXCVrsYriLZp20CdKJq3+jj/chEVz+S5b0wdIOKkLqvgqIr/A==
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b"
integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@ -1799,6 +1820,14 @@
"@types/qs" "*"
"@types/serve-static" "*"
"@types/fs-extra@>=11":
version "11.0.4"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45"
integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==
dependencies:
"@types/jsonfile" "*"
"@types/node" "*"
"@types/graceful-fs@^4.1.3":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@ -1857,6 +1886,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/jsonfile@*":
version "6.1.4"
resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702"
integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==
dependencies:
"@types/node" "*"
"@types/mime@*":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
@ -1872,6 +1908,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5"
integrity sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==
"@types/node@>=20":
version "20.14.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a"
integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==
dependencies:
undici-types "~5.26.4"
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
@ -3114,7 +3157,7 @@ chokidar-cli@^3.0.0:
lodash.throttle "^4.1.1"
yargs "^13.3.0"
chokidar@3.6.0, chokidar@^3.5.2:
chokidar@3.6.0, chokidar@^3.4.0, chokidar@^3.5.2:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@ -3292,7 +3335,7 @@ commander@^5.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
commander@^6.0.0:
commander@^6.0.0, commander@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
@ -5173,6 +5216,11 @@ fs-monkey@^1.0.3:
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==
fs-readdir-recursive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -5355,7 +5403,7 @@ glob@^10.3.7, glob@^10.4.2:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
glob@^7.0.0, glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@ -9805,6 +9853,11 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@ -10015,16 +10068,7 @@ string-natural-compare@3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
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"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -10138,7 +10182,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -10152,13 +10196,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
@ -10815,6 +10852,11 @@ undefsafe@^2.0.5:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
@ -11340,7 +11382,7 @@ wordwrap@0.0.2:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
integrity sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -11358,15 +11400,6 @@ wrap-ansi@^5.1.0:
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@ -11511,3 +11544,11 @@ yocto-queue@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
zx@^8.1.4:
version "8.1.4"
resolved "https://registry.yarnpkg.com/zx/-/zx-8.1.4.tgz#19bcd543fab34d01d7d4174a314b33cde9115a17"
integrity sha512-QFDYYpnzdpRiJ3dL2102Cw26FpXpWshW4QLTGxiYfIcwdAqg084jRCkK/kuP/NOSkxOjydRwNFG81qzA5r1a6w==
optionalDependencies:
"@types/fs-extra" ">=11"
"@types/node" ">=20"