diff --git a/.env.example b/.env.example index 0c6a33dd1..2ded1a66a 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ -GITHUB_TOKEN= \ No newline at end of file +GITHUB_TOKEN= +CROWDIN_TOKEN= +CROWDIN_PROJECT_ID= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5db0a79a4..4d59f4fcf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,13 @@ node_modules .DS_Store .env .vscode/settings.json +##### +blog/ build/ content/ docs/ -blog/ \ No newline at end of file +# Temp folders used during pre-build +temp-docs/ +temp-i18n/ +i18n/ +!i18n/en-US/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 73f7bc384..075a02db0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Current file", + "program": "${file}", + "request": "launch", + "cwd": "${workspaceFolder}", + "skipFiles": ["/**"], + "type": "pwa-node" + }, { "type": "node", "name": "vscode-jest-tests", diff --git a/docusaurus.config.js b/docusaurus.config.js index 5095f98b5..f4a5a4de1 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -12,6 +12,10 @@ module.exports = { favicon: 'img/favicon.ico', organizationName: 'electron', projectName: 'electron', + i18n: { + defaultLocale: 'en', + locales: ['en', 'es-ES'] + }, themeConfig: { announcementBar: { id: 'to_old_docs', @@ -45,6 +49,10 @@ module.exports = { position: 'left', activeBaseRegex: '^\b$' // never active }, + { + type: 'localeDropdown', + position: 'right', + }, { href: 'https://github.com/electron/electron', label: 'GitHub', @@ -99,7 +107,7 @@ module.exports = { ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`, + copyright: `Copyright © OpenJS Foundation and Electron contributors.`, }, algolia: { apiKey: 'f9fb1d51a99fc479d5979cfa2aae48b9', diff --git a/i18n/en-US/docusaurus-theme-classic/footer.json b/i18n/en-US/docusaurus-theme-classic/footer.json index 9956ea729..c575e76a3 100644 --- a/i18n/en-US/docusaurus-theme-classic/footer.json +++ b/i18n/en-US/docusaurus-theme-classic/footer.json @@ -40,7 +40,7 @@ "description": "The label of footer link with label=GitHub linking to https://github.com/electron/electron" }, "copyright": { - "message": "Copyright © 2021 My Project, Inc. Built with Docusaurus.", + "message": "Copyright © OpenJS Foundation and Electron contributors.", "description": "The footer copyright" } } \ No newline at end of file diff --git a/package.json b/package.json index bed1630c4..48ab52e25 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "devDependencies": { "@actions/core": "^1.2.7", "@actions/github": "^4.0.0", + "@crowdin/crowdin-api-client": "^1.11.1", "@types/jest": "^26.0.23", "@types/unist": "^2.0.3", "del": "^6.0.0", @@ -62,7 +63,8 @@ "make-dir": "^3.1.0", "prettier": "^2.2.1", "tar-stream": "^2.2.0", - "unist-util-visit-parents": "^3.1.1" + "unist-util-visit-parents": "^3.1.1", + "unzipper": "^0.10.11" }, "sha": "0cd80cd424495b9efd244ae7d57f980ad43780ff" } diff --git a/scripts/pre-build.js b/scripts/pre-build.js index 62ae03d34..12417bdc3 100644 --- a/scripts/pre-build.js +++ b/scripts/pre-build.js @@ -7,34 +7,102 @@ */ const path = require('path'); const { existsSync } = require('fs'); +const fs = require('fs-extra'); -const del = require('del'); const latestVersion = require('latest-version'); -const { copy, download } = require('./tasks/download-docs'); +const { cleanProject } = require('./tasks/clean-project'); +const { download } = require('./tasks/download-docs'); +const { copy, copyStaticAssets } = require('./tasks/reorg-docs'); const { addFrontmatter } = require('./tasks/add-frontmatter'); const { createSidebar } = require('./tasks/create-sidebar'); const { fixContent } = require('./tasks/md-fixers'); const { copyNewContent } = require('./tasks/copy-new-content'); +const { downloadTranslations } = require('./tasks/download-translations'); + const { sha } = require('../package.json'); const DOCS_FOLDER = 'docs'; +const DOCS_TEMP_FOLDER = 'temp-docs'; +const TEMP_I18N_FOLDER = 'temp-i18n'; +const I18N_FOLDER = 'i18n'; + +const CROWDIN_CONTENT_PATH = path.join( + TEMP_I18N_FOLDER, + '[electron.i18n] master/content' +); // const BLOG_FOLDER = 'blog'; +/** + * Performs all the required transformations and fixes for the given path. + * @param {string} localeTarget The root where or the markdown docs are. For the + * original content it should be `docs/`, and + * `i18n/%locale%/docusaurus-plugin-content-docs/current/` for the localized one. + */ +const processLocale = async (localeTarget) => { + console.log('Copying new content'); + await copyNewContent(localeTarget); + + console.log('Fixing markdown'); + await fixContent(localeTarget); + + console.log('Adding automatic frontmatter'); + await addFrontmatter(localeTarget); +}; + +/** + * Updates the navbar for the original content and the localized one. + * @param {string} localeTarget + */ +const processNavigations = async (localeTarget) => { + console.log('Updating sidebar.js'); + if (!localeTarget.includes(I18N_FOLDER)) { + await createSidebar(localeTarget, path.join(process.cwd(), 'sidebars.js')); + } +}; + +/** + * Downloads the translations from Crowdin and makes sure all the files + * are in the right place or generated if needed. + */ +const processLocales = async () => { + console.log('Downloading latest translations'); + await downloadTranslations(TEMP_I18N_FOLDER); + + // The contents are the locales folders names, i.e.: `de-DE`, `es-ES`, etc. + const locales = await fs.readdir(CROWDIN_CONTENT_PATH); + + for (const locale of locales) { + const source = path.join(CROWDIN_CONTENT_PATH, locale); + const localeTarget = path.join('i18n', locale); + const docsTarget = path.join( + localeTarget, + 'docusaurus-plugin-content-docs', + 'current' + ); + console.log(`Copying contents from "${source}" to "${docsTarget}"`); + await copy(source, docsTarget, 'docs'); + await copyStaticAssets(DOCS_FOLDER, docsTarget); + // The non-markdown content is under the `website/i18n` folder in crowdin + // It keeps the right structure so it just needs to be copy over to the locale folder + await fs.copy(path.join(source, 'website', 'i18n'), localeTarget); + + await processLocale(docsTarget); + await processNavigations(localeTarget); + } +}; + /** * * @param {string} source */ const start = async (source) => { - console.log(`Deleting previous content`); - await del(DOCS_FOLDER); + console.log(`Cleaning project`); + await cleanProject(path.join(process.cwd(), '.gitignore')); const localElectron = source && (source.includes('/') || source.includes('\\')); - // TODO: Uncomment once we have the blog up and running - // await del(BLOG_FOLDER); - if (!localElectron) { console.log(`Detecting latest Electron version`); const version = await latestVersion('electron'); @@ -50,19 +118,29 @@ const start = async (source) => { target, org: process.env.ORG || 'electron', repository: 'electron', - destination: DOCS_FOLDER, downloadMatch: 'docs', + destination: DOCS_TEMP_FOLDER, }); + await copy(DOCS_TEMP_FOLDER, DOCS_FOLDER); } else if (existsSync(source)) { - await copy({ - target: source, - destination: DOCS_FOLDER, - downloadMatch: 'docs', - }); + await copy(source, DOCS_FOLDER, 'docs'); } else { console.error(`Path ${localElectron} does not exist`); return process.exit(-1); } + await processLocale(DOCS_FOLDER); + await processNavigations(DOCS_FOLDER); + + // No need to download translations if the source content is local + if (localElectron) { + return; + } + + if(process.env.CROWDIN_TOKEN && process.env.CROWDIN_PROJECT_ID) { + await processLocales(); + }else{ + console.log(`CROWDIN_TOKEN and/or CROWDIN_PROJECT_ID are missing. Skipping downloading translations.`); + } // TODO: Uncoment once we have the blog enabled // console.log(`Downloading posts`); @@ -72,18 +150,6 @@ const start = async (source) => { // destination: BLOG_FOLDER, // downloadMatch: 'data/blog', // }); - - console.log('Copying new content'); - await copyNewContent(DOCS_FOLDER); - - console.log('Fixing markdown'); - await fixContent(DOCS_FOLDER); - - console.log('Adding automatic frontmatter'); - await addFrontmatter(DOCS_FOLDER); - - console.log('Updating sidebar.js'); - await createSidebar(DOCS_FOLDER, path.join(process.cwd(), 'sidebars.js')); }; start(process.argv[2]); diff --git a/scripts/tasks/clean-project.js b/scripts/tasks/clean-project.js new file mode 100644 index 000000000..2d9da0375 --- /dev/null +++ b/scripts/tasks/clean-project.js @@ -0,0 +1,44 @@ +//@ts-check +const fs = require('fs').promises; +const { join } = require('path'); +const del = require('del'); + +const splitPattern = `#####`; + +/** + * Gets the patterns to use from `contents`: + * * Contents after the `splitPattern` + * * No comment lines + * * No empty lines + * + * @param {string} contents + */ +const getPatterns = (contents) => { + const [, toDelete] = contents.split(splitPattern); + + return toDelete + .trim() + .split('\n') + .filter((line) => !line.startsWith('#')) + .map((line) => line.trim()); +}; + +/** + * Deletes all the patterns specified in the provided .gitignore + * after the pattern `#####` + * @param {string} gitignorePath + */ +const cleanProject = async (gitignorePath) => { + const gitignore = await fs.readFile(gitignorePath, 'utf-8'); + const toDelete = getPatterns(gitignore); + + const files = await del(toDelete, { dryRun: true }); +}; + +if (require.main === module) { + cleanProject(join(process.cwd(), '.gitignore')); +} + +module.exports = { + cleanProject, +}; diff --git a/scripts/tasks/docs-reorg.json b/scripts/tasks/docs-reorg.json deleted file mode 100644 index 57fb500db..000000000 --- a/scripts/tasks/docs-reorg.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "docs/experimental.md": "docs/api/experimental.md", - "docs/faq.md": "docs/resources/faq.md", - "docs/glossary.md": "docs/resources/glossary.md", - "docs/README.md": "", - "docs/styleguide.md": "", - "docs/development/README.md": "docs/contributing/README.md", - "docs/development/accessibility.md": "docs/testing-and-debugging/accessibility.md", - "docs/development/azure-vm-setup.md": "docs/contributing/azure-vm-setup.md", - "docs/development/build-instructions-gn.md": "docs/contributing/build-instructions-gn.md", - "docs/development/build-instructions-linux.md": "docs/contributing/build-instructions-linux.md", - "docs/development/build-instructions-macos.md": "docs/contributing/build-instructions-macos.md", - "docs/development/build-instructions-windows.md": "docs/contributing/build-instructions-windows.md", - "docs/development/build-system-overview.md": "docs/contributing/build-system-overview.md", - "docs/development/chromium-development.md": "docs/contributing/chromium-development.md", - "docs/development/clang-format.md": "docs/contributing/clang-format.md", - "docs/development/clang-tidy.md": "docs/contributing/clang-tidy.md", - "docs/development/coding-style.md": "docs/contributing/coding-style.md", - "docs/development/debug-instructions-windows.md": "docs/contributing/debug-instructions-windows.md", - "docs/development/debugging-instructions-macos-xcode.md": "docs/contributing/debugging-instructions-macos-xcode.md", - "docs/development/debugging-instructions-macos.md": "docs/contributing/debugging-instructions-macos.md", - "docs/development/electron-vs-nwjs.md": "docs/internals/electron-vs-nwjs.md", - "docs/development/goma.md": "docs/contributing/goma.md", - "docs/development/issues.md": "docs/contributing/issues.md", - "docs/development/patches.md": "docs/contributing/patches.md", - "docs/development/pull-requests.md": "docs/contributing/pull-requests.md", - "docs/development/setting-up-symbol-server.md": "docs/contributing/setting-up-symbol-server.md", - "docs/development/source-code-directory-structure.md": "docs/contributing/source-code-directory-structure.md", - "docs/development/testing.md": "docs/contributing/testing.md", - "docs/development/using-native-node-modules.md": "docs/how-to/using-native-node-modules.md", - "docs/development/v8-development.md": "docs/contributing/v8-development.md", - "docs/tutorial/accessibility.md": "docs/development/accessibility.md", - "docs/tutorial/using-native-node-modules.md": "docs/development/using-native-node-modules.md", - "docs/tutorial/application-distribution.md": "docs/distribution/application-distribution.md", - "docs/tutorial/code-signing.md": "docs/distribution/code-signing.md", - "docs/tutorial/mac-app-store-submission-guide.md": "docs/distribution/mac-app-store-submission-guide.md", - "docs/tutorial/snapcraft.md": "docs/distribution/snapcraft.md", - "docs/tutorial/windows-store-guide.md": "docs/distribution/windows-store-guide.md", - "docs/tutorial/introduction.md": "docs/get-started/introduction.md", - "docs/tutorial/installation.md": "docs/get-started/installation.md", - "docs/tutorial/process-model.md": "docs/get-started/process-model.md", - "docs/tutorial/quick-start.md": "docs/get-started/quick-start.md", - "docs/tutorial/dark-mode.md": "docs/how-to/dark-mode.md", - "docs/tutorial/desktop-environment-integration.md": "docs/how-to/desktop-environment-integration.md", - "docs/tutorial/fuses.md": "docs/how-to/fuses.md", - "docs/tutorial/in-app-purchases.md": "docs/how-to/in-app-purchases.md", - "docs/tutorial/keyboard-shortcuts.md": "docs/how-to/keyboard-shortcuts.md", - "docs/tutorial/linux-desktop-actions.md": "docs/how-to/linux-desktop-actions.md", - "docs/tutorial/macos-dock.md": "docs/how-to/macos-dock.md", - "docs/tutorial/multithreading.md": "docs/how-to/multithreading.md", - "docs/tutorial/native-file-drag-drop.md": "docs/how-to/native-file-drag-drop.md", - "docs/tutorial/notifications.md": "docs/how-to/notifications.md", - "docs/tutorial/offscreen-rendering.md": "docs/how-to/offscreen-rendering.md", - "docs/tutorial/online-offline-events.md": "docs/how-to/online-offline-events.md", - "docs/tutorial/progress-bar.md": "docs/how-to/progress-bar.md", - "docs/tutorial/recent-documents.md": "docs/how-to/recent-documents.md", - "docs/tutorial/repl.md": "docs/how-to/repl.md", - "docs/tutorial/represented-file.md": "docs/how-to/represented-file.md", - "docs/tutorial/spellchecker.md": "docs/how-to/spellchecker.md", - "docs/tutorial/using-pepper-flash-plugin.md": "docs/how-to/using-pepper-flash-plugin.md", - "docs/tutorial/web-embeds.md": "docs/how-to/web-embeds.md", - "docs/tutorial/windows-arm.md": "docs/how-to/windows-arm.md", - "docs/tutorial/windows-taskbar.md": "docs/how-to/windows-taskbar.md", - "docs/tutorial/message-ports.md": "docs/performance/message-ports.md", - "docs/tutorial/performance.md": "docs/performance/performance.md", - "docs/tutorial/boilerplates-and-clis.md": "docs/resources/boilerplates-and-clis.md", - "docs/breaking-changes.md": "docs/resources/breaking-changes.md", - "docs/tutorial/electron-timelines.md": "docs/resources/electron-timelines.md", - "docs/tutorial/electron-versioning.md": "docs/resources/electron-versioning.md", - "docs/tutorial/support.md": "docs/resources/support.md", - "docs/tutorial/context-isolation.md": "docs/security/context-isolation.md", - "docs/tutorial/security.md": "docs/security/security.md", - "docs/tutorial/application-debugging.md": "docs/testing-and-debugging/application-debugging.md", - "docs/tutorial/automated-testing-with-a-custom-driver.md": "docs/testing-and-debugging/automated-testing-with-a-custom-driver.md", - "docs/tutorial/debugging-main-process.md": "docs/testing-and-debugging/debugging-main-process.md", - "docs/tutorial/debugging-vscode.md": "docs/testing-and-debugging/debugging-vscode.md", - "docs/tutorial/devtools-extension.md": "docs/testing-and-debugging/devtools-extension.md", - "docs/tutorial/testing-on-headless-ci.md": "docs/testing-and-debugging/testing-on-headless-ci.md", - "docs/tutorial/testing-widevine-cdm.md": "docs/testing-and-debugging/testing-widevine-cdm.md", - "docs/tutorial/updates.md": "docs/how-to/updates.md", - "docs/tutorial/using-selenium-and-webdriver.md": "docs/testing-and-debugging/using-selenium-and-webdriver.md" -} diff --git a/scripts/tasks/download-docs.js b/scripts/tasks/download-docs.js index fddfbc842..db1f30850 100644 --- a/scripts/tasks/download-docs.js +++ b/scripts/tasks/download-docs.js @@ -5,17 +5,13 @@ const path = require('path'); const makeDir = require('make-dir'); const tar = require('tar-stream'); const got = require('got'); -const globby = require('globby'); - -const pathRewrites = require('./docs-reorg.json'); -const fixedFolders = ['api', 'images', 'fiddles']; /** * @typedef DownloadOptions * @type {object} * @property {string} [org] - The organization to download the contents from * @property {string} [repository] - The repository to download the contents from - * @property {string} destination - The destination absolute path. + * @property {string} destination - Where the files should be saved * @property {string} target - The branch, commit, version. (e.g. `v1.0.0`, `main`) * @property {string} downloadMatch - The math to use to filter the downloaded contents */ @@ -27,73 +23,10 @@ const fixedFolders = ['api', 'images', 'fiddles']; * @property {Buffer} content */ -/** - * Checks if the given folder is one of those that do not have to be - * modified - * @param {string} folder - * @returns - */ -const isFixedFolder = (folder) => { - for (const fixedFolder of fixedFolders) { - if (folder.includes(`/${fixedFolder}`)) { - return true; - } - } - return false; -}; - -/** - * Returns the right folder where the given document needs to be place - * taking into consideration: - * 1. If it's a "fixed" folder (api, images, fiddles) - * 1. It has an entry in `docs-reorg.json` - * 1. Using the default or puts it the default folder ('how-to') - * @param {string} destination - * @param {string} filename - */ -const getFinalPath = (destination, filename) => { - const relativePath = path.join(destination, filename); - - let finalPath = ''; - - if (isFixedFolder(relativePath)) { - finalPath = relativePath; - } else if (pathRewrites[relativePath] === '') { - return ''; - } else if (pathRewrites[relativePath]) { - finalPath = pathRewrites[relativePath]; - } else { - const basename = path.basename(filename); - finalPath = path.join(destination, 'how-to', basename); - } - - return path.join(process.cwd(), finalPath); -}; - -/** - * Saves the file on disk creating the necessary folders - * @param {Entry[]} files - * @param {string} destination - */ -const saveContents = async (files, destination) => { - for (const file of files) { - const { content, filename } = file; - const finalPath = getFinalPath(destination, filename); - - // These are files we do not need to copy - if (finalPath === '') { - continue; - } - - await makeDir(path.dirname(finalPath)); - - await fs.writeFile(finalPath, content); - } -}; - /** * Downloads the contents of a branch or release from GitHub * @param {DownloadOptions} options + * @returns {Promise} */ const downloadFromGitHub = async (options) => { const { org, repository, target, downloadMatch = '' } = options; @@ -152,38 +85,15 @@ const download = async (userOptions) => { ...userOptions, }; - const contents = await downloadFromGitHub(options); - - await saveContents(contents, userOptions.destination); -}; - -/** - * Copies the contents of the given folder to the destination, - * filtering by path, and reorganizing the folder structure - * as needed. - * @param {DownloadOptions} userOptions - */ -const copy = async ({ target, destination, downloadMatch = '.' }) => { - const filesPaths = await globby(`${downloadMatch}/**/*`, { - cwd: target, - }); - - const contents = []; - - for (const filePath of filesPaths) { - const content = { - filename: filePath.replace(`${downloadMatch}/`, ''), - content: await fs.readFile(path.join(target, filePath)), - slug: path.basename(filePath, '.md'), - }; + const files = await downloadFromGitHub(options); - contents.push(content); + for (const file of files) { + const destination = path.join(options.destination, file.filename); + await makeDir(path.dirname(destination)); + await fs.writeFile(destination, file.content); } - - await saveContents(contents, destination); }; module.exports = { download, - copy, }; diff --git a/scripts/tasks/download-translations.js b/scripts/tasks/download-translations.js new file mode 100644 index 000000000..11dc09dcb --- /dev/null +++ b/scripts/tasks/download-translations.js @@ -0,0 +1,113 @@ +//@ts-check +const stream = require('stream'); +const { join } = require('path'); +const { promisify } = require('util'); +const pipeline = promisify(stream.pipeline); +const got = require('got').default; +const unzipper = require('unzipper'); + +// Assumes running from the root of the repo +const OUTPUT_PATH = join(process.cwd(), 'temp-i18n'); +const { CROWDIN_TOKEN, CROWDIN_PROJECT_ID } = process.env; +const PROJECT_ID = parseInt(CROWDIN_PROJECT_ID); +const crowdin = require('@crowdin/crowdin-api-client').default; + +const { translationsApi } = new crowdin({ + token: CROWDIN_TOKEN, +}); + +/** + * Downloads the Crowdin file and unzips the contents + * @param {string} url + * @param {string} destination + * @returns {Promise} + */ +const downloadFiles = async (url, destination) => { + await pipeline(got.stream(url), unzipper.Extract({ path: destination })); +}; + +/** + * Waits for the given number of seconds + * @param {number} seconds + * @returns + */ +const waitFor = (seconds) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, seconds * 1000); + }); +}; + +/** + * @param {number} buildId + * @returns + */ +const getBuild = async (buildId) => { + const builds = await translationsApi.listProjectBuilds(PROJECT_ID); + const build = builds.data.find((item) => item.data.id === buildId); + + return build.data; +}; + +/** + * Kicks a build for `PROJECT_ID` and returns the download link once it + * has finished. + * If a build is not kicked we risked downloading outdated files. + */ +const getDownloadLink = async () => { + const { + data: { id: buildId }, + } = await translationsApi.buildProject(PROJECT_ID); + + let counter = 10; + let interval = 30; + let build; + + for (let i = 0; i < counter; i++) { + build = await getBuild(buildId); + console.log(`Crowdin status: Project ${buildId} - ${build.status}`); + + if (build.status === 'finished') { + break; + } else { + console.log( + `Crowdin status: Waiting ${interval} seconds (retry ${i}/${counter})` + ); + await waitFor(interval); + } + } + + if (build.status !== 'finished') { + throw new Error(`The project didn't build fast enough on Crowdin`); + } + + const { + data: { url }, + } = await translationsApi.downloadTranslations(PROJECT_ID, buildId); + + return url; +}; + +/** + * Downloads the translations into the given target + * or the default one otherwise. + * @param {string} [target] + */ +const downloadTranslations = async (target) => { + const downloadLink = await getDownloadLink(); + const destination = target || OUTPUT_PATH; + await downloadFiles(downloadLink, destination); +}; + +// When a file is run directly from Node.js, `require.main` is set to its module. +// That means that it is possible to determine whether a file has been run directly +// by testing `require.main === module`. +// https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module +if (require.main === module) { + downloadTranslations(); +} + +module.exports = { + downloadTranslations, +}; diff --git a/scripts/tasks/md-fixers.js b/scripts/tasks/md-fixers.js index 3d39695be..daebffaf6 100644 --- a/scripts/tasks/md-fixers.js +++ b/scripts/tasks/md-fixers.js @@ -4,38 +4,6 @@ const fs = require('fs').promises; const path = require('path'); const globby = require('globby'); -/** The keywords that need to be escaped so MDX does not complain */ -const keywords = new Set([ - 'any', - 'Boolean', - 'Buffer', - 'Extension', - 'ExtensionInfo', - 'Integer', - 'local', - 'NativeImage', - // TODO: Normalize (nN)umber in the docs - 'number', - 'Number', - 'Object', - 'port', - 'Product', - 'proxyHost', - 'proxyPort', - 'proxyScheme', - 'proxyURL', - 'proxyURIList', - 'ServiceWorkerInfo', - // TODO: Normalize (sS)tring in the docs - 'string', - 'String', - 'Uint8Array', - 'unknown', - 'urlScheme', - 'void', - 'webview', -]); - /** * RegExp used to match the details of the arguments of a function * in the documention and used in `apiTransformer`. It matches: @@ -99,6 +67,19 @@ const fiddleTransformer = (line) => { return `\`\`\`fiddle ${matches[1]}`; }; +/** + * Crowdin translations put markdown content right + * after HTML comments and thus breaking Docusaurus + * parse engine. We need to add a new EOL after `-->` + * is found. + */ +const newLineOnHTMLComment = (line) => { + if (line.includes('-->')) { + return line.replace('-->', '-->\n'); + } + return line; +}; + /** * Applies any transformation that can be executed line by line on * the document to make sure it is ready to be consumed by @@ -110,7 +91,11 @@ const fiddleTransformer = (line) => { const transform = (doc) => { const lines = doc.split('\n'); const newDoc = []; - const transformers = [apiTransformer, fiddleTransformer]; + const transformers = [ + apiTransformer, + fiddleTransformer, + newLineOnHTMLComment, + ]; for (const line of lines) { const newLine = transformers.reduce((newLine, transformer) => { diff --git a/scripts/tasks/reorg-docs.js b/scripts/tasks/reorg-docs.js new file mode 100644 index 000000000..8b35daeca --- /dev/null +++ b/scripts/tasks/reorg-docs.js @@ -0,0 +1,136 @@ +//@ts-check +const fs = require('fs-extra').promises; +const { existsSync } = require('fs-extra'); +const path = require('path'); + +const makeDir = require('make-dir'); +const globby = require('globby'); + +const pathRewrites = require('./reorg-docs.json'); +const fixedFolders = ['api', 'images', 'fiddles']; + +/** + * @typedef Entry + * @property {string} filename + * @property {string} slug + * @property {Buffer} content + */ + +/** + * Checks if the given folder is one of those that do not have to be + * modified + * @param {string} folder + * @returns + */ +const isFixedFolder = (folder) => { + for (const fixedFolder of fixedFolders) { + if (folder.includes(`${fixedFolder}/`)) { + return true; + } + } + return false; +}; + +/** + * Returns the right folder where the given document needs to be place + * taking into consideration: + * 1. If it's a "fixed" folder (api, images, fiddles) + * 1. It has an entry in `docs-reorg.json` + * 1. Using the default or puts it the default folder ('how-to') + * @param {string} destination + * @param {string} filename + */ +const getFinalPath = (destination, filename) => { + let finalPath = ''; + + if (isFixedFolder(filename)) { + finalPath = path.join(destination, filename); + } else if (pathRewrites[filename] === '') { + return ''; + } else if (pathRewrites[filename]) { + finalPath = path.join(destination, pathRewrites[filename]); + } else { + const basename = path.basename(filename); + finalPath = path.join(destination, 'how-to', basename); + } + + return path.join(process.cwd(), finalPath); +}; + +/** + * Saves the file on disk creating the necessary folders + * @param {Entry[]} files + * @param {string} destination + */ +const saveContents = async (files, destination) => { + for (const file of files) { + const { content, filename } = file; + const finalPath = getFinalPath(destination, filename); + + // These are files we do not need to copy + if (finalPath === '') { + continue; + } + + await makeDir(path.dirname(finalPath)); + + await fs.writeFile(finalPath, content); + } +}; + +/** + * Copies the contents of the given folder to the destination, + * filtering by path, and reorganizing the folder structure + * as needed. + * @param {string} root Where to start looking for the files + * @param {string} destination Where the files need to be copied to + * @param {string} rootPath The path under root to search for files + */ +const copy = async (root, destination, rootPath = '.') => { + const filesPaths = await globby(`${rootPath}/**/*`, { + cwd: root, + }); + + const contents = []; + + for (const filePath of filesPaths) { + const content = { + filename: filePath.replace(`${rootPath}/`, ''), + content: await fs.readFile(path.join(root, filePath)), + slug: path.basename(filePath, '.md'), + }; + + contents.push(content); + } + + await saveContents(contents, destination); +}; + +/** + * The translations do not have the images or fiddles. This method + * is used to copy the "fixed folders" from the source to the destination + * if they do not exist + * @param {string} source + * @param {string} destination + */ +const copyStaticAssets = async (source, destination) => { + for (const fixedFolder of fixedFolders) { + const files = await globby(`${fixedFolder}/**/*`, { cwd: source }); + + for (const file of files) { + const finalDestination = path.join(destination, file); + if (!existsSync(finalDestination)) { + await makeDir(path.dirname(finalDestination)); + await fs.copyFile( + path.join(source, file), + path.join(destination, file) + ); + } + } + } +}; + +module.exports = { + copy, + copyStaticAssets, +}; diff --git a/scripts/tasks/reorg-docs.json b/scripts/tasks/reorg-docs.json new file mode 100644 index 000000000..8ffe3427f --- /dev/null +++ b/scripts/tasks/reorg-docs.json @@ -0,0 +1,82 @@ +{ + "experimental.md": "api/experimental.md", + "faq.md": "resources/faq.md", + "glossary.md": "resources/glossary.md", + "README.md": "", + "styleguide.md": "", + "development/README.md": "contributing/README.md", + "development/accessibility.md": "testing-and-debugging/accessibility.md", + "development/azure-vm-setup.md": "contributing/azure-vm-setup.md", + "development/build-instructions-gn.md": "contributing/build-instructions-gn.md", + "development/build-instructions-linux.md": "contributing/build-instructions-linux.md", + "development/build-instructions-macos.md": "contributing/build-instructions-macos.md", + "development/build-instructions-windows.md": "contributing/build-instructions-windows.md", + "development/build-system-overview.md": "contributing/build-system-overview.md", + "development/chromium-development.md": "contributing/chromium-development.md", + "development/clang-format.md": "contributing/clang-format.md", + "development/clang-tidy.md": "contributing/clang-tidy.md", + "development/coding-style.md": "contributing/coding-style.md", + "development/debug-instructions-windows.md": "contributing/debug-instructions-windows.md", + "development/debugging-instructions-macos-xcode.md": "contributing/debugging-instructions-macos-xcode.md", + "development/debugging-instructions-macos.md": "contributing/debugging-instructions-macos.md", + "development/electron-vs-nwjs.md": "internals/electron-vs-nwjs.md", + "development/goma.md": "contributing/goma.md", + "development/issues.md": "contributing/issues.md", + "development/patches.md": "contributing/patches.md", + "development/pull-requests.md": "contributing/pull-requests.md", + "development/setting-up-symbol-server.md": "contributing/setting-up-symbol-server.md", + "development/source-code-directory-structure.md": "contributing/source-code-directory-structure.md", + "development/testing.md": "contributing/testing.md", + "development/using-native-node-modules.md": "how-to/using-native-node-modules.md", + "development/v8-development.md": "contributing/v8-development.md", + "tutorial/accessibility.md": "development/accessibility.md", + "tutorial/using-native-node-modules.md": "development/using-native-node-modules.md", + "tutorial/application-distribution.md": "distribution/application-distribution.md", + "tutorial/code-signing.md": "distribution/code-signing.md", + "tutorial/mac-app-store-submission-guide.md": "distribution/mac-app-store-submission-guide.md", + "tutorial/snapcraft.md": "distribution/snapcraft.md", + "tutorial/windows-store-guide.md": "distribution/windows-store-guide.md", + "tutorial/introduction.md": "get-started/introduction.md", + "tutorial/installation.md": "get-started/installation.md", + "tutorial/process-model.md": "get-started/process-model.md", + "tutorial/quick-start.md": "get-started/quick-start.md", + "tutorial/dark-mode.md": "how-to/dark-mode.md", + "tutorial/desktop-environment-integration.md": "how-to/desktop-environment-integration.md", + "tutorial/fuses.md": "how-to/fuses.md", + "tutorial/in-app-purchases.md": "how-to/in-app-purchases.md", + "tutorial/keyboard-shortcuts.md": "how-to/keyboard-shortcuts.md", + "tutorial/linux-desktop-actions.md": "how-to/linux-desktop-actions.md", + "tutorial/macos-dock.md": "how-to/macos-dock.md", + "tutorial/multithreading.md": "how-to/multithreading.md", + "tutorial/native-file-drag-drop.md": "how-to/native-file-drag-drop.md", + "tutorial/notifications.md": "how-to/notifications.md", + "tutorial/offscreen-rendering.md": "how-to/offscreen-rendering.md", + "tutorial/online-offline-events.md": "how-to/online-offline-events.md", + "tutorial/progress-bar.md": "how-to/progress-bar.md", + "tutorial/recent-documents.md": "how-to/recent-documents.md", + "tutorial/repl.md": "how-to/repl.md", + "tutorial/represented-file.md": "how-to/represented-file.md", + "tutorial/spellchecker.md": "how-to/spellchecker.md", + "tutorial/using-pepper-flash-plugin.md": "how-to/using-pepper-flash-plugin.md", + "tutorial/web-embeds.md": "how-to/web-embeds.md", + "tutorial/windows-arm.md": "how-to/windows-arm.md", + "tutorial/windows-taskbar.md": "how-to/windows-taskbar.md", + "tutorial/message-ports.md": "performance/message-ports.md", + "tutorial/performance.md": "performance/performance.md", + "tutorial/boilerplates-and-clis.md": "resources/boilerplates-and-clis.md", + "breaking-changes.md": "resources/breaking-changes.md", + "tutorial/electron-timelines.md": "resources/electron-timelines.md", + "tutorial/electron-versioning.md": "resources/electron-versioning.md", + "tutorial/support.md": "resources/support.md", + "tutorial/context-isolation.md": "security/context-isolation.md", + "tutorial/security.md": "security/security.md", + "tutorial/application-debugging.md": "testing-and-debugging/application-debugging.md", + "tutorial/automated-testing-with-a-custom-driver.md": "testing-and-debugging/automated-testing-with-a-custom-driver.md", + "tutorial/debugging-main-process.md": "testing-and-debugging/debugging-main-process.md", + "tutorial/debugging-vscode.md": "testing-and-debugging/debugging-vscode.md", + "tutorial/devtools-extension.md": "testing-and-debugging/devtools-extension.md", + "tutorial/testing-on-headless-ci.md": "testing-and-debugging/testing-on-headless-ci.md", + "tutorial/testing-widevine-cdm.md": "testing-and-debugging/testing-widevine-cdm.md", + "tutorial/updates.md": "how-to/updates.md", + "tutorial/using-selenium-and-webdriver.md": "testing-and-debugging/using-selenium-and-webdriver.md" +} diff --git a/scripts/utils/save-json.js b/scripts/utils/save-json.js new file mode 100644 index 000000000..a1d16a58b --- /dev/null +++ b/scripts/utils/save-json.js @@ -0,0 +1,15 @@ +//@ts-check + +const { writeFile } = require('fs').promises; +const { dirname } = require('path'); +const makeDir = require('make-dir'); + +const saveJSON = async (filename, contents) => { + await makeDir(dirname(filename)); + + await writeFile(filename, `${JSON.stringify(contents, null, 2)}\n`); +}; + +module.exports = { + saveJSON, +}; diff --git a/yarn.lock b/yarn.lock index 745d23dba..7ca2df3e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1310,6 +1310,13 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@crowdin/crowdin-api-client@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@crowdin/crowdin-api-client/-/crowdin-api-client-1.11.1.tgz#91d833ff0133f41ebfb6d38a435375757714bb86" + integrity sha512-10P3dG3leFD30FSPF+RXfe7wQzUh6ZVHN774tbaLDNhbSz21QLSi4aKWwM3eefDXqOD+RC/B+Pp+oGmGvcfWMw== + dependencies: + axios "^0.21.1" + "@docsearch/css@3.0.0-alpha.36": version "3.0.0-alpha.36" resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.36.tgz#0af69a86b845974d0f8cab62db0218f66b6ad2d6" @@ -3049,6 +3056,11 @@ before-after-hook@^2.2.0: resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c" integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw== +big-integer@^1.6.17: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3064,6 +3076,14 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk= + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -3085,6 +3105,11 @@ bluebird@^3.7.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM= + body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -3208,6 +3233,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer-indexof@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" @@ -3221,6 +3251,11 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -3345,6 +3380,13 @@ ccount@^1.0.0, ccount@^1.0.3: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg= + dependencies: + traverse ">=0.3.0 <0.4" + chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4426,6 +4468,13 @@ dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" @@ -5131,6 +5180,16 @@ fsevents@^2.1.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -5370,7 +5429,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -5891,7 +5950,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -7020,6 +7079,11 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc= + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -7443,7 +7507,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: +"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -9246,7 +9310,7 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@^2.6.3: +rimraf@2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -9511,7 +9575,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.5: +setimmediate@^1.0.5, setimmediate@~1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -10244,6 +10308,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= + trim-trailing-lines@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" @@ -10520,6 +10589,22 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unzipper@^0.10.11: + version "0.10.11" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.11.tgz#0b4991446472cbdb92ee7403909f26c2419c782e" + integrity sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"