diff --git a/.github/actions/context/action.yml b/.github/actions/context/action.yml new file mode 100644 index 0000000000..7604b6ecb3 --- /dev/null +++ b/.github/actions/context/action.yml @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..ae0d7421d8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 + + + diff --git a/.prettierignore b/.prettierignore index b7923be159..a65d5d6fe2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,6 +2,7 @@ *.* # exclude these files Dockerfile +src/fonts/LICENSE # exclude these directories /assets/ /bin/ diff --git a/babel.config.locales.js b/babel.config.locales.js new file mode 100644 index 0000000000..c05c53235c --- /dev/null +++ b/babel.config.locales.js @@ -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 ', + 'Language-Team': 'LANGUAGE ', + '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, + }, + ], + ], +}; diff --git a/bin/extract-locales b/bin/extract-locales index 4d00c81a10..282d7b10d0 100755 --- a/bin/extract-locales +++ b/bin/extract-locales @@ -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 diff --git a/bin/merge-locales b/bin/merge-locales deleted file mode 100755 index a156aa2e64..0000000000 --- a/bin/merge-locales +++ /dev/null @@ -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); -}); diff --git a/bin/push-locales b/bin/push-locales new file mode 100755 index 0000000000..1f8ed66030 --- /dev/null +++ b/bin/push-locales @@ -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 "$@" diff --git a/bin/run-l10n-extraction b/bin/run-l10n-extraction deleted file mode 100755 index 6388b2bc87..0000000000 --- a/bin/run-l10n-extraction +++ /dev/null @@ -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 diff --git a/docs/i18n.md b/docs/i18n.md index 9479c94725..b5462cf543 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -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 \n" - "Language-Team: LANGUAGE \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 ``` diff --git a/package.json b/package.json index aa27024005..0ca319a5ba 100644 --- a/package.json +++ b/package.json @@ -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": [ { diff --git a/webpack.l10n.config.babel.js b/webpack.l10n.config.babel.js deleted file mode 100644 index b5bf5667ae..0000000000 --- a/webpack.l10n.config.babel.js +++ /dev/null @@ -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 ', - 'Language-Team': 'LANGUAGE ', - '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, - ], -}; diff --git a/yarn.lock b/yarn.lock index 08f1f8260b..aef7e76b6d 100644 --- a/yarn.lock +++ b/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"