From 7ffbfefbd31d28256a96d7d7627980cd9de50842 Mon Sep 17 00:00:00 2001 From: EGOIST <0x142857@gmail.com> Date: Sun, 3 Nov 2019 02:00:16 +0800 Subject: [PATCH] refactor(ts): convert source-pages plugin --- packages/saber-utils/package.json | 2 +- packages/saber/src/Pages.ts | 93 ++++++------ packages/saber/src/plugins/layouts.ts | 2 +- packages/saber/src/plugins/source-pages.js | 142 ------------------ packages/saber/src/plugins/source-pages.ts | 159 +++++++++++++++++++++ yarn.lock | 73 +++++++++- 6 files changed, 274 insertions(+), 197 deletions(-) delete mode 100644 packages/saber/src/plugins/source-pages.js create mode 100644 packages/saber/src/plugins/source-pages.ts diff --git a/packages/saber-utils/package.json b/packages/saber-utils/package.json index 66859073f..52ff0217f 100644 --- a/packages/saber-utils/package.json +++ b/packages/saber-utils/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@zeit/ncc": "0.20.5", - "fast-glob": "2.2.6", + "fast-glob": "3.1.0", "fs-extra": "8.1.0", "is-absolute-url": "^3.0.2" }, diff --git a/packages/saber/src/Pages.ts b/packages/saber/src/Pages.ts index a1f4865ea..53055fb92 100644 --- a/packages/saber/src/Pages.ts +++ b/packages/saber/src/Pages.ts @@ -44,8 +44,8 @@ export interface CreatePageOptions { } export interface CreatePageInput { - type: 'page' | 'post' - content: string + type?: 'page' | 'post' + content?: string /** * We apply different transformers based on the `contentType` * Built-in types are `default`, `markdown`, `vue` @@ -66,6 +66,8 @@ export interface CreatePageInput { internal: { /** A unique ID for this page */ id: string + /** ID of parent page */ + parent?: string /** * Is this page a local file? * If it is you also need to set `absolute` and `relative` @@ -94,6 +96,7 @@ export interface Page { slug?: string internal: { id: string + parent?: string isFile?: boolean absolute?: string relative?: string @@ -115,44 +118,18 @@ export class Pages extends Map { this.redirectRoutes = new Map() } - normalizePage(_page: CreatePageInput, file?: FileInfo): Page { + normalizePage(_page: CreatePageInput): Page { const { api } = this - let page = merge( + const page = merge( { + type: 'page', internal: {}, contentType: 'default' }, _page ) - let parsedFileName: RegExpExecArray | null = null - if (file) { - const relativePath = slash(file.relative) - const absolutePath = slash(file.absolute) - parsedFileName = FILE_NAME_REGEXP.exec( - relativePath - // Remove leading _posts/ - .replace(/^_posts\//, '') - // Remove extension - .replace(/\.[a-z]+$/i, '') - ) as RegExpExecArray // It could never be `null` - const slug = parsedFileName[16] - page = merge({}, page, { - slug, - internal: { - id: hash(file.absolute), - absolute: absolutePath, - relative: relativePath, - isFile: true - }, - contentType: api.transformers.getContentTypeByExtension( - path.extname(relativePath).slice(1) - ), - content: file.content - }) - } - let transformer = api.transformers.get(page.contentType) if (!transformer) { @@ -169,21 +146,11 @@ export class Pages extends Map { // And transformers can update the `page` // So we set them after applying the transformer - if (file && parsedFileName) { - // Read createdAt from page attribute - // Or fallback to `page.date` (Hexo compatibility) - // Or fallback to the date in fileName - // Or fallback to the `file.birthtime` - page.createdAt = - page.createdAt || page.date || parsedFileName[2] || file.birthtime + // Fallback to `page.date` (Hexo compatibility) + page.createdAt = page.createdAt || page.date - // Read updatedAt from page attribute - // Or fallback to `page.updated` (Hexo compatibility) - // Or fallback to `file.mtime` - page.updatedAt = page.updatedAt || page.updated || file.mtime - - page.type = page.type || getPageType(slash(file.relative)) - } + // Fallback to `page.updated` (Hexo compatibility) + page.updatedAt = page.updatedAt || page.updated // Ensure date format if (typeof page.createdAt === 'string') { @@ -229,13 +196,39 @@ export class Pages extends Map { return page as Page } - createPage(page: CreatePageInput, options: CreatePageOptions = {}) { - const { file, normalize } = options - if (normalize !== false) { - page = this.normalizePage(page, file) + fileToPage(file: FileInfo): Page { + const relativePath = slash(file.relative) + const absolutePath = slash(file.absolute) + const parsedFileName = FILE_NAME_REGEXP.exec( + relativePath + // Remove leading _posts/ + .replace(/^_posts\//, '') + // Remove extension + .replace(/\.[a-z]+$/i, '') + ) as RegExpExecArray // It could never be `null` + const slug = parsedFileName[16] + const pageInput: CreatePageInput = { + slug, + type: getPageType(slash(file.relative)), + internal: { + id: hash(file.absolute), + absolute: absolutePath, + relative: relativePath, + isFile: true + }, + contentType: this.api.transformers.getContentTypeByExtension( + path.extname(relativePath).slice(1) + ), + content: file.content, + createdAt: parsedFileName[2] || file.birthtime, + updatedAt: file.mtime } + return this.normalizePage(pageInput) + } - this.set(page.internal.id, page as Page) + createPage(pageInput: CreatePageInput) { + const page = this.normalizePage(pageInput) + this.set(page.internal.id, page) } removePage(id: string) { diff --git a/packages/saber/src/plugins/layouts.ts b/packages/saber/src/plugins/layouts.ts index 5f7ea6535..d8aba0c4f 100644 --- a/packages/saber/src/plugins/layouts.ts +++ b/packages/saber/src/plugins/layouts.ts @@ -31,7 +31,7 @@ const layoutsPlugin: SaberPlugin = { }) const layouts = {} files.forEach(file => { - setLayout(layouts, path.join(dir, file as string)) + setLayout(layouts, path.join(dir, file)) }) return layouts } diff --git a/packages/saber/src/plugins/source-pages.js b/packages/saber/src/plugins/source-pages.js deleted file mode 100644 index 844c2d249..000000000 --- a/packages/saber/src/plugins/source-pages.js +++ /dev/null @@ -1,142 +0,0 @@ -const path = require('path') -const { fs, glob } = require('saber-utils') -const chokidar = require('chokidar') -const { log, colors } = require('saber-log') -const hash = require('hash-sum') - -const ID = 'builtin:source-pages' - -exports.name = ID - -exports.apply = api => { - api.hooks.beforeRun.tapPromise(ID, async () => { - const pagesDir = api.resolveCwd('pages') - const exts = api.transformers.supportedExtensions - const filePatterns = [ - `**/*.${exts.length === 1 ? exts[0] : `{${exts.join(',')}}`}`, - '!**/{node_modules,dist,vendor}/**', - '!**/_!(posts)/**' - ] - - const files = await glob(filePatterns, { - cwd: pagesDir, - dot: false, - stats: true - }).then(files => - Promise.all( - files - .sort((a, b) => (a.path > b.path ? 1 : -1)) - .map(async file => { - file.relative = file.path - file.absolute = path.join(pagesDir, file.relative) - // eslint-disable-next-line require-atomic-updates - file.content = await fs.readFile(file.absolute, 'utf8') - log.verbose(`Found page`, colors.dim(file.absolute)) - return file - }) - ) - ) - - api.hooks.manipulatePage.tapPromise( - 'manipulate-page', - async ({ action, id, page }) => { - // Remove all child pages - api.pages.removeWhere(page => page.internal.parent) - - if (action === 'remove') { - // Remove itself - api.pages.removeWhere(page => { - return page.internal.id === id - }) - } else if (action) { - api.pages.createPage(page, { normalize: false }) - await api.hooks.onCreatePage.promise(page) - } - } - ) - - // Write all pages - // This is triggered by all file actions: change, add, remove - api.hooks.emitPages.tapPromise('pages', async () => { - const pages = [...api.pages.values()] - log.verbose('Emitting pages') - // TODO: maybe write pages with limited concurrency? - await Promise.all( - pages.map(async page => { - if (page.internal.saved) return - - const newContentHash = hash(page) - const outPath = api.resolveCache( - 'pages', - `${page.internal.id}.saberpage` - ) - // TODO: is there any better solution to checking if we need to write the page? - const exists = await fs.pathExists(outPath) - if (exists) { - const contentHash = await fs.readFile(outPath, 'utf8') - if (contentHash === newContentHash) { - // Skip if content doesn't change - return - } - } - - log.verbose(`Emitting page ${outPath}`) - await fs.outputFile(outPath, newContentHash, 'utf8') - // eslint-disable-next-line require-atomic-updates - page.internal.saved = true - }) - ) - }) - - await api.hooks.initPages.promise() - - await Promise.all( - files.map(async file => { - const page = api.pages.normalizePage({}, file) - api.pages.createPage(page, { normalize: false }) - await api.hooks.onCreatePage.promise(page) - }) - ) - - await api.hooks.onCreatePages.promise() - await api.hooks.emitPages.promise() - - if (api.dev) { - const watcher = chokidar.watch(filePatterns, { - cwd: pagesDir, - ignoreInitial: true - }) - const handler = type => async filename => { - const filepath = path.join(pagesDir, filename) - - if (type === 'remove') { - await api.hooks.manipulatePage.promise({ - action: 'remove', - id: hash(filepath) - }) - } else { - const file = await fs.stat(filepath) - file.relative = filename - file.absolute = filepath - file.content = await fs.readFile(file.absolute, 'utf8') - const page = api.pages.normalizePage({}, file) - await api.hooks.manipulatePage.promise({ action: 'create', page }) - } - - await api.hooks.onCreatePages.promise() - await api.hooks.emitPages.promise() - await api.hooks.emitRoutes.promise() - } - - watcher.on('add', () => { - handler('add') - }) - watcher.on('unlink', () => { - handler('remove') - }) - watcher.on('change', () => { - handler('change') - }) - } - }) -} diff --git a/packages/saber/src/plugins/source-pages.ts b/packages/saber/src/plugins/source-pages.ts new file mode 100644 index 000000000..4fa27fafd --- /dev/null +++ b/packages/saber/src/plugins/source-pages.ts @@ -0,0 +1,159 @@ +import path from 'path' +import { fs, glob } from 'saber-utils' +import chokidar from 'chokidar' +import { log, colors } from 'saber-log' +import hash from 'hash-sum' +import { SaberPlugin } from '..' +import { FileInfo } from '../Pages' + +const ID = 'builtin:source-pages' + +const sourcePagesPlugin: SaberPlugin = { + name: ID, + + apply(api) { + api.hooks.beforeRun.tapPromise(ID, async () => { + const pagesDir = api.resolveCwd('pages') + const exts = api.transformers.supportedExtensions + const filePatterns = [ + `**/*.${exts.length === 1 ? exts[0] : `{${exts.join(',')}}`}`, + '!**/{node_modules,dist,vendor}/**', + '!**/_!(posts)/**' + ] + + const files: FileInfo[] = await glob(filePatterns, { + cwd: pagesDir, + dot: false, + stats: true + }).then(files => + Promise.all( + files + .sort((a, b) => (a.path > b.path ? 1 : -1)) + .map(async file => { + log.verbose(`Found page`, colors.dim(file.path)) + const absolute = path.join(pagesDir, file.path) + // eslint-disable-next-line require-atomic-updates + const content = await fs.readFile(absolute, 'utf8') + return { + relative: file.path, + absolute, + content, + mtime: file.stats && file.stats.mtime, + birthtime: file.stats && file.stats.birthtime + } as FileInfo + }) + ) + ) + + api.hooks.manipulatePage.tapPromise( + 'manipulate-page', + async ({ action, id, page }) => { + // Remove all child pages + api.pages.removeWhere(page => Boolean(page.internal.parent)) + + if (action === 'remove') { + // Remove itself + api.pages.removeWhere(page => { + return page.internal.id === id + }) + } else if (action) { + api.pages.createPage(page) + await api.hooks.onCreatePage.promise(page) + } + } + ) + + // Write all pages + // This is triggered by all file actions: change, add, remove + api.hooks.emitPages.tapPromise('pages', async () => { + const pages = [...api.pages.values()] + log.verbose('Emitting pages') + // TODO: maybe write pages with limited concurrency? + await Promise.all( + pages.map(async page => { + if (page.internal.saved) return + + const newContentHash = hash(page) + const outPath = api.resolveCache( + 'pages', + `${page.internal.id}.saberpage` + ) + // TODO: is there any better solution to checking if we need to write the page? + const exists = await fs.pathExists(outPath) + if (exists) { + const contentHash = await fs.readFile(outPath, 'utf8') + if (contentHash === newContentHash) { + // Skip if content doesn't change + return + } + } + + log.verbose(`Emitting page ${outPath}`) + await fs.outputFile(outPath, newContentHash, 'utf8') + // eslint-disable-next-line require-atomic-updates + page.internal.saved = true + }) + ) + }) + + await api.hooks.initPages.promise() + + await Promise.all( + files.map(async file => { + const page = api.pages.fileToPage(file) + api.pages.createPage(page) + await api.hooks.onCreatePage.promise(page) + }) + ) + + await api.hooks.onCreatePages.promise() + await api.hooks.emitPages.promise() + + if (api.dev) { + const watcher = chokidar.watch(filePatterns, { + cwd: pagesDir, + ignoreInitial: true + }) + const handler = (type: 'add' | 'remove' | 'change') => async ( + filename: string + ) => { + const filepath = path.join(pagesDir, filename) + + if (type === 'remove') { + await api.hooks.manipulatePage.promise({ + action: 'remove', + id: hash(filepath) + }) + } else { + const stat = await fs.stat(filepath) + const file: FileInfo = { + absolute: filepath, + relative: filename, + birthtime: stat.birthtime, + mtime: stat.mtime, + content: await fs.readFile(filepath, 'utf8') + } + const page = api.pages.fileToPage(file) + await api.hooks.manipulatePage.promise({ action: 'create', page }) + } + + await api.hooks.onCreatePages.promise() + await api.hooks.emitPages.promise() + await api.hooks.emitRoutes.promise() + } + + watcher.on('add', (filename: string) => { + handler('add')(filename) + }) + watcher.on('unlink', (filename: string) => { + handler('remove')(filename) + }) + watcher.on('change', (filename: string) => { + handler('change')(filename) + }) + } + }) + } +} + +export default sourcePagesPlugin diff --git a/yarn.lock b/yarn.lock index 33a50905f..99ee28bda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1969,11 +1969,32 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@octokit/endpoint@^5.1.0": version "5.3.2" resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.3.2.tgz#2deda2d869cac9ba7f370287d55667be2a808d4b" @@ -3129,7 +3150,7 @@ braces@^2.3.0, braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -5436,7 +5457,18 @@ fast-diff@^1.1.2: resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@2.2.6, fast-glob@^2.2.6: +fast-glob@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.0.tgz#77375a7e3e6f6fc9b18f061cddd28b8d1eec75ae" + integrity sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + +fast-glob@^2.2.6: version "2.2.6" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz#a5d5b697ec8deda468d85a74035290a025a95295" integrity sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w== @@ -5463,6 +5495,13 @@ fastparse@^1.1.1: resolved "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== +fastq@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" + integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== + dependencies: + reusify "^1.0.0" + fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" @@ -6040,7 +6079,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== @@ -8388,6 +8427,11 @@ merge2@^1.2.3: resolved "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== +merge2@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== + merge@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" @@ -8412,6 +8456,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -9628,6 +9680,11 @@ picomatch@^2.0.4: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.0.5.tgz#067456ff321d6447ca3dc32273d7bbf19ab2face" integrity sha512-Zisqgaq/4P05ZclrU/g5XrzFqVo7YiJx+EP4haeVI9S7kvtZmZgmQMZfcvjEus9JcMhqZfQZObimT5ZydvKJGA== +picomatch@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177" + integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw== + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -11226,6 +11283,11 @@ retry@^0.10.0: resolved "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -11378,6 +11440,11 @@ run-node@^1.0.0: resolved "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"