TT#106254 AUI/CSC: As a Developer, I want to add a new translation by executing a script

AC:
Can run a script from package.json "i18n:extract" to add new translation keys found in source files
Can see the new translation in all language files
Can see an empty string but the key for untranslated phrases
Can see all language files synchronised based on the english translation

Change-Id: I7181a5224836f5e8f275cee4c975cb6d5199d8c0
mr9.3
Sergii Leonenko 5 years ago
parent d02f82e69c
commit 9f1b25c5f9

@ -52,6 +52,50 @@ In addition, we also recommend the following Quasar Framework tutorials:
* [Reusable Vue.js Components](doc/COMPONENTS.md)
### Translations
To keep translation files consistent and updated please run **i18n:extract** command before each commit to the GIT repo.
yarn run i18n:extract
That CLI command will collect all new translation keys from the JS source code, and will place those keys into all translation files in a proper format.
Example of the JS code with translations:
```javascript
const someOptions = {
label: this.$t('Remove message'),
message: this.$t('The {email} will be removed. Are you sure?', { email: this.email })
}
```
**Important**: We are trying to avoid usage of the dynamic keys (example below) because it's very difficult to detect and collect automatically.
Example (anti-pattern):
```javascript
function getTranslatedMessage (weatherState) {
return this.$t('Tooday is ' + weatherState)
}
```
Try to avoid such code and use text messages with substitution variables (like `email` in example above) or if there are only a couple similar messages, you can use a map object to convert some exact state to exact translation message.
But if it's really impossible to do, and you have to use dynamic keys, you should place such keys to the English translation file manually and execute `i18n:extract` which will do all the rest.
For example, for the code above, you would need to place next lines into `en.json`:
```JSON
{
...
"Today is sunny": "",
"Today is windy": "",
"Today is cloudy": ""
}
```
**Note**: if you want to see information about missed or possible unused translation keys you could run **i18n:extract-report** command. It will just display detailed report without any modifications in the language files.
Keep in mind that some of "Unused translations" keys might be dynamic translation keys which cannot be detected in source code automatically and were added manually.
yarn run i18n:extract-report
### Add a new page
In order to add a new page you need to go along the following steps:

@ -0,0 +1,10 @@
#!/bin/bash
if [ "$1" == "report" ]; then
OTHER_PARAMS=()
else
OTHER_PARAMS=(--add --json-sort-keys --json-indent-characters "1,tab" --no-detailed-report)
fi
node ./bin/vue-i18n-extract/vue-i18n-extract.js report -v './src/**/*.?(js|vue)' -l './src/i18n/*.json' \
--no-dot-notation --add-value-for-languages en --main-language-to-sync-keys en "${OTHER_PARAMS[@]}"

@ -0,0 +1 @@
Codebase of the tool is vue-i18n-extract:v1.1.11 package

@ -0,0 +1,77 @@
#!/usr/bin/env node
// vim: set filetype=javascript:
/* eslint-disable */
'use strict';
const program = require('commander');
const { reportCommand } = require('./vue-i18n-extract.umd.js');
function increaseDynamic(dummyValue, previous) {
return previous + 1;
}
program
.command('report', { isDefault: true })
.description('Create a report from a glob of your Vue.js source files and your language files.')
.requiredOption(
'-v, --vueFiles <vueFiles>',
'The Vue.js file(s) you want to extract i18n strings from. It can be a path to a folder or to a file. It accepts glob patterns. (ex. *, ?, (pattern|pattern|pattern)',
)
.requiredOption(
'-l, --languageFiles <languageFiles>',
'The language file(s) you want to compare your Vue.js file(s) to. It can be a path to a folder or to a file. It accepts glob patterns (ex. *, ?, (pattern|pattern|pattern) ',
)
.option(
'-o, --output <output>',
'Use if you want to create a json file out of your report. (ex. -o output.json)',
)
.option(
'-a, --add',
'Use if you want to add missing keys into your json language files.',
)
.option(
'-d, --dynamic',
'Use if you want to ignore dynamic keys false-positive. Use it 2 times to get dynamic keys report',
increaseDynamic,
0
)
.option(
'--no-dot-notation',
'Use if your language keys are flat, contains dots and you do not use a dot character as a separator for nested key structure'
)
.option(
'--add-value-for-languages <languages>',
'Duplicate missing key text to it`s value for specified languages. (Pass them as coma-separated parameter value)',
function splitLanguages(value= '') {
return value.split(',');
}
)
.option(
'--main-language-to-sync-keys <mainLanguage>',
'Use it if you want to check and synchronize all translation keys from some lang file to all others. Mainly it should be English lang file'
)
.option(
'--json-sort-keys',
'Use if you want to resort language JSON file`s keys alphabetically',
false
)
.option(
'--json-indent-characters <jsonIndentCharacters>',
'You can specify indentation characters for lines in language JSON files. It will work if JSON language file will be updated, for example because of (--add) option',
function checkValue(value) {
let [amount, character] = value.split(',');
amount = parseInt(amount, 10)
if (isNaN(amount) || amount <= 0 || !['space', 'tab'].includes(character)) {
console.error(`Unknown or incorrect value format for "--json-indent-characters" option: "${value}"`);
process.exit(1)
}
return value
},
'2,space'
)
.option(
'--no-detailed-report',
'Use if you do not want to see detailed list of the keys output on the screen'
)
.action(reportCommand);
program.parseAsync(process.argv);

