From d883fb7961ccc2de7ccad597ee0347894170dc9a Mon Sep 17 00:00:00 2001 From: Nikita Melnikov Date: Sat, 8 Oct 2022 22:42:58 +0800 Subject: [PATCH] implement cli and some refactoring --- .gitignore | 2 + package.json | 12 +- src/backend/app.ts | 115 +++--------------- src/backend/build-static.ts | 175 +++++++++++++++------------ src/backend/server.ts | 214 ++++++++++++++++++++++----------- src/backend/utils/appConfig.ts | 11 ++ src/backend/utils/banner.ts | 33 +++++ yarn.lock | 66 +++++++++- 8 files changed, 379 insertions(+), 249 deletions(-) create mode 100644 src/backend/utils/banner.ts diff --git a/.gitignore b/.gitignore index 0f84a0a..26dcdf2 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,5 @@ db/ /public/dist/* *.local.yaml + +static-build diff --git a/package.json b/package.json index e030bea..fb6c21d 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "codex.docs", "license": "Apache-2.0", - "version": "0.0.1-alpha.5", + "version": "0.0.1-alpha.6", "type": "module", "bin": { - "codex.docs": "dist/backend/server.js" + "codex.docs": "dist/backend/app.js" }, "browserslist": [ "last 2 versions", @@ -14,8 +14,8 @@ "start": "concurrently \"yarn start-backend\" \"yarn build-frontend\"", "dev": "concurrently \"yarn start-backend\" \"yarn build-frontend:dev\"", "build-all": "yarn build-frontend && yarn build-backend", - "build-static": "ts-node src/backend/build-static.ts", - "start-backend": "cross-env NODE_ENV=development npx nodemon --config nodemon.json src/backend/server.ts -c docs-config.yaml -c docs-config.local.yaml", + "build-static": "ts-node src/backend/app.ts build-static -c docs-config.yaml -c docs-config.local.yaml", + "start-backend": "cross-env NODE_ENV=development npx nodemon --config nodemon.json src/backend/app.ts -c docs-config.yaml -c docs-config.local.yaml", "build-backend": "tsc && copyfiles -u 3 ./src/**/*.twig ./dist/backend/views && copyfiles -u 1 ./src/**/*.svg ./dist/", "build-frontend": "webpack --mode=production", "build-frontend:dev": "webpack --mode=development --watch", @@ -31,6 +31,7 @@ "@hawk.so/javascript": "^3.0.1", "@hawk.so/nodejs": "^3.1.4", "@types/multer-s3": "^3.0.0", + "@types/yargs": "^17.0.13", "arg": "^5.0.2", "config": "^3.3.6", "cookie-parser": "^1.4.5", @@ -39,6 +40,7 @@ "dotenv": "^16.0.0", "express": "^4.17.1", "file-type": "^16.5.4", + "fs-extra": "^10.1.0", "http-errors": "^2.0.0", "jsonwebtoken": "^8.5.1", "mime": "^3.0.0", @@ -53,6 +55,7 @@ "open-graph-scraper": "^4.9.0", "twig": "^1.15.4", "uuid4": "^2.0.2", + "yargs": "^17.6.0", "zod": "^3.19.1" }, "devDependencies": { @@ -84,6 +87,7 @@ "@types/debug": "^4.1.7", "@types/express": "^4.17.13", "@types/file-type": "^10.9.1", + "@types/fs-extra": "^9.0.13", "@types/jsonwebtoken": "^8.5.4", "@types/mime": "^2.0.3", "@types/mkdirp": "^1.0.2", diff --git a/src/backend/app.ts b/src/backend/app.ts index 313e7df..99d1ef3 100644 --- a/src/backend/app.ts +++ b/src/backend/app.ts @@ -1,99 +1,20 @@ -import express, { NextFunction, Request, Response } from 'express'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import cookieParser from 'cookie-parser'; -import morgan from 'morgan'; -import routes from './routes/index.js'; -import HttpException from './exceptions/httpException.js'; -import * as dotenv from 'dotenv'; -import HawkCatcher from '@hawk.so/nodejs'; -import os from 'os'; -import { downloadFavicon, FaviconData } from './utils/downloadFavicon.js'; -import appConfig from './utils/appConfig.js'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import runHttpServer from './server.js'; +import buildStatic from './build-static.js'; -/** - * The __dirname CommonJS variables are not available in ES modules. - * https://nodejs.org/api/esm.html#no-__filename-or-__dirname - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -dotenv.config(); -const app = express(); -const localConfig = appConfig.frontend; - -// Initialize the backend error tracking catcher. -if (appConfig.hawk?.backendToken) { - HawkCatcher.init(appConfig.hawk.backendToken); -} - -// Get url to upload favicon from config -const favicon = appConfig.favicon; - -app.locals.config = localConfig; -// Set client error tracking token as app local. -if (appConfig.hawk?.frontendToken) { - app.locals.config.hawkClientToken = appConfig.hawk.frontendToken; -} - -// view engine setup -app.set('views', path.join(__dirname, './', 'views')); -app.set('view engine', 'twig'); -import('./utils/twig.js'); - -const downloadedFaviconFolder = os.tmpdir(); - -// Check if favicon is not empty -if (favicon) { - // Upload favicon by url, it's path on server is '/temp/favicon.{format}' - downloadFavicon(favicon, downloadedFaviconFolder).then((res) => { - app.locals.favicon = res; - console.log('Favicon successfully uploaded'); +yargs(hideBin(process.argv)) + .option('config', { + alias: 'c', + type: 'string', + default: './docs-config.yaml', + description: 'Config files paths', }) - .catch( (err) => { - console.log(err); - console.log('Favicon has not uploaded'); - }); -} else { - console.log('Favicon is empty, using default path'); - app.locals.favicon = { - destination: '/favicon.png', - type: 'image/png', - } as FaviconData; -} - -app.use(morgan('dev')); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); -app.use(cookieParser()); -app.use(express.static(path.join(__dirname, '../../public'))); - -if (appConfig.uploads.driver === 'local') { - app.use('/uploads', express.static(appConfig.uploads.local.path)); -} - -app.use('/favicon', express.static(downloadedFaviconFolder)); - -app.use('/', routes); - - -// global error handler -app.use(function (err: unknown, req: Request, res: Response, next: NextFunction) { - // send any type of error to hawk server. - if (appConfig.hawk?.backendToken && err instanceof Error) { - HawkCatcher.send(err); - } - // only send Http based exception to client. - if (err instanceof HttpException) { - // set locals, only providing error in development - res.locals.message = err.message; - res.locals.error = req.app.get('env') === 'development' ? err : {}; - // render the error page - res.status(err.status || 500); - res.render('error'); - } - next(err); -}); - - -export default app; + .help('h') + .alias('h', 'help') + .command('$0', 'start the server', () => {/* empty */}, runHttpServer) + .command('build-static', 'build files from database', () => {/* empty */}, async () => { + await buildStatic(); + process.exit(0); + }) + .parse(); diff --git a/src/backend/build-static.ts b/src/backend/build-static.ts index 1273cf2..a8dd4a0 100644 --- a/src/backend/build-static.ts +++ b/src/backend/build-static.ts @@ -1,7 +1,6 @@ import twig from 'twig'; import Page from './models/page.js'; import PagesFlatArray from './models/pagesFlatArray.js'; -import appConfig from './utils/appConfig.js'; import path from 'path'; import { fileURLToPath } from 'url'; import('./utils/twig.js'); @@ -10,85 +9,113 @@ import mkdirp from 'mkdirp'; import { createMenuTree } from './utils/menu.js'; import { EntityId } from './database/types.js'; import PagesOrder from './controllers/pagesOrder.js'; - -const dirname = path.dirname(fileURLToPath(import.meta.url)); -const cwd = process.cwd(); -const distPath = path.resolve(cwd, 'dist'); -const pagesOrder = await PagesOrder.getAll(); -const allPages = await Page.getAll(); - -await mkdirp(distPath); +import fse from 'fs-extra'; +import appConfig from './utils/appConfig.js'; +import Aliases from './controllers/aliases.js'; +import Pages from './controllers/pages.js'; /** - * Render template with twig by path - * - * @param filePath - path to template - * @param data - data to render template + * Build static pages from database */ -function renderTemplate(filePath: string, data: Record): Promise { - return new Promise((resolve, reject) => { - twig.renderFile(path.resolve(dirname, filePath), data, (err, html) => { - if (err) { - reject(err); - } - resolve(html); - }); - }); -} +export default async function buildStatic(): Promise { + const config = appConfig.staticBuild; -/** - * Renders single page - * - * @param page - page to render - */ -async function renderPage(page: Page): Promise { - console.log(`Rendering page ${page.uri}`); - const pageParent = await page.getParent(); - const pageId = page._id; - - if (!pageId) { - throw new Error('Page id is not defined'); + if (!config) { + throw new Error('Static build config not found'); } - const parentIdOfRootPages = '0' as EntityId; - const previousPage = await PagesFlatArray.getPageBefore(pageId); - const nextPage = await PagesFlatArray.getPageAfter(pageId); - const menu = createMenuTree(parentIdOfRootPages, allPages, pagesOrder, 2); - const result = await renderTemplate('./views/pages/page.twig', { - page, - pageParent, - previousPage, - nextPage, - menu, - config: appConfig.frontend, - }); - // console.log(result); - const filename = page.uri === '' ? 'index.html' : `${page.uri}.html`; + const dirname = path.dirname(fileURLToPath(import.meta.url)); + const cwd = process.cwd(); + const distPath = path.resolve(cwd, config.outputDir); - await fs.writeFile(path.resolve(distPath, filename), result); - console.log(`Page ${page.uri} rendered`); + /** + * Render template with twig by path + * + * @param filePath - path to template + * @param data - data to render template + */ + function renderTemplate(filePath: string, data: Record): Promise { + return new Promise((resolve, reject) => { + twig.renderFile(path.resolve(dirname, filePath), data, (err, html) => { + if (err) { + reject(err); + } + resolve(html); + }); + }); + } + + console.log('Remove old static files'); + await fse.remove(distPath); + + console.log('Building static files'); + const pagesOrder = await PagesOrder.getAll(); + const allPages = await Page.getAll(); + + await mkdirp(distPath); + + /** + * Renders single page + * + * @param page - page to render + * @param isIndex - is this page index page + */ + async function renderPage(page: Page, isIndex?: boolean): Promise { + console.log(`Rendering page ${page.uri}`); + const pageParent = await page.getParent(); + const pageId = page._id; + + if (!pageId) { + throw new Error('Page id is not defined'); + } + const parentIdOfRootPages = '0' as EntityId; + const previousPage = await PagesFlatArray.getPageBefore(pageId); + const nextPage = await PagesFlatArray.getPageAfter(pageId); + const menu = createMenuTree(parentIdOfRootPages, allPages, pagesOrder, 2); + const result = await renderTemplate('./views/pages/page.twig', { + page, + pageParent, + previousPage, + nextPage, + menu, + config: appConfig.frontend, + }); + + const filename = (isIndex || page.uri === '') ? 'index.html' : `${page.uri}.html`; + + await fs.writeFile(path.resolve(distPath, filename), result); + console.log(`Page ${page.uri} rendered`); + } + + /** + * Render index page + * + * @param indexPageUri - uri of index page + */ + async function renderIndexPage(indexPageUri: string): Promise { + const alias = await Aliases.get(indexPageUri); + + if (!alias.id) { + throw new Error(`Alias ${indexPageUri} not found`); + } + + const page = await Pages.get(alias.id); + + console.log(page); + + await renderPage(page, true); + } + + /** + * Render all pages + */ + for (const page of allPages) { + await renderPage(page); + } + + await renderIndexPage(config.indexPageUri); + + await fse.copy(path.resolve(dirname, '../../public'), distPath); + console.log('Static files built'); } -/** - * Render all pages - */ -for (const page of allPages) { - await renderPage(page); -} - -/** - * Render index page - */ -async function renderIndexPage(): Promise { - console.log('Rendering index page'); - const result = await renderTemplate('./views/pages/index.twig', { - config: appConfig.frontend, - }); - - const filename = 'index.html'; - - await fs.writeFile(path.resolve(distPath, filename), result); - console.log('Index page rendered'); -} - -await renderIndexPage(); diff --git a/src/backend/server.ts b/src/backend/server.ts index 9c4d0ab..8de431e 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -2,10 +2,20 @@ /** * Module dependencies. */ -import app from './app.js'; import http from 'http'; import Debug from 'debug'; import appConfig from './utils/appConfig.js'; +import { drawBanner } from './utils/banner.js'; +import express, { NextFunction, Request, Response } from 'express'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import HawkCatcher from '@hawk.so/nodejs'; +import os from 'os'; +import { downloadFavicon, FaviconData } from './utils/downloadFavicon.js'; +import morgan from 'morgan'; +import cookieParser from 'cookie-parser'; +import routes from './routes/index.js'; +import HttpException from './exceptions/httpException.js'; const debug = Debug.debug('codex.editor.docs:server'); @@ -14,19 +24,140 @@ const debug = Debug.debug('codex.editor.docs:server'); */ const port = normalizePort(appConfig.port.toString() || '3000'); -app.set('port', port); +/** + * Create Express server + */ +function createApp(): express.Express { + /** + * The __dirname CommonJS variables are not available in ES modules. + * https://nodejs.org/api/esm.html#no-__filename-or-__dirname + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + + const app = express(); + const localConfig = appConfig.frontend; + + // Initialize the backend error tracking catcher. + if (appConfig.hawk?.backendToken) { + HawkCatcher.init(appConfig.hawk.backendToken); + } + + // Get url to upload favicon from config + const favicon = appConfig.favicon; + + app.locals.config = localConfig; + // Set client error tracking token as app local. + if (appConfig.hawk?.frontendToken) { + app.locals.config.hawkClientToken = appConfig.hawk.frontendToken; + } + + // view engine setup + app.set('views', path.join(__dirname, './', 'views')); + app.set('view engine', 'twig'); + import('./utils/twig.js'); + + const downloadedFaviconFolder = os.tmpdir(); + + // Check if favicon is not empty + if (favicon) { + // Upload favicon by url, it's path on server is '/temp/favicon.{format}' + downloadFavicon(favicon, downloadedFaviconFolder).then((res) => { + app.locals.favicon = res; + console.log('Favicon successfully uploaded'); + }) + .catch((err) => { + console.log(err); + console.log('Favicon has not uploaded'); + }); + } else { + console.log('Favicon is empty, using default path'); + app.locals.favicon = { + destination: '/favicon.png', + type: 'image/png', + } as FaviconData; + } + + app.use(morgan('dev')); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + app.use(cookieParser()); + app.use(express.static(path.join(__dirname, '../../public'))); + + if (appConfig.uploads.driver === 'local') { + app.use('/uploads', express.static(appConfig.uploads.local.path)); + } + + app.use('/favicon', express.static(downloadedFaviconFolder)); + + app.use('/', routes); + + + // global error handler + app.use(function (err: unknown, req: Request, res: Response, next: NextFunction) { + // send any type of error to hawk server. + if (appConfig.hawk?.backendToken && err instanceof Error) { + HawkCatcher.send(err); + } + // only send Http based exception to client. + if (err instanceof HttpException) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + // render the error page + res.status(err.status || 500); + res.render('error'); + } + next(err); + }); + + return app; +} /** - * Create HTTP server. + * Create and run HTTP server. */ -const server = http.createServer(app); +export default function runHttpServer(): void { + const app = createApp(); -/** - * Listen on provided port, on all network interfaces. - */ -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); + app.set('port', port); + + /** + * Create HTTP server. + */ + const server = http.createServer(app); + + /** + * Event listener for HTTP server 'listening' event. + */ + function onListening(): void { + const addr = server.address(); + + if (addr === null) { + debug('Address not found'); + process.exit(1); + } + + const bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + + debug('Listening on ' + bind); + + drawBanner([ + `CodeX Docs server is running`, + ``, + `Main page: http://localhost:${port}`, + ]); + } + + /** + * Listen on provided port, on all network interfaces. + */ + server.listen(port); + server.on('error', onError); + server.on('listening', onListening); +} /** * Normalize a port into a number, string, or false. @@ -77,66 +208,3 @@ function onError(error: NodeJS.ErrnoException): void { throw error; } } - -/** - * Event listener for HTTP server 'listening' event. - */ -function onListening(): void { - const addr = server.address(); - - if (addr === null) { - debug('Address not found'); - process.exit(1); - } - - const bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - - debug('Listening on ' + bind); - - drawBanner([ - `CodeX Docs server is running`, - ``, - `Main page: http://localhost:${port}`, - ]); -} - -/** - * Draw banner in console with given text lines - * - * @param {string[]} lines - */ -function drawBanner(lines: string[]): void { - /** Define banner parts */ - const PARTS = { - TOP_LEFT: '┌', - TOP_RIGHT: '┐', - BOTTOM_LEFT: '└', - BOTTOM_RIGHT: '┘', - HORIZONTAL: '─', - VERTICAL: '│', - SPACE: ' ', - }; - - /** Calculate max line length */ - const maxLength = lines.reduce((max, line) => Math.max(max, line.length), 0); - - /** Prepare top line */ - const top = PARTS.TOP_LEFT + PARTS.HORIZONTAL.repeat(maxLength + 2) + PARTS.TOP_RIGHT; - - /** Compose middle lines */ - const middle = lines.map(line => PARTS.VERTICAL + ' ' + line + PARTS.SPACE.repeat(maxLength - line.length) + ' ' + PARTS.VERTICAL); - - /** Prepare bottom line */ - const bottom = PARTS.BOTTOM_LEFT + PARTS.HORIZONTAL.repeat(maxLength + 2) + PARTS.BOTTOM_RIGHT; - - console.log(top); - console.log(middle.join('\n')); - console.log(bottom); -} - -export default { - server, - app, -}; diff --git a/src/backend/utils/appConfig.ts b/src/backend/utils/appConfig.ts index 9e155a3..7d90d9e 100644 --- a/src/backend/utils/appConfig.ts +++ b/src/backend/utils/appConfig.ts @@ -84,6 +84,16 @@ const FrontendConfig = z.object({ uri: z.string() })])), // Menu for pages }); +/** + * Static build configuration + */ +const StaticBuildConfig = z.object({ + outputDir: z.string(), // Output directory for static build + indexPageUri: z.string(), // URI for index page to render +}); + +export type StaticBuildConfig = z.infer; + /** * Application configuration */ @@ -97,6 +107,7 @@ const AppConfig = z.object({ frontend: FrontendConfig, // Frontend configuration auth: AuthConfig, // Auth configuration database: z.union([LocalDatabaseConfig, MongoDatabaseConfig]), // Database configuration + staticBuild: StaticBuildConfig.optional(), // Static build configuration }); export type AppConfig = z.infer; diff --git a/src/backend/utils/banner.ts b/src/backend/utils/banner.ts new file mode 100644 index 0000000..26ca8cc --- /dev/null +++ b/src/backend/utils/banner.ts @@ -0,0 +1,33 @@ +/** + * Draw banner in console with given text lines + * + * @param lines - data to draw + */ +export function drawBanner(lines: string[]): void { + /** Define banner parts */ + const PARTS = { + TOP_LEFT: '┌', + TOP_RIGHT: '┐', + BOTTOM_LEFT: '└', + BOTTOM_RIGHT: '┘', + HORIZONTAL: '─', + VERTICAL: '│', + SPACE: ' ', + }; + + /** Calculate max line length */ + const maxLength = lines.reduce((max, line) => Math.max(max, line.length), 0); + + /** Prepare top line */ + const top = PARTS.TOP_LEFT + PARTS.HORIZONTAL.repeat(maxLength + 2) + PARTS.TOP_RIGHT; + + /** Compose middle lines */ + const middle = lines.map(line => PARTS.VERTICAL + ' ' + line + PARTS.SPACE.repeat(maxLength - line.length) + ' ' + PARTS.VERTICAL); + + /** Prepare bottom line */ + const bottom = PARTS.BOTTOM_LEFT + PARTS.HORIZONTAL.repeat(maxLength + 2) + PARTS.BOTTOM_RIGHT; + + console.log(top); + console.log(middle.join('\n')); + console.log(bottom); +} diff --git a/yarn.lock b/yarn.lock index a2d5c74..6c6e585 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2216,6 +2216,13 @@ dependencies: file-type "*" +"@types/fs-extra@^9.0.13": + version "9.0.13" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== + dependencies: + "@types/node" "*" + "@types/glob@*": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -2397,6 +2404,18 @@ "@types/node" "*" "@types/webidl-conversions" "*" +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^17.0.13": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^5.38.0": version "5.38.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz#9f05d42fa8fb9f62304cc2f5c2805e03c01c2620" @@ -3152,6 +3171,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -4315,6 +4343,15 @@ fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -4467,7 +4504,7 @@ got@^11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -4936,6 +4973,15 @@ json5@^2.1.1, json5@^2.1.2, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -7135,6 +7181,11 @@ uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -7441,6 +7492,19 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" +yargs@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" + integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"