From 77377fbbec5e3074138947ca90cc10d7d349b24e Mon Sep 17 00:00:00 2001 From: Sebastian Silbermann Date: Thu, 14 Jun 2018 23:05:48 +0200 Subject: [PATCH] feat(locale-data): add messages for item api data --- locale-data/de/api_messages.json | 30 +++++ locale-data/es/api_messages.json | 30 +++++ locale-data/fr/api_messages.json | 30 +++++ locale-data/pt/api_messages.json | 30 +++++ locale-data/ru/api_messages.json | 30 +++++ locale-data/th/api_messages.json | 30 +++++ package.json | 5 + scripts/popup/.gitignore | 1 + scripts/popup/api/getApi.js | 45 ++++++++ scripts/popup/api/getStashTab.js | 23 ++++ scripts/popup/api/getTradeItem.js | 28 +++++ scripts/popup/api/index.js | 4 + scripts/popup/config.js | 14 +++ scripts/popup/createApiMessages.js | 176 +++++++++++++++++++++++++++++ scripts/popup/createMessages.js | 84 ++++++++++++++ scripts/popup/util.js | 39 +++++++ yarn.lock | 14 +++ 17 files changed, 613 insertions(+) create mode 100644 locale-data/de/api_messages.json create mode 100644 locale-data/es/api_messages.json create mode 100644 locale-data/fr/api_messages.json create mode 100644 locale-data/pt/api_messages.json create mode 100644 locale-data/ru/api_messages.json create mode 100644 locale-data/th/api_messages.json create mode 100644 scripts/popup/.gitignore create mode 100644 scripts/popup/api/getApi.js create mode 100644 scripts/popup/api/getStashTab.js create mode 100644 scripts/popup/api/getTradeItem.js create mode 100644 scripts/popup/api/index.js create mode 100644 scripts/popup/config.js create mode 100644 scripts/popup/createApiMessages.js create mode 100644 scripts/popup/createMessages.js create mode 100644 scripts/popup/util.js diff --git a/locale-data/de/api_messages.json b/locale-data/de/api_messages.json new file mode 100644 index 0000000..ac4e496 --- /dev/null +++ b/locale-data/de/api_messages.json @@ -0,0 +1,30 @@ +{ + "Armour": "Rüstung", + "Attacks per Second": "Angriffe pro Sekunde", + "Bow": "Bogen", + "Chaos Damage": "Chaosschaden", + "Claw": "Klaue", + "Corrupted": "Verderbt", + "Critical Strike Chance": "Kritische Trefferchance", + "Dagger": "Dolch", + "Dex": "Ges", + "Elemental Damage": "Elementarschaden", + "Energy Shield": "Energieschild", + "Evasion Rating": "Ausweichwert", + "Int": "Int", + "Level": "Stufe", + "Mirrored": "Gespiegelt", + "One Handed Axe": "Einhandaxt", + "One Handed Mace": "Einhandstreitkolben", + "One Handed Sword": "Einhandschwert", + "Physical Damage": "Physischer Schaden", + "Requires": "Erfordert", + "Staff": "Stab", + "Str": "Str", + "Two Handed Axe": "Zweihandaxt", + "Two Handed Mace": "Zweihandstreitkolben", + "Two Handed Sword": "Zweihandschwert", + "Unidentified": "Nicht identifiziert", + "Wand": "Zauberstab", + "Weapon Range": "Waffenreichweite" +} diff --git a/locale-data/es/api_messages.json b/locale-data/es/api_messages.json new file mode 100644 index 0000000..eda48aa --- /dev/null +++ b/locale-data/es/api_messages.json @@ -0,0 +1,30 @@ +{ + "Armour": "Armadura", + "Attacks per Second": "Ataques por Segundo", + "Bow": "Arco", + "Chaos Damage": "Daño de Caos", + "Claw": "Garra", + "Corrupted": "Corrupto", + "Critical Strike Chance": "Daño de Golpe Crítico", + "Dagger": "Daga", + "Dex": "Des", + "Elemental Damage": "Daño Elemental", + "Energy Shield": "Escudo de Energía", + "Evasion Rating": "Índice de Evasión", + "Int": "Int", + "Level": "Nivel", + "Mirrored": "Reflejado", + "One Handed Axe": "Hacha a Una Mano", + "One Handed Mace": "Maza a Una Mano", + "One Handed Sword": "Espada a Una Mano", + "Physical Damage": "Daño Físico", + "Requires": "Requiere", + "Staff": "Báculo", + "Str": "Fue", + "Two Handed Axe": "Hacha a Dos Manos", + "Two Handed Mace": "Maza a Dos Manos", + "Two Handed Sword": "Espada a Dos Manos", + "Unidentified": "Sin identificar", + "Wand": "Varita", + "Weapon Range": "Rango de Arma" +} diff --git a/locale-data/fr/api_messages.json b/locale-data/fr/api_messages.json new file mode 100644 index 0000000..096084f --- /dev/null +++ b/locale-data/fr/api_messages.json @@ -0,0 +1,30 @@ +{ + "Armour": "Armure", + "Attacks per Second": "Attaques par seconde", + "Bow": "Arc", + "Chaos Damage": "Dégâts de chaos", + "Claw": "Griffe", + "Corrupted": "Corrompu", + "Critical Strike Chance": "Chances de coup critique", + "Dagger": "Dague", + "Dex": "Dex", + "Elemental Damage": "Dégâts élémentaires", + "Energy Shield": "Bouclier d'énergie", + "Evasion Rating": "Score d'Évasion", + "Int": "Int", + "Level": "Niveau", + "Mirrored": "Reflété", + "One Handed Axe": "Hache à une main", + "One Handed Mace": "Masse à une main", + "One Handed Sword": "Épée à une main", + "Physical Damage": "Dégâts physiques", + "Requires": "Nécessite", + "Staff": "Bâton", + "Str": "For", + "Two Handed Axe": "Hache à deux mains", + "Two Handed Mace": "Masse à deux mains", + "Two Handed Sword": "Épée à deux mains", + "Unidentified": "Non identifié", + "Wand": "Baguette", + "Weapon Range": "Allonge" +} diff --git a/locale-data/pt/api_messages.json b/locale-data/pt/api_messages.json new file mode 100644 index 0000000..f197c1a --- /dev/null +++ b/locale-data/pt/api_messages.json @@ -0,0 +1,30 @@ +{ + "Armour": "Armadura", + "Attacks per Second": "Ataques por Segundo", + "Bow": "Arco", + "Chaos Damage": "Dano de Caos", + "Claw": "Garra", + "Corrupted": "Corrompido", + "Critical Strike Chance": "Chance de Crítico", + "Dagger": "Adaga", + "Dex": "Des", + "Elemental Damage": "Dano Elemental", + "Energy Shield": "Escudo de Energia", + "Evasion Rating": "Evasão", + "Int": "Int", + "Level": "Nível", + "Mirrored": "Espelhado", + "One Handed Axe": "Machado de Uma Mão", + "One Handed Mace": "Maça de Uma Mão", + "One Handed Sword": "Espada de Uma Mão", + "Physical Damage": "Dano Físico", + "Requires": "Requer", + "Staff": "Cajado", + "Str": "For", + "Two Handed Axe": "Machado de Duas Mãos", + "Two Handed Mace": "Maça de Duas Mãos", + "Two Handed Sword": "Espada de Duas Mãos", + "Unidentified": "Não Identificado", + "Wand": "Varinha", + "Weapon Range": "Alcance da Arma" +} diff --git a/locale-data/ru/api_messages.json b/locale-data/ru/api_messages.json new file mode 100644 index 0000000..8f257f4 --- /dev/null +++ b/locale-data/ru/api_messages.json @@ -0,0 +1,30 @@ +{ + "Armour": "Броня", + "Attacks per Second": "Атак в секунду", + "Bow": "Лук", + "Chaos Damage": "Урон хаосом", + "Claw": "Когти", + "Corrupted": "Оскверненный", + "Critical Strike Chance": "Шанс критического удара", + "Dagger": "Кинжал", + "Dex": "Ловк", + "Elemental Damage": "Урон от стихий", + "Energy Shield": "Энерг. щит", + "Evasion Rating": "Уклонение", + "Int": "Инт", + "Level": "Уровень", + "Mirrored": "Отзеркаленный", + "One Handed Axe": "Одноручный топор", + "One Handed Mace": "Одноручная булава", + "One Handed Sword": "Одноручный меч", + "Physical Damage": "Физический урон", + "Requires": "Требует", + "Staff": "Посох", + "Str": "Сила", + "Two Handed Axe": "Двуручный топор", + "Two Handed Mace": "Двуручная булава", + "Two Handed Sword": "Двуручный меч", + "Unidentified": "Неопознанный", + "Wand": "Жезл", + "Weapon Range": "Дальность оружия" +} diff --git a/locale-data/th/api_messages.json b/locale-data/th/api_messages.json new file mode 100644 index 0000000..512e291 --- /dev/null +++ b/locale-data/th/api_messages.json @@ -0,0 +1,30 @@ +{ + "Armour": "เกราะ", + "Attacks per Second": "การโจมตีต่อวินาที", + "Bow": "ธนู", + "Chaos Damage": "ความเสียหายจากเคออส", + "Claw": "กรงเล็บ", + "Corrupted": "Corrupted", + "Critical Strike Chance": "โอกาสจู่โจมคริติคอล", + "Dagger": "มีด", + "Dex": "Dex", + "Elemental Damage": "ความเสียหายจากธาตุ", + "Energy Shield": "โล่พลังงาน", + "Evasion Rating": "อัตราการหลบหลีก", + "Int": "Int", + "Level": "เลเวล", + "Mirrored": "สะท้อน", + "One Handed Axe": "ขวานมือเดียว", + "One Handed Mace": "กระบองมือเดียว", + "One Handed Sword": "ดาบมือเดียว", + "Physical Damage": "ความเสียหายทางกายภาพ", + "Requires": "ต้องการ", + "Staff": "ไม้เท้า", + "Str": "Str", + "Two Handed Axe": "ขวานสองมือ", + "Two Handed Mace": "กระบองสองมือ", + "Two Handed Sword": "ดาบสองมือ", + "Unidentified": "ยังไม่ได้ตรวจสอบ", + "Wand": "คทา", + "Weapon Range": "ระยะโจมตีของอาวุธ" +} diff --git a/package.json b/package.json index 2263e3a..b5330a5 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", "format": "prettier --write src/**/*.ts", "format:check": "prettier --list-different src/**/*.ts", + "generate-locale-data-popup": "node --harmony_async_iteration scripts/popup/createMessages && node --harmony_async_iteration scripts/popup/createApiMessages", + "postgenerate-locale-data-popup": "prettier --write \"locale-data/**/api_messages.json\"", "generate-locale-data:clean": "rimraf locale-data/**/*.json", "generate-locale-data": "yarn run parse-locale-data && yarn run process-locale-data", "generate-skill-meta": "node scripts/generateSkillMeta", @@ -59,7 +61,10 @@ "@types/jest": "^23.0.0", "@types/node": "^9.3.0", "coveralls": "^3.0.0", + "dot-prop": "^4.2.0", "jest": "^23.0.0", + "limiter": "^1.1.3", + "mkdirp": "^0.5.1", "moo": "^0.4.3", "nearley": "^2.11.0", "poe-mods": "^1.8.0", diff --git a/scripts/popup/.gitignore b/scripts/popup/.gitignore new file mode 100644 index 0000000..1c2f433 --- /dev/null +++ b/scripts/popup/.gitignore @@ -0,0 +1 @@ +tmp \ No newline at end of file diff --git a/scripts/popup/api/getApi.js b/scripts/popup/api/getApi.js new file mode 100644 index 0000000..5eae486 --- /dev/null +++ b/scripts/popup/api/getApi.js @@ -0,0 +1,45 @@ +const https = require('https'); +const { createGunzip } = require('zlib'); + +module.exports = async function getApi(endpoint, fn_options = {}) { + const { subdomain, timeout = 30 * 1000 } = fn_options; + + const gunzip = createGunzip(); + const options = { + host: `${subdomain ? `${subdomain}.` : ''}pathofexile.com`, + path: `/api/${endpoint}`, + headers: { + 'Accept-Encoding': 'gzip' + } + }; + console.log(`loading ${options.host}${options.path}`); + + return await new Promise((resolve, reject) => { + let data = ''; + const request = https + .get(options, response => { + const readStream = + response.headers['content-encoding'] === 'gzip' + ? response.pipe(gunzip) + : response; + + // response.pipe(file); but with a write after response finishes + readStream.on('data', chunk => (data += chunk)); + readStream.on('end', () => { + const json = JSON.parse(data); + if (response.statusCode !== 200) { + reject(json) + } else { + resolve(json); + } + + }); + response.on('error', err => reject(err)); + }) + .on('error', err => reject(err)) + .setTimeout(timeout, () => { + request.abort(); + reject(`timeout after ${timeout}ms`); + }); + }); +}; diff --git a/scripts/popup/api/getStashTab.js b/scripts/popup/api/getStashTab.js new file mode 100644 index 0000000..3788494 --- /dev/null +++ b/scripts/popup/api/getStashTab.js @@ -0,0 +1,23 @@ +const { RateLimiter } = require('limiter'); + +const getApi = require('./getApi'); + +const limiter = new RateLimiter(2, 'second'); + +module.exports = async function getStashTabLimited(id, options = {}) { + return await new Promise((resolve, reject) => { + limiter.removeTokens(1, err => { + if (err) { + reject(err); + } else { + getStashTab(id, options) + .then(result => resolve(result)) + .catch(err => reject(err)); + } + }); + }); +}; + +async function getStashTab(id, options = {}) { + return await getApi(`public-stash-tabs?id=${id}`, options); +} diff --git a/scripts/popup/api/getTradeItem.js b/scripts/popup/api/getTradeItem.js new file mode 100644 index 0000000..2ae34ab --- /dev/null +++ b/scripts/popup/api/getTradeItem.js @@ -0,0 +1,28 @@ +const { RateLimiter } = require('limiter'); + +const getApi = require('./getApi'); + +// X-Rate-Limit-IP: 20:5:60 +// 20 per 5 seconds, don't know how to apply this with limiter so fallback to 4 every second +const limiter = new RateLimiter(3, 'second'); + +module.exports = async function getTradeItemLimited(item_ids, options = {}) { + return await new Promise((resolve, reject) => { + limiter.removeTokens(1, err => { + if (err) { + reject(err); + } else { + getTradeItem(item_ids, options) + .then(result => resolve(result)) + .catch(err => reject(err)); + } + }); + }); +}; + +async function getTradeItem(item_ids, options = {}) { + const ids = Array.isArray(item_ids) ? item_ids.join(',') : item_ids; + + const { result } = await getApi(`trade/fetch/${ids}`, options); + return result.map(({ item }) => item); +} diff --git a/scripts/popup/api/index.js b/scripts/popup/api/index.js new file mode 100644 index 0000000..3f9bfaa --- /dev/null +++ b/scripts/popup/api/index.js @@ -0,0 +1,4 @@ +const getStashTab = require('./getStashTab'); +const getTradeItem = require('./getTradeItem'); + +module.exports = { getStashTab, getTradeItem }; diff --git a/scripts/popup/config.js b/scripts/popup/config.js new file mode 100644 index 0000000..1039ccb --- /dev/null +++ b/scripts/popup/config.js @@ -0,0 +1,14 @@ +const path = require('path'); + +const LOCALE_DATA_DIR = path.join(__dirname, '../../locale-data'); +const TMP_DIR = path.join(__dirname, 'tmp/messages'); +const LOCALES = [ + { short: 'pt', code: 'pt_BR', subdomain: 'br' }, + { short: 'ru', code: 'ru_RU' }, + { short: 'th', code: 'th_TH' }, + { short: 'de', code: 'de_DE' }, + { short: 'fr', code: 'fr_FR' }, + { short: 'es', code: 'es_ES' } +]; + +module.exports = { LOCALE_DATA_DIR, LOCALES, TMP_DIR }; diff --git a/scripts/popup/createApiMessages.js b/scripts/popup/createApiMessages.js new file mode 100644 index 0000000..b0be88b --- /dev/null +++ b/scripts/popup/createApiMessages.js @@ -0,0 +1,176 @@ +const dotProp = require('dot-prop'); + +const { getStashTab, getTradeItem } = require('./api'); +const { LOCALES } = require('./config'); +const { mergeAllMessages } = require('./util'); + +const MESSAGE_KEYS = [ + createRequirementsProperty('Level'), + createRequirementsProperty('Dex'), + createRequirementsProperty('Int'), + createRequirementsProperty('Str'), + createPropertiesProperty('Claw'), + createPropertiesProperty('Dagger'), + createPropertiesProperty('Wand'), + // sceptres are display with one hand mace + createPropertiesProperty('One Handed Axe'), + createPropertiesProperty('One Handed Mace'), + createPropertiesProperty('One Handed Sword'), + createPropertiesProperty('Two Handed Axe'), + createPropertiesProperty('Two Handed Mace'), + createPropertiesProperty('Two Handed Sword'), + createPropertiesProperty('Staff'), + createPropertiesProperty('Bow'), + createPropertiesProperty('Physical Damage'), + createPropertiesProperty('Elemental Damage'), + createPropertiesProperty('Chaos Damage'), + createPropertiesProperty('Critical Strike Chance'), + createPropertiesProperty('Attacks per Second'), + createPropertiesProperty('Weapon Range'), + createPropertiesProperty('Armour'), + createPropertiesProperty('Energy Shield'), + createPropertiesProperty('Evasion Rating') +]; + +findMessages() + .then(messages => { + return mergeAllMessages(messages); + }) + .then(() => { + return console.log('done'); + }); + +async function findMessages() { + const messages = LOCALES.reduce((acc, locale) => { + acc[locale.short] = {}; + return acc; + }, {}); + + let covered_properties, missing_properties, item; + for await ({ covered_properties, missing_properties, item } of exampleItems( + MESSAGE_KEYS, + '0' + )) { + console.log( + 'Covered: ', + covered_properties.map(({ key }) => key), + 'Missing', + missing_properties.map(({ key }) => key), + 'Latest: ', + item.id + ); + } + + await Promise.all( + // group properties by item + groupBy(covered_properties, ({ item }) => item.id).map(group => { + return Promise.all( + LOCALES.map(locale => { + return getTradeItem(group[0].item.id, { + subdomain: locale.subdomain || locale.short + }) + .then(([item]) => { + for (const property of group) { + const value = dotProp.get(item, property.access_path); + messages[locale.short][property.key] = value; + } + }) + .catch(err => { + console.warn(err, group[0].item.id); + }); + }) + ); + }) + ); + + return messages; +} + +async function* exampleItems(properties, start_id) { + let stash_tab_api_id = start_id; + let missing_properties = properties.slice(); + const covered_properties = []; + + while (missing_properties.length > 0) { + const { next_change_id, stashes } = await getStashTab(stash_tab_api_id); + + for (const item of stashTabApiItems(stashes)) { + const new_covered_properties = []; + const new_missing_properties = missing_properties.filter(property => { + const access_path = property.findAccessPath(item); + if (access_path !== undefined) { + new_covered_properties.push({ ...property, access_path, item }); + return false; + } + + return true; + }); + + if (new_covered_properties.length > 0) { + covered_properties.push(...new_covered_properties); + missing_properties = new_missing_properties; + yield { covered_properties, missing_properties, item }; + } + } + + stash_tab_api_id = next_change_id; + } + + return { covered_properties, missing_properties }; +} + +function* stashTabApiItems(stashes) { + for (const { items = [] } of stashes) { + for (const item of items) { + yield item; + } + } +} + +function createProperty(name, dotProp) { + return { + key: name, + findAccessPath: item => dotProp(item, name) + }; +} + +function createRequirementsProperty(name) { + return createProperty(name, ({ requirements = [] }) => { + const index = requirements.findIndex(requirement => { + return requirement.name === name; + }); + + if (index === -1) { + return undefined; + } else { + return `requirements.${index}.name`; + } + }); +} + +function createPropertiesProperty(name) { + return createProperty(name, ({ properties = [] }) => { + const index = properties.findIndex(property => { + return property.name === name; + }); + + if (index === -1) { + return undefined; + } else { + return `properties.${index}.name`; + } + }); +} + +function groupBy(arr, group) { + const groups = arr.reduce((acc, entry) => { + const key = group(entry); + if (!acc.has(key)) { + acc.set(key, []); + } + acc.get(key).push(entry); + return acc; + }, new Map()); + + return Array.from(groups.values()); +} diff --git a/scripts/popup/createMessages.js b/scripts/popup/createMessages.js new file mode 100644 index 0000000..f39e0cd --- /dev/null +++ b/scripts/popup/createMessages.js @@ -0,0 +1,84 @@ +const fs = require('fs'); +const https = require('https'); +const path = require('path'); +const { URL } = require('url'); +const { promisify } = require('util'); + +const { LOCALES, TMP_DIR } = require('./config'); +const { mergeAllMessages } = require('./util'); + +const mkdirp = promisify(require('mkdirp')); +const rimraf = promisify(require('rimraf')); + +const MESSAGES_BASE_URL = 'https://web.poecdn.com/js/'; + +const MESSAGE_KEYS = ['Corrupted', 'Mirrored', 'Unidentified', 'Requires']; + +createCleanTmp() + .then(async tmp_dir => { + const downloaded = await downloadTranslations(LOCALES, tmp_dir); + const all_messages = {}; + + for (const locale of downloaded) { + const messages = require(locale.require_path); + // filtered_messages = filterObj(messages, key => MESSAGE_KEYS.has(key)); + const needed_messages = {}; + for (const key of MESSAGE_KEYS) { + needed_messages[key] = messages[key]; + } + + all_messages[locale.short] = needed_messages; + } + + return all_messages; + }) + .then(all_messages => { + return mergeAllMessages(all_messages); + }); + +async function createCleanTmp() { + await mkdirp(TMP_DIR); + await rimraf(path.join(TMP_DIR, '*')); + return TMP_DIR; +} + +async function downloadTranslations(locales, tmp_dir) { + const downloads = locales.map(async locale => { + return { + ...locale, + require_path: await loadMessagesFile(locale.code, tmp_dir) + }; + }); + + return await Promise.all(downloads); +} + +async function loadMessagesFile(locale, tmp_dir) { + return await new Promise((resolve, reject) => { + const url = messagesUrl(locale); + const filepath = messagesPath(locale, tmp_dir); + const file = fs + .createWriteStream(filepath) + .on('close', () => resolve(filepath)) + .on('error', err => reject(err)); + + https + .get(url, response => { + // response.pipe(file); but with a write after response finishes + response.on('data', chunk => file.write(chunk)); + response.on('end', () => { + file.end('\n\nmodule.exports = __;\n'); + }); + response.on('error', err => reject(err)); + }) + .on('error', err => reject(err)); + }); +} + +function messagesPath(locale, tmp_dir) { + return path.join(tmp_dir, `translate.${locale}.js`); +} + +function messagesUrl(locale) { + return new URL(`translate.${locale}.js`, MESSAGES_BASE_URL); +} diff --git a/scripts/popup/util.js b/scripts/popup/util.js new file mode 100644 index 0000000..50018f3 --- /dev/null +++ b/scripts/popup/util.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const path = require('path'); +const { promisify } = require('util'); +const { LOCALE_DATA_DIR } = require('./config'); + +const writeFile = promisify(fs.writeFile); + +module.exports = { mergeMessages, mergeAllMessages }; + +async function mergeAllMessages(all_messages) { + for (const [locale, messages] of Object.entries(all_messages)) { + await mergeMessages(locale, messages); + } +} + +/** + * merges the messages into the api_messages.json under the specified locale + * while sorting the resulting json object by keys + * @param {*} locale + * @param {*} messages + */ +async function mergeMessages(locale, messages) { + const data_path = path.join(LOCALE_DATA_DIR, `${locale}/api_messages.json`); + let old_messages = {}; + try { + old_messages = require(data_path); + } catch (err) { + console.log(`creating new messages for locale ${locale}`); + } + const merged_messages = { ...old_messages, ...messages }; + const sorted_messages = Object.entries(merged_messages) + .sort((a, b) => a[0].localeCompare(b[0])) + .reduce((acc, [key, value]) => { + acc[key] = value; + return acc; + }, {}); + const json = JSON.stringify(sorted_messages, undefined, 2); + await writeFile(data_path, json); +} diff --git a/yarn.lock b/yarn.lock index e0ac115..d46ed80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1263,6 +1263,12 @@ domexception@^1.0.0: dependencies: webidl-conversions "^4.0.2" +dot-prop@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" + dependencies: + is-obj "^1.0.0" + duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2167,6 +2173,10 @@ is-number@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + is-odd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" @@ -2853,6 +2863,10 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +limiter@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.3.tgz#32e2eb55b2324076943e5d04c1185ffb387968ef" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"