@ -0,0 +1,466 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('path'), require('is-valid-glob'), require('glob'), require('fs'), require('dot-object'), require('js-yaml')) :
typeof define === 'function' && define.amd ? define(['exports', 'path', 'is-valid-glob', 'glob', 'fs', 'dot-object', 'js-yaml'], factory) :
(global = global || self, factory(global.vueI18NExtract = {}, global.path, global.isValidGlob, global.glob, global.fs, global.dotObject, global.jsYaml));
}(this, (function (exports, path, isValidGlob, glob, fs, dot, yaml) {
path = path && Object.prototype.hasOwnProperty.call(path, 'default') ? path['default'] : path;
isValidGlob = isValidGlob && Object.prototype.hasOwnProperty.call(isValidGlob, 'default') ? isValidGlob['default'] : isValidGlob;
glob = glob && Object.prototype.hasOwnProperty.call(glob, 'default') ? glob['default'] : glob;
fs = fs && Object.prototype.hasOwnProperty.call(fs, 'default') ? fs['default'] : fs;
dot = dot && Object.prototype.hasOwnProperty.call(dot, 'default') ? dot['default'] : dot;
yaml = yaml && Object.prototype.hasOwnProperty.call(yaml, 'default') ? yaml['default'] : yaml;
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function readVueFiles(src) {
if (!isValidGlob(src)) {
throw new Error(`vueFiles isn't a valid glob pattern.`);
}
const targetFiles = glob.sync(src);
if (targetFiles.length === 0) {
throw new Error('vueFiles glob has no files.');
}
return targetFiles.map(f => {
const fileName = f.replace(process.cwd(), '');
return {
fileName,
path: f,
content: fs.readFileSync(f, 'utf8')
};
});
}
function* getMatches(file, regExp, captureGroup = 1) {
while (true) {
const match = regExp.exec(file.content);
if (match === null) {
break;
}
const line = (file.content.substring(0, match.index).match(/\n/g) || []).length + 1;
yield {
path: match[captureGroup],
line,
file: file.fileName
};
}
}
/**
* Extracts translation keys from methods such as `$t` and `$tc`.
*
* - **regexp pattern**: (?:[$ .]tc?)\(
*
* **description**: Matches the sequence t( or tc(, optionally with either $, . or in front of it.
*
* - **regexp pattern**: (["'`])
*
* **description**: 1. capturing group. Matches either ", ', or `”.
*
* - **regexp pattern**: ((?:[^\\]|\\.)*?)
*
* **description**: 2. capturing group. Matches anything except a backslash
* *or* matches any backslash followed by any character (e.g. \", \`”, “\t”, etc.)
*
* - **regexp pattern**: \1
*
* **description**: matches whatever was matched by capturing group 1 (e.g. the starting string character)
*
* @param file a file object
* @returns a list of translation keys found in `file`.
*/
function extractMethodMatches(file) {
const methodRegExp = /(?:[$ .]tc?)\(\s*?(["'`])((?:[^\\]|\\.)*?)\1/g;
return [...getMatches(file, methodRegExp, 2)];
}
function extractComponentMatches(file) {
const componentRegExp = /(?:<i18n)(?:.|\n)*?(?:[^:]path=("|'))(.*?)\1/gi;
return [...getMatches(file, componentRegExp, 2)];
}
function extractDirectiveMatches(file) {
const directiveRegExp = /v-t="'(.*?)'"/g;
return [...getMatches(file, directiveRegExp)];
}
function extractI18nItemsFromVueFiles(sourceFiles) {
return sourceFiles.reduce((accumulator, file) => {
const methodMatches = extractMethodMatches(file);
const componentMatches = extractComponentMatches(file);
const directiveMatches = extractDirectiveMatches(file);
return [...accumulator, ...methodMatches, ...componentMatches, ...directiveMatches];
}, []);
}
function parseVueFiles(vueFilesPath) {
const filesList = readVueFiles(vueFilesPath);
return extractI18nItemsFromVueFiles(filesList);
}
function readLangFiles(src) {
if (!isValidGlob(src)) {
throw new Error(`languageFiles isn't a valid glob pattern.`);
}
const targetFiles = glob.sync(src);
if (targetFiles.length === 0) {
throw new Error('languageFiles glob has no files.');
}
return targetFiles.map(f => {
const langPath = path.resolve(process.cwd(), f);
const extension = langPath.substring(langPath.lastIndexOf('.')).toLowerCase();
const isJSON = extension === '.json';
const isYAML = extension === '.yaml' || extension === '.yml';
let langObj;
if (isJSON) {
langObj = JSON.parse(fs.readFileSync(langPath, 'utf8'));
} else if (isYAML) {
langObj = yaml.safeLoad(fs.readFileSync(langPath, 'utf8'));
} else {
langObj = eval(fs.readFileSync(langPath, 'utf8'));
}
const fileName = f.replace(process.cwd(), '');
return {
fileName,
path: f,
content: JSON.stringify(langObj)
};
});
}
function extractI18nItemsFromLanguageFiles(languageFiles, missingKeysOptions) {
return languageFiles.reduce((accumulator, file) => {
const language = file.fileName.substring(file.fileName.lastIndexOf('/') + 1, file.fileName.lastIndexOf('.'));
if (!accumulator[language]) {
accumulator[language] = [];
}
const fileContent = JSON.parse(file.content);
const flattenedObject = dot.dot(fileContent);
Object.keys(flattenedObject).forEach((key, index) => {
var _accumulator$language;
(_accumulator$language = accumulator[language]) == null ? void 0 : _accumulator$language.push({
line: index,
path: key,
file: file.fileName,
translated: ((missingKeysOptions.dotNotation ? dot.pick(key, fileContent) : fileContent[key]) || '').trim().length > 0
});
});
return accumulator;
}, {});
}
function sortedJSONStringify(obj, indent = 2) {
function flattenEntries([key, value]) {
return typeof value !== 'object' ? [[key, value]] : [[key, value], ...Object.entries(value).flatMap(flattenEntries)];
}
const allEntries = Object.entries(obj).flatMap(flattenEntries);
const sorted = allEntries.map(entry => entry[0]).sort();
return JSON.stringify(obj, sorted, indent);
}
function writeMissingToLanguage(resolvedLanguageFiles, missingKeys, missingKeysOptions, outputOptions) {
const languageFiles = readLangFiles(resolvedLanguageFiles);
languageFiles.forEach(languageFile => {
const languageFileContent = JSON.parse(languageFile.content);
missingKeys.forEach(item => {
if (item.language && languageFile.fileName.includes(item.language) || !item.language) {
const keyValue = item.language && missingKeysOptions.addValueForLanguages.includes(item.language) ? item.path : '';
if (missingKeysOptions.dotNotation) {
dot.str(item.path, keyValue, languageFileContent);
} else {
languageFileContent[item.path] = keyValue;
}
}
});
const fileExtension = languageFile.fileName.substring(languageFile.fileName.lastIndexOf('.') + 1);
const filePath = path.resolve(process.cwd(), languageFile.fileName);
const stringifiedContent = outputOptions.sortKeys ? sortedJSONStringify(languageFileContent, outputOptions.indentationString) : JSON.stringify(languageFileContent, null, outputOptions.indentationString || 2);
if (fileExtension === 'json') {
fs.writeFileSync(filePath, stringifiedContent);
} else if (fileExtension === 'js') {
const jsFile = `export default ${stringifiedContent}; \n`;
fs.writeFileSync(filePath, jsFile);
} else if (fileExtension === 'yaml' || fileExtension === 'yml') {
const yamlFile = yaml.safeDump(languageFileContent);
fs.writeFileSync(filePath, yamlFile);
}
});
}
function parseLanguageFiles(languageFilesPath, missingKeysOptions) {
const filesList = readLangFiles(languageFilesPath);
return extractI18nItemsFromLanguageFiles(filesList, missingKeysOptions);
}
(function (VueI18NExtractReportTypes) {
VueI18NExtractReportTypes[VueI18NExtractReportTypes["None"] = 0] = "None";
VueI18NExtractReportTypes[VueI18NExtractReportTypes["Missing"] = 1] = "Missing";
VueI18NExtractReportTypes[VueI18NExtractReportTypes["Unused"] = 2] = "Unused";
VueI18NExtractReportTypes[VueI18NExtractReportTypes["Dynamic"] = 4] = "Dynamic";
VueI18NExtractReportTypes[VueI18NExtractReportTypes["All"] = 7] = "All";
})(exports.VueI18NExtractReportTypes || (exports.VueI18NExtractReportTypes = {}));
const mightBeUsedDynamically = function (languageItem, dynamicKeys) {
return dynamicKeys.some(dynamicKey => languageItem.path.includes(dynamicKey.path));
};
function extractI18NReport(parsedVueFiles, parsedLanguageFiles, reportType = exports.VueI18NExtractReportTypes.Missing + exports.VueI18NExtractReportTypes.Unused) {
const missingKeys = [];
const unusedKeys = [];
const dynamicKeys = [];
const dynamicReportEnabled = reportType & exports.VueI18NExtractReportTypes.Dynamic;
Object.keys(parsedLanguageFiles).forEach(language => {
let languageItems = parsedLanguageFiles[language];
parsedVueFiles.forEach(vueItem => {
const usedByVueItem = function (languageItem) {
return languageItem.path === vueItem.path || languageItem.path.startsWith(vueItem.path + '.');
};
if (dynamicReportEnabled && (vueItem.path.includes('${') || vueItem.path.endsWith('.'))) {
dynamicKeys.push(_extends({}, vueItem, {
language
}));
return;
}
if (!parsedLanguageFiles[language].some(usedByVueItem)) {
missingKeys.push(_extends({}, vueItem, {
language
}));
}
languageItems = languageItems.filter(languageItem => dynamicReportEnabled ? !mightBeUsedDynamically(languageItem, dynamicKeys) && !usedByVueItem(languageItem) : !usedByVueItem(languageItem));
});
unusedKeys.push(...languageItems.map(item => _extends({}, item, {
language
})));
});
let extracts = {};
if (reportType & exports.VueI18NExtractReportTypes.Missing) {
extracts = Object.assign(extracts, {
missingKeys
});
}
if (reportType & exports.VueI18NExtractReportTypes.Unused) {
extracts = Object.assign(extracts, {
unusedKeys
});
}
if (dynamicReportEnabled) {
extracts = Object.assign(extracts, {
dynamicKeys
});
}
return extracts;
}
function extractI18NLangFilesSynchronizationReport(parsedLanguageFiles, missingKeysOptions) {
const missingKeys = [];
if (missingKeysOptions.mainLanguageToSyncKeys) {
const otherLanguages = _extends({}, parsedLanguageFiles);
delete otherLanguages[missingKeysOptions.mainLanguageToSyncKeys];
const mainLanguageKeys = parsedLanguageFiles[missingKeysOptions.mainLanguageToSyncKeys] || [];
mainLanguageKeys.forEach(({
path
}) => {
Object.entries(otherLanguages).forEach(([lang, iItems]) => {
if (!iItems.some(iItem => iItem.path === path)) {
missingKeys.push({
path,
language: lang
});
}
});
});
} // detect empty translations for some languages (mainly for English)
Object.entries(parsedLanguageFiles).forEach(([lang, iItems]) => {
if (missingKeysOptions.addValueForLanguages.includes(lang)) {
iItems.filter(({
translated
}) => !translated).forEach(({
path
}) => {
missingKeys.push({
path,
language: lang
});
});
}
});
return {
missingKeys
};
}
async function writeReportToFile(report, writePath) {
const reportString = JSON.stringify(report);
return new Promise((resolve, reject) => {
fs.writeFile(writePath, reportString, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
function createI18NReport(vueFiles, languageFiles, command, missingKeysOptions) {
const resolvedVueFiles = path.resolve(process.cwd(), vueFiles);
const resolvedLanguageFiles = path.resolve(process.cwd(), languageFiles);
const parsedVueFiles = parseVueFiles(resolvedVueFiles);
const parsedLanguageFiles = parseLanguageFiles(resolvedLanguageFiles, missingKeysOptions);
const reportType = command.dynamic ? exports.VueI18NExtractReportTypes.All : exports.VueI18NExtractReportTypes.Missing + exports.VueI18NExtractReportTypes.Unused;
const report = extractI18NReport(parsedVueFiles, parsedLanguageFiles, reportType);
const syncReport = extractI18NLangFilesSynchronizationReport(parsedLanguageFiles, missingKeysOptions); // @ts-ignore
report.missingKeys = [...report.missingKeys, ...syncReport.missingKeys];
return report;
}
function getStatisticsForKeys(keysData = []) {
const result = {
uniqueKeys: {},
keysNumbers: {}
};
result.uniqueKeys = keysData.reduce((accumulator, currentValue) => {
const langStatistics = accumulator[currentValue.language || '?'] = accumulator[currentValue.language || '?'] || {};
langStatistics[currentValue.path] = (langStatistics[currentValue.path] || 0) + 1;
return accumulator;
}, {
'en': {}
});
result.keysNumbers = Object.keys(result.uniqueKeys).reduce((acc, language) => {
acc[language] = Object.keys(result.uniqueKeys[language]).length;
return acc;
}, {});
return result;
}
async function reportCommand(command) {
const {
vueFiles,
languageFiles,
output,
add,
dynamic,
dotNotation,
addValueForLanguages,
mainLanguageToSyncKeys,
jsonSortKeys,
jsonIndentCharacters,
detailedReport
} = command;
const outputOptions = {
sortKeys: jsonSortKeys,
indentationString: ((indentationConfig = '2,space') => {
const charactersMap = {
'space': ' ',
'tab': '\t'
};
const [charactersAmount, character] = indentationConfig.split(',');
return ''.padEnd(Number(charactersAmount), charactersMap[character]);
})(jsonIndentCharacters)
};
const missingKeysOptions = {
dotNotation,
addValueForLanguages: addValueForLanguages instanceof Array ? addValueForLanguages : addValueForLanguages ? [addValueForLanguages] : [],
mainLanguageToSyncKeys
};
const report = createI18NReport(vueFiles, languageFiles, command, missingKeysOptions);
if (detailedReport) {
if (report.missingKeys) console.info('missing keys: '), console.table(report.missingKeys);
if (report.unusedKeys) console.info('unused keys: '), console.table(report.unusedKeys);
if (report.dynamicKeys && dynamic && dynamic > 1) console.info('dynamic detected keys: '), console.table(report.dynamicKeys);
}
if (output) {
await writeReportToFile(report, path.resolve(process.cwd(), output));
console.log(`The report has been has been saved to ${output}`);
}
const summaryReport = _extends({
'Missing keys': getStatisticsForKeys(report.missingKeys).keysNumbers,
'Unused keys': getStatisticsForKeys(report.unusedKeys).keysNumbers
}, dynamic ? {
'Dynamic keys': getStatisticsForKeys(report.dynamicKeys).keysNumbers
} : {});
console.info('\nSummary report:');
console.table(summaryReport);
if (add && report.missingKeys && report.missingKeys.length > 0) {
const resolvedLanguageFiles = path.resolve(process.cwd(), languageFiles);
writeMissingToLanguage(resolvedLanguageFiles, report.missingKeys, missingKeysOptions, outputOptions);
console.log('The missing keys have been added to your languages files');
}
}
var report = {
__proto__: null,
createI18NReport: createI18NReport,
reportCommand: reportCommand,
readVueFiles: readVueFiles,
parseVueFiles: parseVueFiles,
writeMissingToLanguage: writeMissingToLanguage,
parseLanguageFiles: parseLanguageFiles,
get VueI18NExtractReportTypes () { return exports.VueI18NExtractReportTypes; },
extractI18NReport: extractI18NReport,
extractI18NLangFilesSynchronizationReport: extractI18NLangFilesSynchronizationReport,
writeReportToFile: writeReportToFile
};
var index = _extends({}, report);
exports.createI18NReport = createI18NReport;
exports.default = index;
exports.extractI18NLangFilesSynchronizationReport = extractI18NLangFilesSynchronizationReport;
exports.extractI18NReport = extractI18NReport;
exports.parseLanguageFiles = parseLanguageFiles;
exports.parseVueFiles = parseVueFiles;
exports.readVueFiles = readVueFiles;
exports.reportCommand = reportCommand;
exports.writeMissingToLanguage = writeMissingToLanguage;
exports.writeReportToFile = writeReportToFile;
})));
//# sourceMappingURL=vue-i18n-extract.umd.js.map

@ -22,7 +22,9 @@
"test:unit:watchAll": "jest --watchAll",
"serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788",
"concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\"",
"new:store": "quasar new store"
"new:store": "quasar new store",
"i18n:extract": "sh ./bin/vue-i18n-extract/extract.sh",
"i18n:extract-report": "sh ./bin/vue-i18n-extract/extract.sh report"
},
"dependencies": {
"@quasar/extras": "^1.9.10",
@ -48,6 +50,8 @@
"@quasar/quasar-app-extension-testing": "^1.0.0",
"@quasar/quasar-app-extension-testing-unit-jest": "^1.0.1",
"babel-eslint": "^10.0.1",
"commander": "6.2.1",
"dot-object": "2.1.4",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.0",
"eslint-loader": "^3.0.3",
@ -57,6 +61,9 @@
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"generate-password": "^1.5.1",
"glob": "7.1.6",
"is-valid-glob": "1.0.0",
"js-yaml": "3.14.1",
"parseuri": "^0.0.6",
"uuid": "8.3.1",
"vue-wait": "1.4.8"

@ -3377,12 +3377,17 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@6.2.1:
version "6.2.1"
resolved "https://npm-registry.sipwise.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.6.0, commander@^2.8.1:
version "2.20.3"
resolved "https://npm-registry.sipwise.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commander@^4.1.1:
commander@^4.0.0, commander@^4.1.1:
version "4.1.1"
resolved "https://npm-registry.sipwise.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
@ -4338,6 +4343,14 @@ dot-case@^3.0.3:
no-case "^3.0.3"
tslib "^1.10.0"
dot-object@2.1.4:
version "2.1.4"
resolved "https://npm-registry.sipwise.com/dot-object/-/dot-object-2.1.4.tgz#c6c54e9fca510b4d0ea4d65acf33726963843b5f"
integrity sha512-7FXnyyCLFawNYJ+NhkqyP9Wd2yzuo+7n9pGiYpkmXCTYa8Ci2U0eUNDVg5OuO5Pm6aFXI2SWN8/N/w7SJWu1WA==
dependencies:
commander "^4.0.0"
glob "^7.1.5"
dot-prop@5.3.0:
version "5.3.0"
resolved "https://npm-registry.sipwise.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@ -5570,7 +5583,7 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0:
dependencies:
is-glob "^4.0.1"
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1:
glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.5, glob@^7.1.6, glob@~7.1.1:
version "7.1.6"
resolved "https://npm-registry.sipwise.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -6624,6 +6637,11 @@ is-utf8@^0.2.0, is-utf8@~0.2.0:
resolved "https://npm-registry.sipwise.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
is-valid-glob@1.0.0:
version "1.0.0"
resolved "https://npm-registry.sipwise.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=
is-whitespace@^0.3.0:
version "0.3.0"
resolved "https://npm-registry.sipwise.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f"
@ -7164,6 +7182,14 @@ js-tokens@^3.0.2:
resolved "https://npm-registry.sipwise.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
js-yaml@3.14.1:
version "3.14.1"
resolved "https://npm-registry.sipwise.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^3.13.1, js-yaml@^3.8.1:
version "3.14.0"
resolved "https://npm-registry.sipwise.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"

Loading…
Cancel
Save