first commit
This commit is contained in:
5
services/web/scripts/translations/.gitignore
vendored
Normal file
5
services/web/scripts/translations/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
|
||||
.cache
|
||||
|
||||
translated-keys-to-upload
|
7
services/web/scripts/translations/Dockerfile
Normal file
7
services/web/scripts/translations/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM node:20.18.2
|
||||
|
||||
WORKDIR /app/scripts/translations
|
||||
|
||||
COPY . /app/scripts/translations/
|
||||
|
||||
RUN npm ci --quiet
|
41
services/web/scripts/translations/checkCoverage.js
Normal file
41
services/web/scripts/translations/checkCoverage.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { loadLocale } from './utils.js'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const LOCALES = Path.join(__dirname, '../../locales')
|
||||
const SORT_BY_PROGRESS = process.argv.includes('--sort-by-progress')
|
||||
|
||||
function count(language) {
|
||||
const locale = loadLocale(language)
|
||||
return Object.keys(locale).length
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const EN = count('en')
|
||||
const rows = []
|
||||
|
||||
for (const file of await fs.promises.readdir(LOCALES)) {
|
||||
if (file === 'README.md') continue
|
||||
const name = file.replace('.json', '')
|
||||
const n = count(name)
|
||||
rows.push({
|
||||
name,
|
||||
done: n,
|
||||
missing: EN - n,
|
||||
progress: ((100 * n) / EN).toFixed(2).padStart(5, ' ') + '%',
|
||||
})
|
||||
}
|
||||
if (SORT_BY_PROGRESS) {
|
||||
rows.sort((a, b) => b.done - a.done)
|
||||
}
|
||||
console.table(rows)
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
43
services/web/scripts/translations/checkSanitizeOptions.js
Normal file
43
services/web/scripts/translations/checkSanitizeOptions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import Path from 'path'
|
||||
import fs from 'fs'
|
||||
import Senitize from './sanitize.js'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { loadLocale } from './utils.js'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const { sanitize } = Senitize
|
||||
|
||||
async function main() {
|
||||
let ok = true
|
||||
const base = Path.join(__dirname, '/../../locales')
|
||||
for (const name of await fs.promises.readdir(base)) {
|
||||
if (name === 'README.md') continue
|
||||
const language = name.replace('.json', '')
|
||||
const locales = loadLocale(language)
|
||||
|
||||
for (const key of Object.keys(locales)) {
|
||||
const want = locales[key]
|
||||
const got = sanitize(locales[key])
|
||||
if (got !== want) {
|
||||
if (want === 'Editor & PDF' && got === 'Editor & PDF') {
|
||||
// Ignore this mismatch. React cannot handle escaped labels.
|
||||
continue
|
||||
}
|
||||
ok = false
|
||||
console.warn(`${name}: ${key}: want: ${want}`)
|
||||
console.warn(`${name}: ${key}: got: ${got}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
throw new Error('Check the logs, some values changed.')
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
87
services/web/scripts/translations/checkVariables.js
Normal file
87
services/web/scripts/translations/checkVariables.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { loadLocale } from './utils.js'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const GLOBALS = ['__appName__']
|
||||
const LOCALES = Path.join(__dirname, '../../locales')
|
||||
const baseLocale = loadLocale('en')
|
||||
const baseLocaleKeys = Object.keys(baseLocale)
|
||||
|
||||
const IGNORE_ORPHANED_TRANSLATIONS = process.argv.includes(
|
||||
'--ignore-orphaned-translations'
|
||||
)
|
||||
|
||||
const IGNORE_NESTING_FOR = {
|
||||
over_x_templates_easy_getting_started: ['__templates__'],
|
||||
all_packages_and_templates: ['__templatesLink__'],
|
||||
}
|
||||
|
||||
function fetchKeys(str) {
|
||||
const matches = str.matchAll(/__.*?__/g)
|
||||
if (matches.length === 0) {
|
||||
return []
|
||||
}
|
||||
return Array.from(matches).map(match => match[0])
|
||||
}
|
||||
|
||||
function difference(key, base, target) {
|
||||
const nesting = IGNORE_NESTING_FOR[key] || []
|
||||
const keysInBaseButNotInTarget = base.filter(
|
||||
key => !target.includes(key) && !nesting.includes(key)
|
||||
)
|
||||
const keysInTargetButNotInBase = target.filter(
|
||||
key => !base.includes(key) && !GLOBALS.includes(key)
|
||||
)
|
||||
return {
|
||||
keysInBaseButNotInTarget,
|
||||
keysInTargetButNotInBase,
|
||||
}
|
||||
}
|
||||
|
||||
let violations = 0
|
||||
for (const localeName of fs.readdirSync(LOCALES)) {
|
||||
if (localeName === 'README.md') continue
|
||||
const locale = loadLocale(localeName.replace('.json', ''))
|
||||
|
||||
for (const key of Object.keys(locale)) {
|
||||
if (!baseLocaleKeys.includes(key)) {
|
||||
if (IGNORE_ORPHANED_TRANSLATIONS) continue
|
||||
violations += 1
|
||||
console.warn(`[${localeName}] Orphaned key "${key}" not found in en.json`)
|
||||
continue
|
||||
}
|
||||
|
||||
const keysInTranslation = fetchKeys(locale[key])
|
||||
const keysInBase = fetchKeys(baseLocale[key])
|
||||
const { keysInBaseButNotInTarget, keysInTargetButNotInBase } = difference(
|
||||
key,
|
||||
keysInBase,
|
||||
keysInTranslation
|
||||
)
|
||||
if (keysInBaseButNotInTarget.length) {
|
||||
violations += keysInBaseButNotInTarget.length
|
||||
console.warn(
|
||||
`[${localeName}] Missing variables in key "${key}":`,
|
||||
keysInBaseButNotInTarget
|
||||
)
|
||||
}
|
||||
if (keysInTargetButNotInBase.length) {
|
||||
violations += keysInTargetButNotInBase.length
|
||||
console.warn(
|
||||
`[${localeName}] Extra variables in key "${key}":`,
|
||||
keysInTargetButNotInBase
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (violations) {
|
||||
console.warn('Variables are not in sync between translations.')
|
||||
process.exit(1)
|
||||
} else {
|
||||
console.log('Variables are in sync.')
|
||||
process.exit(0)
|
||||
}
|
165
services/web/scripts/translations/cleanupUnusedLocales.js
Normal file
165
services/web/scripts/translations/cleanupUnusedLocales.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
import { execSync } from 'child_process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { loadLocale } from './utils.js'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const EN_JSON = Path.join(__dirname, '../../locales/en.json')
|
||||
const CHECK = process.argv.includes('--check')
|
||||
const SYNC_NON_EN = process.argv.includes('--sync-non-en')
|
||||
|
||||
const COUNT_SUFFIXES = [
|
||||
'_plural',
|
||||
'_zero',
|
||||
'_one',
|
||||
'_two',
|
||||
'_few',
|
||||
'_many',
|
||||
'_other',
|
||||
]
|
||||
|
||||
async function main() {
|
||||
const locales = loadLocale('en')
|
||||
|
||||
const src = execSync(
|
||||
// - find all the app source files in web
|
||||
// - exclude data files
|
||||
// - exclude list of locales used in frontend
|
||||
// - exclude locales files
|
||||
// - exclude public assets
|
||||
// - exclude third-party dependencies
|
||||
// - exclude scripts
|
||||
// - exclude tests
|
||||
// - read all the source files
|
||||
`
|
||||
find . -type f \
|
||||
-not -path './cypress/results/*' \
|
||||
-not -path './data/*' \
|
||||
-not -path './frontend/extracted-translations.json' \
|
||||
-not -path './locales/*' \
|
||||
-not -path './public/*' \
|
||||
-not -path '*/node_modules/*' \
|
||||
-not -path '*/scripts/*' \
|
||||
-not -path '*/tests/*' \
|
||||
-exec cat {} +
|
||||
`,
|
||||
{
|
||||
// run from services/web directory
|
||||
cwd: Path.join(__dirname, '../../'),
|
||||
// 1GB
|
||||
maxBuffer: 1024 * 1024 * 1024,
|
||||
// Docs: https://nodejs.org/docs/latest-v16.x/api/child_process.html#child_process_options_stdio
|
||||
// Entries are [stdin, stdout, stderr]
|
||||
stdio: ['ignore', 'pipe', 'inherit'],
|
||||
}
|
||||
).toString()
|
||||
|
||||
const found = new Set([
|
||||
// Month names
|
||||
'january',
|
||||
'february',
|
||||
'march',
|
||||
'april',
|
||||
'may',
|
||||
'june',
|
||||
'july',
|
||||
'august',
|
||||
'september',
|
||||
'october',
|
||||
'november',
|
||||
'december',
|
||||
|
||||
// Notifications created in third-party-datastore
|
||||
'dropbox_email_not_verified',
|
||||
'dropbox_unlinked_because_access_denied',
|
||||
'dropbox_unlinked_because_full',
|
||||
|
||||
// Actually used without the spurious space.
|
||||
// TODO: fix the space and upload the changed locales
|
||||
'the_file_supplied_is_of_an_unsupported_type ',
|
||||
])
|
||||
const matcher = new RegExp(
|
||||
`\\b(${Object.keys(locales)
|
||||
// Sort by length in descending order to match long, compound keys with
|
||||
// special characters (space or -) before short ones.
|
||||
// Examples:
|
||||
// - `\b(x|x-and-y)\b` will match `t('x-and-y')` as 'x'.
|
||||
// This is leaving 'x-and-y' as seemingly unused. Doh!
|
||||
// - `\b(x-and-y|x)\b` will match `t('x-and-y')` as 'x-and-y'. Yay!
|
||||
.sort((a, b) => (a.length < b.length ? 1 : -1))
|
||||
.join('|')})\\b`,
|
||||
'g'
|
||||
)
|
||||
let m
|
||||
while ((m = matcher.exec(src))) {
|
||||
found.add(m[0])
|
||||
for (const suffix of COUNT_SUFFIXES) {
|
||||
found.add(m[0] + suffix)
|
||||
}
|
||||
}
|
||||
|
||||
const unusedKeys = []
|
||||
for (const key of Object.keys(locales)) {
|
||||
if (!found.has(key)) {
|
||||
unusedKeys.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
if (SYNC_NON_EN) {
|
||||
if (CHECK) {
|
||||
throw new Error('--check is incompatible with --sync-non-en')
|
||||
}
|
||||
const LOCALES = Path.join(__dirname, '../../locales')
|
||||
for (const name of await fs.promises.readdir(LOCALES)) {
|
||||
if (name === 'README.md') continue
|
||||
if (name === 'en.json') continue
|
||||
const path = Path.join(LOCALES, name)
|
||||
const locales = loadLocale(name.replace('.json', ''))
|
||||
for (const key of Object.keys(locales)) {
|
||||
if (!found.has(key)) {
|
||||
delete locales[key]
|
||||
}
|
||||
}
|
||||
const sorted =
|
||||
JSON.stringify(locales, Object.keys(locales).sort(), 2) + '\n'
|
||||
await fs.promises.writeFile(path, sorted)
|
||||
}
|
||||
}
|
||||
|
||||
if (unusedKeys.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
console.warn('---')
|
||||
console.warn(
|
||||
`Found ${unusedKeys.length} unused translations keys:\n${unusedKeys
|
||||
.map(s => ` - '${s}'`)
|
||||
.join('\n')}`
|
||||
)
|
||||
console.warn('---')
|
||||
|
||||
if (CHECK) {
|
||||
console.warn('---')
|
||||
console.warn(
|
||||
'Try running:\n\n',
|
||||
' web$ make cleanup_unused_locales',
|
||||
'\n'
|
||||
)
|
||||
console.warn('---')
|
||||
throw new Error('found unused translations keys')
|
||||
}
|
||||
console.log('Deleting unused translations keys')
|
||||
for (const key of unusedKeys) {
|
||||
delete locales[key]
|
||||
}
|
||||
const sorted = JSON.stringify(locales, Object.keys(locales).sort(), 2) + '\n'
|
||||
await fs.promises.writeFile(EN_JSON, sorted)
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
35
services/web/scripts/translations/config.js
Normal file
35
services/web/scripts/translations/config.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const ONESKY_SETTING_PATH = Path.join(__dirname, '../../data/onesky.json')
|
||||
let userOptions
|
||||
try {
|
||||
userOptions = JSON.parse(fs.readFileSync(ONESKY_SETTING_PATH))
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') throw err
|
||||
if (!process.env.ONE_SKY_PUBLIC_KEY) {
|
||||
console.error(
|
||||
'Cannot detect onesky credentials.\n\tDevelopers: see the docs at',
|
||||
'https://github.com/overleaf/developer-manual/blob/master/code/translations.md#testing-translations-scripts',
|
||||
'\n\tOps: environment variable ONE_SKY_PUBLIC_KEY is not set'
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function withAuth(options) {
|
||||
return Object.assign(
|
||||
options,
|
||||
{
|
||||
apiKey: process.env.ONE_SKY_PUBLIC_KEY,
|
||||
secret: process.env.ONE_SKY_PRIVATE_KEY,
|
||||
projectId: '25049',
|
||||
},
|
||||
userOptions
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
withAuth,
|
||||
}
|
12
services/web/scripts/translations/docker-compose.yml
Normal file
12
services/web/scripts/translations/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "2.3"
|
||||
|
||||
services:
|
||||
onesky-client-jail:
|
||||
build: .
|
||||
volumes:
|
||||
# NOTE: Limit code access with targeted bind mount:
|
||||
# Give it read-and-write access to the locales.
|
||||
- ../../locales:/app/locales:rw
|
||||
environment:
|
||||
- ONE_SKY_PUBLIC_KEY
|
||||
- ONE_SKY_PRIVATE_KEY
|
59
services/web/scripts/translations/download.js
Normal file
59
services/web/scripts/translations/download.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import path from 'path'
|
||||
import { promises as fs } from 'fs'
|
||||
import oneSky from '@brainly/onesky-utils'
|
||||
import Sanitize from './sanitize.js'
|
||||
import Config from './config.js'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const { sanitize } = Sanitize
|
||||
const { withAuth } = Config
|
||||
|
||||
async function run() {
|
||||
// The recommended OneSky set-up appears to require an API request to
|
||||
// generate files on their side, which you could then request and use. We
|
||||
// only have 1 such file that appears to be misnamed (en-US, despite our
|
||||
// translations being marked as GB) and very out-of-date.
|
||||
// However by requesting the "multilingual file" for this file, we get all
|
||||
// of the translations
|
||||
const content = await oneSky.getMultilingualFile(
|
||||
withAuth({
|
||||
fileName: 'en-US.json',
|
||||
})
|
||||
)
|
||||
const json = JSON.parse(content)
|
||||
|
||||
for (const [code, lang] of Object.entries(json)) {
|
||||
if (code === 'en-GB') {
|
||||
// OneSky does not have read-after-write consistency.
|
||||
// Skip the dump of English locales, which may not include locales
|
||||
// that were just uploaded.
|
||||
continue
|
||||
}
|
||||
|
||||
for (let [key, value] of Object.entries(lang.translation)) {
|
||||
// Handle multi-line strings as arrays by joining on newline
|
||||
if (Array.isArray(value)) {
|
||||
value = value.join('\n')
|
||||
}
|
||||
lang.translation[key] = sanitize(value)
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(__dirname, `/../../locales/${code}.json`),
|
||||
JSON.stringify(
|
||||
lang.translation,
|
||||
Object.keys(lang.translation).sort(),
|
||||
2
|
||||
) + '\n'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await run()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
63
services/web/scripts/translations/insertHTMLFragments.js
Normal file
63
services/web/scripts/translations/insertHTMLFragments.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
This script will aid the process of inserting HTML fragments into all the
|
||||
locales.
|
||||
We are migrating from
|
||||
locale: 'PRE __key1__ POST'
|
||||
pug: translate(localeKey, { key1: '<b>VALUE</b>' })
|
||||
to
|
||||
locale: 'PRE <0>__key1__</0> POST'
|
||||
pug: translate(localeKey, { key1: 'VALUE' }, ['b'])
|
||||
|
||||
|
||||
MAPPING entries:
|
||||
localeKey: ['key1', 'key2']
|
||||
click_here_to_view_sl_in_lng: ['lngName']
|
||||
*/
|
||||
import TransformLocales from './transformLocales.js'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const MAPPING = {
|
||||
support_lots_of_features: ['help_guides_link'],
|
||||
nothing_to_install_ready_to_go: ['start_now'],
|
||||
all_packages_and_templates: ['templatesLink'],
|
||||
github_merge_failed: ['sharelatex_branch', 'master_branch'],
|
||||
kb_suggestions_enquiry: ['kbLink'],
|
||||
sure_you_want_to_restore_before: ['filename'],
|
||||
you_have_added_x_of_group_size_y: ['addedUsersSize', 'groupSize'],
|
||||
x_price_per_month: ['price'],
|
||||
x_price_per_year: ['price'],
|
||||
x_price_for_first_month: ['price'],
|
||||
x_price_for_first_year: ['price'],
|
||||
sure_you_want_to_change_plan: ['planName'],
|
||||
subscription_canceled_and_terminate_on_x: ['terminateDate'],
|
||||
next_payment_of_x_collectected_on_y: ['paymentAmmount', 'collectionDate'],
|
||||
currently_subscribed_to_plan: ['planName'],
|
||||
recurly_email_update_needed: ['recurlyEmail', 'userEmail'],
|
||||
change_to_annual_billing_and_save: ['percentage', 'yearlySaving'],
|
||||
project_ownership_transfer_confirmation_1: ['user', 'project'],
|
||||
you_introed_high_number: ['numberOfPeople'],
|
||||
you_introed_small_number: ['numberOfPeople'],
|
||||
click_here_to_view_sl_in_lng: ['lngName'],
|
||||
}
|
||||
|
||||
function transformLocale(locale, components) {
|
||||
components.forEach((key, idx) => {
|
||||
const i18nKey = `__${key}__`
|
||||
const replacement = `<${idx}>${i18nKey}</${idx}>`
|
||||
if (!locale.includes(replacement)) {
|
||||
locale = locale.replace(new RegExp(i18nKey, 'g'), replacement)
|
||||
}
|
||||
})
|
||||
return locale
|
||||
}
|
||||
|
||||
function main() {
|
||||
TransformLocales.transformLocales(MAPPING, transformLocale)
|
||||
}
|
||||
|
||||
if (
|
||||
fileURLToPath(import.meta.url).replace(/\.js$/, '') ===
|
||||
process.argv[1].replace(/\.js$/, '')
|
||||
) {
|
||||
main()
|
||||
}
|
915
services/web/scripts/translations/package-lock.json
generated
Normal file
915
services/web/scripts/translations/package-lock.json
generated
Normal file
@@ -0,0 +1,915 @@
|
||||
{
|
||||
"name": "translations",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@brainly/onesky-utils": "https://github.com/overleaf/nodejs-onesky-utils/archive/main.tar.gz",
|
||||
"node-fetch": "^2.7.0",
|
||||
"sanitize-html": "^2.12.1",
|
||||
"yargs": "^17.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@brainly/onesky-utils": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://github.com/overleaf/nodejs-onesky-utils/archive/main.tar.gz",
|
||||
"integrity": "sha512-B4mNrHXvR7ZHf2we+dtGa8ME/8rqHWWM5m05LzcpVAUpIQTxFL+vPsBzis7grVu5TgXlII481stj/68+YIRgPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"md5": "^2.0.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
|
||||
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"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/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/asn1": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
|
||||
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/aws-sign2": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/aws4": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz",
|
||||
"integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA=="
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
|
||||
},
|
||||
"node_modules/caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
||||
},
|
||||
"node_modules/charenc": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"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/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"node_modules/crypt": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/dashdash": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ecc-jsbn": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
||||
"dependencies": {
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"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/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"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==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
|
||||
},
|
||||
"node_modules/extsprintf": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
||||
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
]
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"node_modules/forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/getpass": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/har-schema": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/har-validator": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
|
||||
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
|
||||
"deprecated": "this library is no longer supported",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.3",
|
||||
"har-schema": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"jsprim": "^1.2.2",
|
||||
"sshpk": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8",
|
||||
"npm": ">=1.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
|
||||
},
|
||||
"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-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
||||
},
|
||||
"node_modules/isstream": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
||||
},
|
||||
"node_modules/jsbn": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
||||
},
|
||||
"node_modules/jsprim": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
|
||||
"dependencies": {
|
||||
"assert-plus": "1.0.0",
|
||||
"extsprintf": "1.3.0",
|
||||
"json-schema": "0.4.0",
|
||||
"verror": "1.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||
"dependencies": {
|
||||
"charenc": "0.0.2",
|
||||
"crypt": "0.0.2",
|
||||
"is-buffer": "~1.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.44.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
|
||||
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.27",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
|
||||
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.44.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/oauth-sign": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-srcset": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
|
||||
"integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE="
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
|
||||
"dependencies": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/request-promise": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
|
||||
"integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
|
||||
"deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.0",
|
||||
"request-promise-core": "1.1.4",
|
||||
"stealthy-require": "^1.1.1",
|
||||
"tough-cookie": "^2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"request": "^2.34"
|
||||
}
|
||||
},
|
||||
"node_modules/request-promise-core": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
|
||||
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.19"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"request": "^2.34"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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==",
|
||||
"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/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sanitize-html": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz",
|
||||
"integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==",
|
||||
"dependencies": {
|
||||
"deepmerge": "^4.2.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"htmlparser2": "^8.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"parse-srcset": "^1.0.2",
|
||||
"postcss": "^8.3.11"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sshpk": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
|
||||
"dependencies": {
|
||||
"asn1": "~0.2.3",
|
||||
"assert-plus": "^1.0.0",
|
||||
"bcrypt-pbkdf": "^1.0.0",
|
||||
"dashdash": "^1.12.0",
|
||||
"ecc-jsbn": "~0.1.1",
|
||||
"getpass": "^0.1.1",
|
||||
"jsbn": "~0.1.0",
|
||||
"safer-buffer": "^2.0.2",
|
||||
"tweetnacl": "~0.14.0"
|
||||
},
|
||||
"bin": {
|
||||
"sshpk-conv": "bin/sshpk-conv",
|
||||
"sshpk-sign": "bin/sshpk-sign",
|
||||
"sshpk-verify": "bin/sshpk-verify"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stealthy-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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/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/tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
|
||||
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
|
||||
"bin": {
|
||||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
"core-util-is": "1.0.2",
|
||||
"extsprintf": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
services/web/scripts/translations/package.json
Normal file
9
services/web/scripts/translations/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@brainly/onesky-utils": "https://github.com/overleaf/nodejs-onesky-utils/archive/main.tar.gz",
|
||||
"node-fetch": "^2.7.0",
|
||||
"sanitize-html": "^2.12.1",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
42
services/web/scripts/translations/replaceLinkFragments.js
Normal file
42
services/web/scripts/translations/replaceLinkFragments.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This script will aid the process of inserting HTML fragments into all the
|
||||
locales.
|
||||
We are migrating from
|
||||
locale: 'PRE __keyLinkOpen__INNER__keyLinkClose__ POST'
|
||||
pug: translate(localeKey, { keyLinkOpen: '<a ...>', keyLinkClose: '</a>' })
|
||||
to
|
||||
locale: 'PRE <0>INNER</0> POST'
|
||||
pug: translate(localeKey, {}, [{ name: 'a', attrs: { href: '...', ... }}])
|
||||
|
||||
|
||||
MAPPING entries:
|
||||
localeKey: ['keyLinkOpen', 'keyLinkClose']
|
||||
faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose']
|
||||
*/
|
||||
import TransformLocales from './transformLocales.js'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const MAPPING = {
|
||||
also_provides_free_plan: ['registerLinkOpen', 'registerLinkClose'],
|
||||
faq_pay_by_invoice_answer: ['payByInvoiceLinkOpen', 'payByInvoiceLinkClose'],
|
||||
}
|
||||
|
||||
function transformLocale(locale, [open, close]) {
|
||||
const i18nOpen = `__${open}__`
|
||||
const i18nClose = `__${close}__`
|
||||
if (locale.includes(i18nOpen)) {
|
||||
locale = locale.replace(i18nOpen, '<0>').replace(i18nClose, '</0>')
|
||||
}
|
||||
return locale
|
||||
}
|
||||
|
||||
function main() {
|
||||
TransformLocales.transformLocales(MAPPING, transformLocale)
|
||||
}
|
||||
|
||||
if (
|
||||
fileURLToPath(import.meta.url).replace(/\.js$/, '') ===
|
||||
process.argv[1].replace(/\.js$/, '')
|
||||
) {
|
||||
main()
|
||||
}
|
46
services/web/scripts/translations/sanitize.js
Normal file
46
services/web/scripts/translations/sanitize.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
|
||||
/**
|
||||
* Sanitize a translation string to prevent injection attacks
|
||||
*
|
||||
* @param {string} input
|
||||
* @returns {string}
|
||||
*/
|
||||
function sanitize(input) {
|
||||
// Block Angular XSS
|
||||
// Ticket: https://github.com/overleaf/issues/issues/4478
|
||||
input = input.replace(/'/g, '’')
|
||||
// Use left quote where (likely) appropriate.
|
||||
input.replace(/ ’/g, ' ‘')
|
||||
|
||||
// Allow "replacement" tags (in the format <0>, <1>, <2>, etc) used by
|
||||
// react-i18next to allow for HTML insertion via the Trans component.
|
||||
// See: https://github.com/overleaf/developer-manual/blob/master/code/translations.md
|
||||
// The html parser of sanitize-html is only accepting ASCII alpha characters
|
||||
// at the start of HTML tags. So we need to replace these ahead of parsing
|
||||
// and restore them afterwards.
|
||||
input = input.replaceAll(/<([/]?[0-9])>/g, '<$1>')
|
||||
|
||||
return (
|
||||
sanitizeHtml(input, {
|
||||
allowedTags: ['b', 'strong', 'a', 'code'],
|
||||
allowedAttributes: {
|
||||
a: ['href', 'class'],
|
||||
},
|
||||
textFilter(text) {
|
||||
// Block Angular XSS
|
||||
if (text === '{') return '{'
|
||||
if (text === '}') return '}'
|
||||
return text
|
||||
.replace(/\{\{/, '{{')
|
||||
.replace(/\}\}/, '}}')
|
||||
},
|
||||
})
|
||||
// Restore the escaping again.
|
||||
.replaceAll(/<([/]?[0-9])>/g, '<$1>')
|
||||
// Restore escaped standalone ampersands
|
||||
.replaceAll(/ & /g, ' & ')
|
||||
)
|
||||
}
|
||||
|
||||
export default { sanitize }
|
41
services/web/scripts/translations/sort.js
Normal file
41
services/web/scripts/translations/sort.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const LOCALES = Path.join(__dirname, '../../locales')
|
||||
const CHECK = process.argv.includes('--check')
|
||||
|
||||
async function main() {
|
||||
for (const locale of await fs.promises.readdir(LOCALES)) {
|
||||
if (locale === 'README.md') continue
|
||||
const path = Path.join(LOCALES, locale)
|
||||
const input = await fs.promises.readFile(path, 'utf-8')
|
||||
const parsed = JSON.parse(input)
|
||||
const sorted = JSON.stringify(parsed, Object.keys(parsed).sort(), 2) + '\n'
|
||||
if (input === sorted) {
|
||||
continue
|
||||
}
|
||||
if (CHECK) {
|
||||
console.warn('---')
|
||||
console.warn(
|
||||
locale,
|
||||
'is not sorted. Try running:\n\n',
|
||||
' web$ make sort_locales',
|
||||
'\n'
|
||||
)
|
||||
console.warn('---')
|
||||
throw new Error(locale + ' is not sorted')
|
||||
}
|
||||
console.log('Storing sorted version of', locale)
|
||||
await fs.promises.writeFile(path, sorted)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
54
services/web/scripts/translations/transformLocales.js
Normal file
54
services/web/scripts/translations/transformLocales.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import Path from 'path'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { loadLocale } from './utils.js'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const LANGUAGES = [
|
||||
'cs',
|
||||
'da',
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fi',
|
||||
'fr',
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'nl',
|
||||
'no',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'sv',
|
||||
'tr',
|
||||
'zh-CN',
|
||||
]
|
||||
const LOCALES = {}
|
||||
LANGUAGES.forEach(loadLocales)
|
||||
function loadLocales(language) {
|
||||
LOCALES[language] = loadLocale(language)
|
||||
}
|
||||
|
||||
function transformLocales(mapping, transformLocale) {
|
||||
Object.entries(LOCALES).forEach(([language, translatedLocales]) => {
|
||||
Object.entries(mapping).forEach(([localeKey, spec]) => {
|
||||
const locale = translatedLocales[localeKey]
|
||||
if (!locale) {
|
||||
// This locale is not translated yet.
|
||||
return
|
||||
}
|
||||
translatedLocales[localeKey] = transformLocale(locale, spec)
|
||||
})
|
||||
|
||||
fs.writeFileSync(
|
||||
Path.join(__dirname, `/../../locales/${language}.json`),
|
||||
JSON.stringify(translatedLocales, null, 2) + '\n'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
transformLocales,
|
||||
}
|
113
services/web/scripts/translations/translateLocales.js
Normal file
113
services/web/scripts/translations/translateLocales.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import yargs from 'yargs/yargs'
|
||||
import { hideBin } from 'yargs/helpers'
|
||||
import Path from 'path'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import Settings from '../../config/settings.defaults.js'
|
||||
import Readline from 'readline'
|
||||
import { loadLocale as loadTranslations } from './utils.js'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const LOCALES = Path.join(__dirname, '../../locales')
|
||||
const VALID_LOCALES = Object.keys(Settings.translatedLanguages).filter(
|
||||
locale => locale !== 'en'
|
||||
)
|
||||
|
||||
const readline = Readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
})
|
||||
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.usage('Translate locales')
|
||||
.option('locale', {
|
||||
alias: 'l',
|
||||
type: 'string',
|
||||
required: 'true',
|
||||
description: 'Target 2-letter locale code',
|
||||
choices: VALID_LOCALES,
|
||||
})
|
||||
.option('skip-until', {
|
||||
type: 'string',
|
||||
description: 'Skip locales until after the provided key',
|
||||
})
|
||||
.parse()
|
||||
|
||||
async function translateLocales() {
|
||||
let { locale, skip } = argv
|
||||
console.log(`Looking for missing [${locale}] translations...`)
|
||||
|
||||
const keysToUploadFolder = Path.join(__dirname, `translated-keys-to-upload`)
|
||||
if (!fs.existsSync(keysToUploadFolder)) {
|
||||
fs.mkdirSync(keysToUploadFolder)
|
||||
}
|
||||
|
||||
const localeKeysToUploadPath = Path.join(keysToUploadFolder, `${locale}.json`)
|
||||
const keysToUpload = fs.existsSync(localeKeysToUploadPath)
|
||||
? JSON.parse(fs.readFileSync(localeKeysToUploadPath, 'utf-8'))
|
||||
: []
|
||||
|
||||
const englishTranslations = await loadTranslations('en')
|
||||
const englishKeys = Object.keys(englishTranslations)
|
||||
|
||||
const localeTranslations = await loadTranslations(locale)
|
||||
const translatedKeys = Object.keys(localeTranslations)
|
||||
console.log(
|
||||
`Currently translated: ${translatedKeys.length} / ${englishKeys.length}`
|
||||
)
|
||||
|
||||
for (const key of englishKeys) {
|
||||
if (skip) {
|
||||
if (key === skip) skip = ''
|
||||
continue
|
||||
}
|
||||
const translation = localeTranslations[key]
|
||||
if (!translation || translation.length === 0) {
|
||||
let value = await prompt(
|
||||
`\nMissing translation for: ${key}\n"${englishTranslations[key]}"\n`
|
||||
)
|
||||
while (value.includes("'")) {
|
||||
value = await prompt(
|
||||
`\nTranslations should not contain single-quote characters, please use curvy quotes (‘ or ’) instead:\n`
|
||||
)
|
||||
}
|
||||
if (!value) {
|
||||
console.log(`Skipping ${key}`)
|
||||
continue
|
||||
}
|
||||
localeTranslations[key] = value
|
||||
|
||||
const path = Path.join(LOCALES, `${locale}.json`)
|
||||
const sorted =
|
||||
JSON.stringify(
|
||||
localeTranslations,
|
||||
Object.keys(localeTranslations).sort(),
|
||||
2
|
||||
) + '\n'
|
||||
fs.writeFileSync(path, sorted)
|
||||
|
||||
keysToUpload.push(key)
|
||||
const formattedKeysToUpload =
|
||||
JSON.stringify(Array.from(new Set(keysToUpload)), null, 2) + '\n'
|
||||
fs.writeFileSync(localeKeysToUploadPath, formattedKeysToUpload)
|
||||
|
||||
console.log(`"${key}": "${value}" added to ${locale}.json`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prompt(text) {
|
||||
return new Promise((resolve, reject) =>
|
||||
readline.question(text, value => {
|
||||
resolve(value)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
await translateLocales()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
}
|
71
services/web/scripts/translations/upload.js
Normal file
71
services/web/scripts/translations/upload.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import Path from 'path'
|
||||
import { promises as fs } from 'fs'
|
||||
import { promisify } from 'util'
|
||||
import oneSky from '@brainly/onesky-utils'
|
||||
import Config from './config.js'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const { withAuth } = Config
|
||||
|
||||
const sleep = promisify(setTimeout)
|
||||
|
||||
async function uploadLocales() {
|
||||
// Docs: https://github.com/onesky/api-documentation-platform/blob/master/resources/file.md#upload---upload-a-file
|
||||
const blob = await oneSky.postFile(
|
||||
withAuth({
|
||||
fileName: 'en-US.json',
|
||||
language: 'en-GB',
|
||||
format: 'HIERARCHICAL_JSON',
|
||||
content: await fs.readFile(
|
||||
Path.join(__dirname, '/../../locales/en.json')
|
||||
),
|
||||
keepStrings: false, // deprecate locales that no longer exist in en.json
|
||||
})
|
||||
)
|
||||
return JSON.parse(blob).data.import.id
|
||||
}
|
||||
|
||||
async function getImportTask(importId) {
|
||||
// Docs: https://github.com/onesky/api-documentation-platform/blob/master/resources/import_task.md
|
||||
const blob = await oneSky.getImportTask(withAuth({ importId }))
|
||||
return JSON.parse(blob).data
|
||||
}
|
||||
|
||||
async function pollUploadStatus(importId) {
|
||||
let task
|
||||
while ((task = await getImportTask(importId)).status === 'in-progress') {
|
||||
console.log('onesky is processing the import ...')
|
||||
await sleep(5000)
|
||||
}
|
||||
if (task.status === 'failed') {
|
||||
console.error(JSON.stringify({ task }, null, 2))
|
||||
throw new Error('upload failed')
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadOnce() {
|
||||
const importId = await uploadLocales()
|
||||
await pollUploadStatus(importId)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await uploadOnce()
|
||||
} catch (err) {
|
||||
console.error('--- upload failed once ---')
|
||||
console.error(err)
|
||||
console.error('--- upload failed once ---')
|
||||
console.log('retrying upload in 30s')
|
||||
await sleep(30_000)
|
||||
await uploadOnce()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await main()
|
||||
} catch (error) {
|
||||
console.error({ error })
|
||||
process.exit(1)
|
||||
}
|
12
services/web/scripts/translations/utils.js
Normal file
12
services/web/scripts/translations/utils.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { fileURLToPath } from 'url'
|
||||
import fs from 'fs'
|
||||
import Path from 'path'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const LOCALES_FOLDER = Path.join(__dirname, '../../locales')
|
||||
|
||||
export function loadLocale(language) {
|
||||
return JSON.parse(
|
||||
fs.readFileSync(Path.join(LOCALES_FOLDER, `${language}.json`), 'utf-8')
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user