From d0a782657bef7ae0db8e607c17d877af840d5161 Mon Sep 17 00:00:00 2001 From: gweiying <39231249+gweiying@users.noreply.github.com> Date: Fri, 23 Apr 2021 15:10:01 +0800 Subject: [PATCH 01/25] (hotfix to master) misc bugs (#164) * fix: change base64 encoding * fix: missing content during page move --- classes/Collection.js | 17 ++++++++--------- classes/Config.js | 13 ++++++------- classes/ResourceRoom.js | 27 +++++++++++++-------------- classes/Tree.js | 3 +-- routes/collectionPages.js | 3 +-- routes/collections.js | 8 ++++---- routes/homepage.js | 1 - routes/pages.js | 8 ++++---- routes/resourcePages.js | 7 +++---- utils/menu-utils.js | 3 +-- 10 files changed, 41 insertions(+), 49 deletions(-) diff --git a/classes/Collection.js b/classes/Collection.js index 36d218db5..cbde8be92 100644 --- a/classes/Collection.js +++ b/classes/Collection.js @@ -1,5 +1,4 @@ const yaml = require('yaml') -const base64 = require('base-64') const Bluebird = require('bluebird') const _ = require('lodash') @@ -48,20 +47,20 @@ class Collection { } } if (ISOMER_TEMPLATE_PROTECTED_DIRS.includes(collectionName)) throw new ConflictError(protectedFolderConflictErrorMsg(collectionName)) - const newContent = base64.encode(yaml.stringify(contentObject)) + const newContent = Base64.encode(yaml.stringify(contentObject)) await collectionConfig.create(newContent) const nav = new File(this.accessToken, this.siteName) const dataType = new DataType() nav.setFileType(dataType) const { content:navContent, sha:navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = yaml.parse(base64.decode(navContent)) + const navContentObject = yaml.parse(Base64.decode(navContent)) navContentObject.links.push({ title: deslugifyCollectionName(collectionName), collection: collectionName }) - const newNavContent = base64.encode(yaml.stringify(navContentObject)) + const newNavContent = Base64.encode(yaml.stringify(navContentObject)) await nav.update(NAV_FILE_NAME, newNavContent, navSha) @@ -84,14 +83,14 @@ class Collection { const dataType = new DataType() nav.setFileType(dataType) const { content:navContent, sha:navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = yaml.parse(base64.decode(navContent)) + const navContentObject = yaml.parse(Base64.decode(navContent)) const newNavLinks = navContentObject.links.filter(link => link.collection !== collectionName) const newNavContentObject = { ...navContentObject, links: newNavLinks, } - const newNavContent = base64.encode(yaml.stringify(newNavContentObject)) + const newNavContent = Base64.encode(yaml.stringify(newNavContentObject)) await nav.update(NAV_FILE_NAME, newNavContent, navSha) } catch (err) { throw err @@ -107,7 +106,7 @@ class Collection { const dataType = new DataType() nav.setFileType(dataType) const { content:navContent, sha:navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = yaml.parse(base64.decode(navContent)) + const navContentObject = yaml.parse(Base64.decode(navContent)) const newNavLinks = navContentObject.links.map(link => { if (link.collection === oldCollectionName) { @@ -123,7 +122,7 @@ class Collection { ...navContentObject, links: newNavLinks, } - const newNavContent = base64.encode(yaml.stringify(newNavContentObject)) + const newNavContent = Base64.encode(yaml.stringify(newNavContentObject)) await nav.update(NAV_FILE_NAME, newNavContent, navSha) const gitTree = await getTree(this.siteName, this.accessToken, treeSha); @@ -149,7 +148,7 @@ class Collection { [newCollectionName]: configContentObject.collections[oldCollectionName] } } - const newConfigContent = base64.encode(yaml.stringify(newConfigContentObject)) + const newConfigContent = Base64.encode(yaml.stringify(newConfigContentObject)) await collectionConfig.update(newConfigContent, configSha) } catch (err) { diff --git a/classes/Config.js b/classes/Config.js index f54e7ef0a..ecf83b9f7 100644 --- a/classes/Config.js +++ b/classes/Config.js @@ -1,7 +1,6 @@ const axios = require('axios'); const validateStatus = require('../utils/axios-utils') const yaml = require('yaml') -const base64 = require('base-64') const _ = require('lodash') // Import error @@ -115,7 +114,7 @@ class CollectionConfig extends Config { async read() { const { content, sha } = await super.read() - const contentObject = yaml.parse(base64.decode(content)) + const contentObject = yaml.parse(Base64.decode(content)) return { content: contentObject, sha } } @@ -137,7 +136,7 @@ class CollectionConfig extends Config { } } content.collections[collectionName].order.splice(newIndex, 0, item) - const newContent = base64.encode(yaml.stringify(content)) + const newContent = Base64.encode(yaml.stringify(content)) await this.update(newContent, sha) } @@ -147,7 +146,7 @@ class CollectionConfig extends Config { const { content, sha } = await this.read() const index = content.collections[collectionName].order.indexOf(item) content.collections[collectionName].order.splice(index, 1) - const newContent = base64.encode(yaml.stringify(content)) + const newContent = Base64.encode(yaml.stringify(content)) await this.update(newContent, sha) return { index, item } @@ -159,7 +158,7 @@ class CollectionConfig extends Config { const index = content.collections[collectionName].order.indexOf(oldItem) content.collections[collectionName].order.splice(index, 1) content.collections[collectionName].order.splice(index, 0, newItem) - const newContent = base64.encode(yaml.stringify(content)) + const newContent = Base64.encode(yaml.stringify(content)) await this.update(newContent, sha) } @@ -170,7 +169,7 @@ class CollectionConfig extends Config { const filteredOrder = content.collections[collectionName].order.filter(item => !item.includes(`${subfolder}/`)) const newContentObject = _.cloneDeep(content) newContentObject.collections[collectionName].order = filteredOrder - const newContent = base64.encode(yaml.stringify(newContentObject)) + const newContent = Base64.encode(yaml.stringify(newContentObject)) await this.update(newContent, sha) } @@ -184,7 +183,7 @@ class CollectionConfig extends Config { }) const newContentObject = _.cloneDeep(content) newContentObject.collections[collectionName].order = renamedOrder - const newContent = base64.encode(yaml.stringify(newContentObject)) + const newContent = Base64.encode(yaml.stringify(newContentObject)) await this.update(newContent, sha) } diff --git a/classes/ResourceRoom.js b/classes/ResourceRoom.js index 9ed419495..6bcacbf2b 100644 --- a/classes/ResourceRoom.js +++ b/classes/ResourceRoom.js @@ -1,5 +1,4 @@ const yaml = require('yaml') -const base64 = require('base-64') const Bluebird = require('bluebird') const _ = require('lodash') @@ -24,7 +23,7 @@ class ResourceRoom { try { const config = new Config(this.accessToken, this.siteName) const { content } = await config.read() - const contentObject = yaml.parse(base64.decode(content)) + const contentObject = yaml.parse(Base64.decode(content)) return contentObject.resources_name } catch (err) { @@ -36,11 +35,11 @@ class ResourceRoom { try { const config = new Config(this.accessToken, this.siteName) const { content, sha } = await config.read() - const contentObject = yaml.parse(base64.decode(content)) + const contentObject = yaml.parse(Base64.decode(content)) contentObject.resources_name = resourceRoom - const newContent = base64.encode(yaml.stringify(contentObject)) + const newContent = Base64.encode(yaml.stringify(contentObject)) // Create index file in resourceRoom const IsomerIndexFile = new File(this.accessToken, this.siteName) @@ -54,13 +53,13 @@ class ResourceRoom { const dataType = new DataType() nav.setFileType(dataType) const { content:navContent, sha:navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = yaml.parse(base64.decode(navContent)) + const navContentObject = yaml.parse(Base64.decode(navContent)) navContentObject.links.push({ title: deslugifyCollectionName(resourceRoom), resource_room: true }) - const newNavContent = base64.encode(yaml.stringify(navContentObject)) + const newNavContent = Base64.encode(yaml.stringify(navContentObject)) await nav.update(NAV_FILE_NAME, newNavContent, navSha) @@ -76,19 +75,19 @@ class ResourceRoom { // Add resource room to config const config = new Config(this.accessToken, this.siteName) const { content, sha } = await config.read() - const contentObject = yaml.parse(base64.decode(content)) + const contentObject = yaml.parse(Base64.decode(content)) // Obtain existing resourceRoomName const resourceRoomName = contentObject.resources_name contentObject.resources_name = newResourceRoom - const newContent = base64.encode(yaml.stringify(contentObject)) + const newContent = Base64.encode(yaml.stringify(contentObject)) // Rename resource room in nav if it exists const nav = new File(this.accessToken, this.siteName) const dataType = new DataType() nav.setFileType(dataType) const { content:navContent, sha:navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = yaml.parse(base64.decode(navContent)) + const navContentObject = yaml.parse(Base64.decode(navContent)) const newNavLinks = navContentObject.links.map(link => { if (link.resource_room === true) { @@ -104,7 +103,7 @@ class ResourceRoom { ...navContentObject, links: newNavLinks, } - const newNavContent = base64.encode(yaml.stringify(newNavContentObject)) + const newNavContent = Base64.encode(yaml.stringify(newNavContentObject)) await nav.update(NAV_FILE_NAME, newNavContent, navSha) const { currentCommitSha, treeSha } = await getCommitAndTreeSha(this.siteName, this.accessToken) @@ -134,21 +133,21 @@ class ResourceRoom { // Delete resource in config const config = new Config(this.accessToken, this.siteName) const { content, sha } = await config.read() - const contentObject = yaml.parse(base64.decode(content)) + const contentObject = yaml.parse(Base64.decode(content)) // Obtain resourceRoomName const resourceRoomName = contentObject.resources_name // Delete resourcses_name from Config delete contentObject.resources_name - const newContent = base64.encode(yaml.stringify(contentObject)) + const newContent = Base64.encode(yaml.stringify(contentObject)) // Delete resource room in nav if it exists const nav = new File(this.accessToken, this.siteName) const dataType = new DataType() nav.setFileType(dataType) const { content:navContent, sha:navSha } = await nav.read(NAV_FILE_NAME) - const navContentObject = yaml.parse(base64.decode(navContent)) + const navContentObject = yaml.parse(Base64.decode(navContent)) // Assumption: only a single resource room exists const newNavLinks = navContentObject.links.filter(link => link.resource_room !== true) @@ -156,7 +155,7 @@ class ResourceRoom { ...navContentObject, links: newNavLinks, } - const newNavContent = base64.encode(yaml.stringify(newNavContentObject)) + const newNavContent = Base64.encode(yaml.stringify(newNavContentObject)) await nav.update(NAV_FILE_NAME, newNavContent, navSha) // Delete all resources and resourcePages diff --git a/classes/Tree.js b/classes/Tree.js index bf86db4df..b4d03498b 100644 --- a/classes/Tree.js +++ b/classes/Tree.js @@ -1,6 +1,5 @@ const Bluebird = require('bluebird') const yaml = require('yaml') -const base64 = require('base-64') const _ = require('lodash') @@ -33,7 +32,7 @@ class Tree { const IsomerNavFile = new File(this.accessToken, this.siteName) IsomerNavFile.setFileType(new DataType()) const { content } = await IsomerNavFile.read('navigation.yml') - const navItems = yaml.parse(base64.decode(content)).links; + const navItems = yaml.parse(Base64.decode(content)).links; /** * The following function tokenizes the items diff --git a/routes/collectionPages.js b/routes/collectionPages.js index 52f1ff041..68f1eda0c 100644 --- a/routes/collectionPages.js +++ b/routes/collectionPages.js @@ -2,7 +2,6 @@ const express = require('express'); const router = express.Router(); const Bluebird = require('bluebird'); const yaml = require('yaml'); -const base64 = require('base-64'); const _ = require('lodash'); // Import middleware @@ -52,7 +51,7 @@ async function listCollectionPagesDetails(req, res, next) { const collectionPages = await CollectionPage.list() const collectionPagesMetadata = await Bluebird.map(collectionPages, async (page) => { const { content } = await readCollectionPageUtilFunc(accessToken, siteName, collectionName, page.fileName) - const frontMatter = yaml.parse(base64.decode(content).split('---')[1]) + const frontMatter = yaml.parse(Base64.decode(content).split('---')[1]) return { fileName: page.fileName, title: frontMatter.title, diff --git a/routes/collections.js b/routes/collections.js index b1415e9aa..36a778658 100644 --- a/routes/collections.js +++ b/routes/collections.js @@ -2,7 +2,6 @@ const express = require('express'); const router = express.Router(); const Bluebird = require('bluebird') const yaml = require('yaml'); -const base64 = require('base-64'); // Import middleware const { @@ -111,12 +110,13 @@ async function moveFiles (req, res, next) { await oldIsomerFile.delete(fileName, sha) if (targetSubfolderName || collectionSubfolderName) { // Modifying third nav in front matter, to be removed after template rewrite - const frontMatter = yaml.parse(base64.decode(content).split('---')[1]) + const [ _, encodedFrontMatter, pageContent ] = Base64.decode(content).split('---') + const frontMatter = yaml.parse(encodedFrontMatter) if (targetSubfolderName) frontMatter.third_nav_title = deslugifyCollectionName(targetSubfolderName) else delete frontMatter.third_nav_title const newFrontMatter = yaml.stringify(frontMatter) - const newContent = ['---\n', newFrontMatter, '---'].join('') - const newEncodedContent = base64.encode(newContent) + const newContent = ['---\n', newFrontMatter, '---', pageContent].join('') + const newEncodedContent = Base64.encode(newContent) await newIsomerFile.create(fileName, newEncodedContent) } else { await newIsomerFile.create(fileName, content) diff --git a/routes/homepage.js b/routes/homepage.js index 1be566889..d6badff69 100644 --- a/routes/homepage.js +++ b/routes/homepage.js @@ -1,6 +1,5 @@ const express = require('express'); const router = express.Router(); -const base64 = require('base-64') // Import middleware const { diff --git a/routes/pages.js b/routes/pages.js index a2d3d4a99..e55b5c19d 100644 --- a/routes/pages.js +++ b/routes/pages.js @@ -3,7 +3,6 @@ const router = express.Router(); const Bluebird = require('bluebird') const _ = require('lodash') const yaml = require('yaml') -const base64 = require('base-64') // Import middleware const { @@ -161,11 +160,12 @@ async function moveUnlinkedPages (req, res, next) { await oldIsomerFile.delete(fileName, sha) if (targetSubfolderName) { // Adding third nav to front matter, to be removed after template rewrite - const frontMatter = yaml.parse(base64.decode(content).split('---')[1]) + const [ _, encodedFrontMatter, pageContent ] = Base64.decode(content).split('---') + const frontMatter = yaml.parse(encodedFrontMatter) frontMatter.third_nav_title = deslugifyCollectionName(targetSubfolderName) const newFrontMatter = yaml.stringify(frontMatter) - const newContent = ['---\n', newFrontMatter, '---'].join('') - const newEncodedContent = base64.encode(newContent) + const newContent = ['---\n', newFrontMatter, '---', pageContent].join('') + const newEncodedContent = Base64.encode(newContent) await newIsomerFile.create(fileName, newEncodedContent) } else { await newIsomerFile.create(fileName, content) diff --git a/routes/resourcePages.js b/routes/resourcePages.js index 381d0db0c..ac08e56ce 100644 --- a/routes/resourcePages.js +++ b/routes/resourcePages.js @@ -1,6 +1,5 @@ const express = require('express'); const router = express.Router(); -const base64 = require('base-64'); // Import middleware const { @@ -78,7 +77,7 @@ async function readResourcePage (req, res, next) { const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) IsomerFile.setFileType(resourcePageType) const { sha, content: encodedContent } = await IsomerFile.read(pageName) - const content = base64.decode(encodedContent) + const content = Base64.decode(encodedContent) // TO-DO: // Validate content @@ -101,7 +100,7 @@ async function updateResourcePage (req, res, next) { const IsomerFile = new File(accessToken, siteName) const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) IsomerFile.setFileType(resourcePageType) - const { newSha } = await IsomerFile.update(pageName, base64.encode(pageContent), sha) + const { newSha } = await IsomerFile.update(pageName, Base64.encode(pageContent), sha) res.status(200).json({ resourceName, pageName, pageContent, sha: newSha }) } @@ -141,7 +140,7 @@ async function renameResourcePage (req, res, next) { const IsomerFile = new File(accessToken, siteName) const resourcePageType = new ResourcePageType(resourceRoomName, resourceName) IsomerFile.setFileType(resourcePageType) - const { sha: newSha } = await IsomerFile.create(newPageName, base64.encode(pageContent)) + const { sha: newSha } = await IsomerFile.create(newPageName, Base64.encode(pageContent)) await IsomerFile.delete(pageName, sha) res.status(200).json({ resourceName, pageName: newPageName, pageContent, sha: newSha }) diff --git a/utils/menu-utils.js b/utils/menu-utils.js index dea27b858..2d8a89933 100644 --- a/utils/menu-utils.js +++ b/utils/menu-utils.js @@ -1,6 +1,5 @@ const Bluebird = require('bluebird') const yaml = require('yaml') -const base64 = require('base-64') const { File, CollectionPageType } = require('../classes/File') const { deslugifyCollectionPage } = require('./utils') @@ -67,7 +66,7 @@ const thirdNavAggregator = async (collectionPages, CollectionFile, item) => { if (canCreateThirdnav) { // Retrieve third_nav_title from frontmatter in the thirdnav page - this is slow const { content } = await CollectionFile.read(collectionPage.fileName); - const frontMatter = yaml.parse(base64.decode(content).split('---')[1]); + const frontMatter = yaml.parse(Base64.decode(content).split('---')[1]); accumulator.push({ title: frontMatter.third_nav_title, type: "thirdnav", From 839f79f7000ee74a9c251128fffe3d70a7d9c941 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Mon, 23 Aug 2021 12:29:45 +0800 Subject: [PATCH 02/25] Fix: loosen request schema --- validators/RequestSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validators/RequestSchema.js b/validators/RequestSchema.js index 4e68c8a99..ea603f5aa 100644 --- a/validators/RequestSchema.js +++ b/validators/RequestSchema.js @@ -4,7 +4,7 @@ const FrontMatterSchema = Joi.object({ title: Joi.string().required(), permalink: Joi.string().required(), third_nav_title: Joi.string(), -}) +}).unknown(true) const ContentSchema = Joi.object({ frontMatter: FrontMatterSchema.required(), From c678a26dde0459434c973001b3fcc93ef90ddde1 Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Mon, 23 Aug 2021 16:58:01 +0800 Subject: [PATCH 03/25] refactor: unlinked pages (#278) * refactor: add unlinked pages router and service * refactor: set up server.js to use unlinked pages router * refactor: add test suite for unlinked pages router and service * fix: use mockResolvedValue for async fns * feat: ensure that third_nav_title is deleted in the create method --- newroutes/__tests__/UnlinkedPages.spec.js | 196 ++++++++++++++++++ newroutes/unlinkedPages.js | 141 +++++++++++++ server.js | 10 + .../MdPageServices/UnlinkedPageService.js | 85 ++++++++ .../__tests__/UnlinkedPageService.spec.js | 168 +++++++++++++++ 5 files changed, 600 insertions(+) create mode 100644 newroutes/__tests__/UnlinkedPages.spec.js create mode 100644 newroutes/unlinkedPages.js create mode 100644 services/fileServices/MdPageServices/UnlinkedPageService.js create mode 100644 services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js diff --git a/newroutes/__tests__/UnlinkedPages.spec.js b/newroutes/__tests__/UnlinkedPages.spec.js new file mode 100644 index 000000000..463ce9496 --- /dev/null +++ b/newroutes/__tests__/UnlinkedPages.spec.js @@ -0,0 +1,196 @@ +const express = require("express") +const request = require("supertest") + +const { errorHandler } = require("@middleware/errorHandler") +const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") + +const { UnlinkedPagesRouter } = require("../unlinkedPages") + +describe("Unlinked Pages Router", () => { + const mockService = { + create: jest.fn(), + read: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + rename: jest.fn(), + } + + const router = new UnlinkedPagesRouter({ + unlinkedPageService: mockService, + }) + + const app = express() + app.use(express.json({ limit: "7mb" })) + app.use(express.urlencoded({ extended: false })) + + // We can use read route handler here because we don't need to lock the repo + app.post( + "/:siteName/pages", + attachReadRouteHandlerWrapper(router.createUnlinkedPage) + ) + app.get( + "/:siteName/pages/:pageName", + attachReadRouteHandlerWrapper(router.readUnlinkedPage) + ) + app.post( + "/:siteName/pages/:pageName", + attachReadRouteHandlerWrapper(router.updateUnlinkedPage) + ) + app.delete( + "/:siteName/pages/:pageName", + attachReadRouteHandlerWrapper(router.deleteUnlinkedPage) + ) + app.use(errorHandler) + + const siteName = "test-site" + const accessToken = undefined // Can't set request fields - will always be undefined + const fileName = "test-file" + const mockSha = "12345" + const mockContent = "mock-content" + + const reqDetails = { siteName, accessToken } + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("createUnlinkedPage", () => { + const createPageDetails = { + newFileName: "newFile", + content: { + pageBody: "test", + frontMatter: { + title: "fileTitle", + permalink: "file/permalink", + }, + }, + } + + it("rejects create requests with invalid body", async () => { + await request(app).post(`/${siteName}/pages`).send({}).expect(400) + }) + + it("accepts valid unlinked page creation requests and returns the details of the file created", async () => { + const expectedServiceInput = { + fileName: createPageDetails.newFileName, + content: createPageDetails.content.pageBody, + frontMatter: createPageDetails.content.frontMatter, + } + await request(app) + .post(`/${siteName}/pages`) + .send(createPageDetails) + .expect(200) + expect(mockService.create).toHaveBeenCalledWith( + reqDetails, + expectedServiceInput + ) + }) + }) + + describe("readUnlinkedPage", () => { + mockService.read.mockResolvedValue({ + sha: mockSha, + content: mockContent, + }) + + it("retrieves unlinked page details", async () => { + const expectedServiceInput = { + fileName, + } + await request(app).get(`/${siteName}/pages/${fileName}`).expect(200) + expect(mockService.read).toHaveBeenCalledWith( + reqDetails, + expectedServiceInput + ) + }) + }) + + describe("updateUnlinkedPage", () => { + const updatePageDetails = { + content: { + pageBody: "test", + frontMatter: { + title: "fileTitle", + permalink: "file/permalink", + }, + }, + sha: mockSha, + } + const renamePageDetails = { + ...updatePageDetails, + newFileName: "new-file", + } + + it("rejects update requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/pages/${fileName}`) + .send({}) + .expect(400) + }) + + it("accepts valid unlinked page update requests and returns the details of the updated file", async () => { + const expectedServiceInput = { + fileName, + content: updatePageDetails.content.pageBody, + frontMatter: updatePageDetails.content.frontMatter, + sha: updatePageDetails.sha, + } + await request(app) + .post(`/${siteName}/pages/${fileName}`) + .send(updatePageDetails) + .expect(200) + expect(mockService.update).toHaveBeenCalledWith( + reqDetails, + expectedServiceInput + ) + }) + + it("accepts valid unlinked page rename requests and returns the details of the renamed file", async () => { + const expectedServiceInput = { + oldFileName: fileName, + newFileName: renamePageDetails.newFileName, + content: renamePageDetails.content.pageBody, + frontMatter: renamePageDetails.content.frontMatter, + sha: renamePageDetails.sha, + } + await request(app) + .post(`/${siteName}/pages/${fileName}`) + .send(renamePageDetails) + .expect(200) + expect(mockService.rename).toHaveBeenCalledWith( + reqDetails, + expectedServiceInput + ) + }) + }) + + describe("deleteUnlinkedPage", () => { + const deletePageDetails = { + sha: mockSha, + } + + it("rejects delete requests with invalid body", async () => { + await request(app) + .delete(`/${siteName}/pages/${fileName}`) + .send({}) + .expect(400) + }) + + it("accepts valid unlinked page delete requests", async () => { + const expectedServiceInput = { + fileName, + sha: deletePageDetails.sha, + } + await request(app) + .delete(`/${siteName}/pages/${fileName}`) + .send(deletePageDetails) + .expect(200) + expect(mockService.delete).toHaveBeenCalledWith( + reqDetails, + expectedServiceInput + ) + }) + }) + + // TO-DO: Add listUnlinkedPages tests +}) diff --git a/newroutes/unlinkedPages.js b/newroutes/unlinkedPages.js new file mode 100644 index 000000000..69248798c --- /dev/null +++ b/newroutes/unlinkedPages.js @@ -0,0 +1,141 @@ +const autoBind = require("auto-bind") +const express = require("express") + +// Import middleware +const { BadRequestError } = require("@errors/BadRequestError") + +const { + attachReadRouteHandlerWrapper, + attachWriteRouteHandlerWrapper, + attachRollbackRouteHandlerWrapper, +} = require("@middleware/routeHandler") + +const { + CreatePageRequestSchema, + UpdatePageRequestSchema, + DeletePageRequestSchema, +} = require("@validators/RequestSchema") + +class UnlinkedPagesRouter { + constructor({ unlinkedPageService }) { + this.unlinkedPageService = unlinkedPageService + // We need to bind all methods because we don't invoke them from the class directly + autoBind(this) + } + + async createUnlinkedPage(req, res) { + const { accessToken } = req + + const { siteName } = req.params + const { error } = CreatePageRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error.message) + const { + content: { frontMatter, pageBody }, + newFileName, + } = req.body + const createResp = await this.unlinkedPageService.create( + { siteName, accessToken }, + { + fileName: newFileName, + content: pageBody, + frontMatter, + } + ) + + return res.status(200).json(createResp) + } + + async readUnlinkedPage(req, res) { + const { accessToken } = req + + const { siteName, pageName } = req.params + const { sha, content } = await this.unlinkedPageService.read( + { siteName, accessToken }, + { fileName: pageName } + ) + + return res.status(200).json({ pageName, sha, content }) + } + + async updateUnlinkedPage(req, res) { + const { accessToken } = req + + const { siteName, pageName } = req.params + const { error } = UpdatePageRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error) + const { + content: { frontMatter, pageBody }, + sha, + newFileName, + } = req.body + + let updateResp + if (newFileName) { + updateResp = await this.unlinkedPageService.rename( + { siteName, accessToken }, + { + oldFileName: pageName, + newFileName, + content: pageBody, + frontMatter, + sha, + } + ) + } else { + updateResp = await this.unlinkedPageService.update( + { siteName, accessToken }, + { + fileName: pageName, + content: pageBody, + frontMatter, + sha, + } + ) + } + + return res.status(200).json(updateResp) + } + + async deleteUnlinkedPage(req, res) { + const { accessToken } = req + + const { siteName, pageName } = req.params + const { error } = DeletePageRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error) + const { sha } = req.body + await this.unlinkedPageService.delete( + { siteName, accessToken }, + { + fileName: pageName, + sha, + } + ) + + return res.status(200).send("OK") + } + + getRouter() { + const router = express.Router() + + router.post( + "/:siteName/pages", + attachRollbackRouteHandlerWrapper(this.createUnlinkedPage) + ) + router.get( + "/:siteName/pages/:pageName", + attachReadRouteHandlerWrapper(this.readUnlinkedPage) + ) + router.post( + "/:siteName/pages/:pageName", + attachWriteRouteHandlerWrapper(this.updateUnlinkedPage) + ) + router.delete( + "/:siteName/pages/:pageName", + attachRollbackRouteHandlerWrapper(this.deleteUnlinkedPage) + ) + + return router + } +} + +module.exports = { UnlinkedPagesRouter } diff --git a/server.js b/server.js index 87a075bd9..a73479f26 100644 --- a/server.js +++ b/server.js @@ -56,6 +56,9 @@ const { GitHubService } = require("@services/db/GitHubService") const { CollectionPageService, } = require("@services/fileServices/MdPageServices/CollectionPageService") +const { + UnlinkedPageService, +} = require("@services/fileServices/MdPageServices/UnlinkedPageService") const { CollectionYmlService, } = require("@services/fileServices/YmlFileServices/CollectionYmlService") @@ -64,6 +67,7 @@ const { } = require("@services/fileServices/YmlFileServices/NavYmlService") const { CollectionPagesRouter } = require("./newroutes/collectionPages") +const { UnlinkedPagesRouter } = require("./newroutes/unlinkedPages") const gitHubService = new GitHubService({ axiosInstance }) const collectionYmlService = new CollectionYmlService({ gitHubService }) @@ -75,6 +79,8 @@ const subcollectionPageService = new SubcollectionPageService({ gitHubService, collectionYmlService, }) +const unlinkedPageService = new UnlinkedPageService({ gitHubService }) + const collectionController = new CollectionController({ collectionPageService, subcollectionPageService, @@ -82,6 +88,9 @@ const collectionController = new CollectionController({ const collectionPagesV2Router = new CollectionPagesRouter({ collectionController, }) +const unlinkedPagesRouter = new UnlinkedPagesRouter({ + unlinkedPageService, +}) const app = express() app.use(helmet()) @@ -125,6 +134,7 @@ app.use("/v1/sites", navigationRouter) app.use("/v1/sites", netlifyTomlRouter) app.use("/v2/sites", collectionPagesV2Router.getRouter()) +app.use("/v2/sites", unlinkedPagesRouter.getRouter()) // catch 404 and forward to error handler app.use((req, res, next) => { diff --git a/services/fileServices/MdPageServices/UnlinkedPageService.js b/services/fileServices/MdPageServices/UnlinkedPageService.js new file mode 100644 index 000000000..ac71bb9a2 --- /dev/null +++ b/services/fileServices/MdPageServices/UnlinkedPageService.js @@ -0,0 +1,85 @@ +const { + retrieveDataFromMarkdown, + convertDataToMarkdown, +} = require("@utils/markdown-utils") + +const UNLINKED_PAGES_DIRECTORY_NAME = "pages" + +class UnlinkedPageService { + constructor({ gitHubService }) { + this.gitHubService = gitHubService + } + + async create(reqDetails, { fileName, content, frontMatter }) { + // Ensure that third_nav_title is removed for files that are being moved from collections + delete frontMatter.third_nav_title + const newContent = convertDataToMarkdown(frontMatter, content) + const { sha } = await this.gitHubService.create(reqDetails, { + content: newContent, + fileName, + directoryName: UNLINKED_PAGES_DIRECTORY_NAME, + }) + return { fileName, content: { frontMatter, pageBody: content }, sha } + } + + async read(reqDetails, { fileName }) { + const { content: rawContent, sha } = await this.gitHubService.read( + reqDetails, + { + fileName, + directoryName: UNLINKED_PAGES_DIRECTORY_NAME, + } + ) + const { frontMatter, pageContent } = retrieveDataFromMarkdown(rawContent) + return { fileName, content: { frontMatter, pageBody: pageContent }, sha } + } + + async update(reqDetails, { fileName, content, frontMatter, sha }) { + const newContent = convertDataToMarkdown(frontMatter, content) + const { newSha } = await this.gitHubService.update(reqDetails, { + fileContent: newContent, + sha, + fileName, + directoryName: UNLINKED_PAGES_DIRECTORY_NAME, + }) + return { + fileName, + content: { frontMatter, pageBody: content }, + oldSha: sha, + newSha, + } + } + + async delete(reqDetails, { fileName, sha }) { + return this.gitHubService.delete(reqDetails, { + sha, + fileName, + directoryName: UNLINKED_PAGES_DIRECTORY_NAME, + }) + } + + async rename( + reqDetails, + { oldFileName, newFileName, content, frontMatter, sha } + ) { + const newContent = convertDataToMarkdown(frontMatter, content) + await this.gitHubService.delete(reqDetails, { + sha, + fileName: oldFileName, + directoryName: UNLINKED_PAGES_DIRECTORY_NAME, + }) + const { sha: newSha } = await this.gitHubService.create(reqDetails, { + content: newContent, + fileName: newFileName, + directoryName: UNLINKED_PAGES_DIRECTORY_NAME, + }) + return { + fileName: newFileName, + content: { frontMatter, pageBody: content }, + oldSha: sha, + newSha, + } + } +} + +module.exports = { UnlinkedPageService } diff --git a/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js b/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js new file mode 100644 index 000000000..7094a2f9f --- /dev/null +++ b/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js @@ -0,0 +1,168 @@ +describe("Unlinked Page Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + const fileName = "test-file" + const directoryName = "pages" + const mockContent = "test" + const mockMarkdownContent = "---test---" + const mockFrontMatter = { + title: "fileTitle", + permalink: "file/permalink", + } + const sha = "12345" + + const reqDetails = { siteName, accessToken } + + const mockGithubService = { + create: jest.fn(), + read: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + } + + jest.mock("@utils/markdown-utils", () => ({ + retrieveDataFromMarkdown: jest.fn().mockReturnValue({ + frontMatter: mockFrontMatter, + pageContent: mockContent, + }), + convertDataToMarkdown: jest.fn().mockReturnValue(mockMarkdownContent), + })) + const { + UnlinkedPageService, + } = require("@services/fileServices/MdPageServices/UnlinkedPageService") + const service = new UnlinkedPageService({ + gitHubService: mockGithubService, + }) + const { + retrieveDataFromMarkdown, + convertDataToMarkdown, + } = require("@utils/markdown-utils") + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("Create", () => { + mockGithubService.create.mockResolvedValue({ sha }) + it("Creating pages works correctly", async () => { + await expect( + service.create(reqDetails, { + fileName, + content: mockContent, + frontMatter: mockFrontMatter, + }) + ).resolves.toMatchObject({ + fileName, + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + sha, + }) + expect(convertDataToMarkdown).toHaveBeenCalledWith( + mockFrontMatter, + mockContent + ) + expect(mockGithubService.create).toHaveBeenCalledWith(reqDetails, { + content: mockMarkdownContent, + fileName, + directoryName, + }) + }) + }) + + describe("Read", () => { + mockGithubService.read.mockResolvedValue({ + content: mockMarkdownContent, + sha, + }), + it("Reading pages works correctly", async () => { + await expect( + service.read(reqDetails, { fileName }) + ).resolves.toMatchObject({ + fileName, + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + sha, + }) + expect(retrieveDataFromMarkdown).toHaveBeenCalledWith( + mockMarkdownContent + ) + expect(mockGithubService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + }) + }) + }) + + describe("Update", () => { + const oldSha = "54321" + mockGithubService.update.mockResolvedValue({ newSha: sha }) + it("Updating page content works correctly", async () => { + await expect( + service.update(reqDetails, { + fileName, + content: mockContent, + frontMatter: mockFrontMatter, + sha: oldSha, + }) + ).resolves.toMatchObject({ + fileName, + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + oldSha, + newSha: sha, + }) + expect(convertDataToMarkdown).toHaveBeenCalledWith( + mockFrontMatter, + mockContent + ) + expect(mockGithubService.update).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + fileContent: mockMarkdownContent, + sha: oldSha, + }) + }) + }) + + describe("Delete", () => { + it("Deleting pages works correctly", async () => { + await expect(service.delete(reqDetails, { fileName, sha })) + expect(mockGithubService.delete).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + sha, + }) + }) + }) + + describe("Rename", () => { + const oldSha = "54321" + const oldFileName = "test-old-file" + mockGithubService.create.mockResolvedValue({ sha }) + it("Renaming pages works correctly", async () => { + await expect( + service.rename(reqDetails, { + oldFileName, + newFileName: fileName, + content: mockContent, + frontMatter: mockFrontMatter, + sha: oldSha, + }) + ).resolves.toMatchObject({ + fileName, + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + oldSha, + newSha: sha, + }) + expect(mockGithubService.delete).toHaveBeenCalledWith(reqDetails, { + fileName: oldFileName, + directoryName, + sha: oldSha, + }) + expect(mockGithubService.create).toHaveBeenCalledWith(reqDetails, { + content: mockMarkdownContent, + fileName, + directoryName, + }) + }) + }) + + // TO-DO: Add tests for the list method +}) From 11d20143afa2339660f18cccc98232f1d8d9a715 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Tue, 24 Aug 2021 15:57:47 +0800 Subject: [PATCH 04/25] Refactor/remove controller (#275) * Refactor: have router link directly to page services * Fix: update router tests * Fix: swap mockReturnValue to mockResolvedValue for async tests * Refactor: remove collectionController class --- .eslintrc.json | 1 - controllers/CollectionController.js | 109 --------- .../__tests__/CollectionController.spec.js | 216 ------------------ jsconfig.json | 1 - newroutes/__tests__/CollectionPages.spec.js | 91 +++++--- newroutes/collectionPages.js | 103 +++++++-- package.json | 2 - server.js | 12 +- services/db/__tests__/GitHubService.spec.js | 28 +-- .../__tests__/CollectionPageService.spec.js | 8 +- .../SubcollectionPageService.spec.js | 8 +- .../__tests__/CollectionYmlService.spec.js | 36 +-- 12 files changed, 176 insertions(+), 439 deletions(-) delete mode 100644 controllers/CollectionController.js delete mode 100644 controllers/__tests__/CollectionController.spec.js diff --git a/.eslintrc.json b/.eslintrc.json index ca02f4cb0..f1e9b98d3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -75,7 +75,6 @@ ["@routes", "./routes"], ["@utils", "./utils"], ["@services", "./services"], - ["@controllers", "./controllers"], ["@validators", "./validators"] ] } diff --git a/controllers/CollectionController.js b/controllers/CollectionController.js deleted file mode 100644 index 342c7c8fd..000000000 --- a/controllers/CollectionController.js +++ /dev/null @@ -1,109 +0,0 @@ -class CollectionController { - constructor({ collectionPageService, subcollectionPageService }) { - this.collectionPageService = collectionPageService - this.subcollectionPageService = subcollectionPageService - } - - async createPage( - reqDetails, - { fileName, collectionName, subcollectionName, content, frontMatter } - ) { - if (subcollectionName) - return this.subcollectionPageService.create(reqDetails, { - fileName, - collectionName, - subcollectionName, - content, - frontMatter, - }) - return this.collectionPageService.create(reqDetails, { - fileName, - collectionName, - content, - frontMatter, - }) - } - - async readPage(reqDetails, { fileName, collectionName, subcollectionName }) { - if (subcollectionName) - return this.subcollectionPageService.read(reqDetails, { - fileName, - collectionName, - subcollectionName, - }) - return this.collectionPageService.read(reqDetails, { - fileName, - collectionName, - }) - } - - async updatePage( - reqDetails, - { - fileName, - newFileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - } - ) { - if (subcollectionName) { - if (newFileName) - return this.subcollectionPageService.rename(reqDetails, { - oldFileName: fileName, - newFileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - }) - return this.subcollectionPageService.update(reqDetails, { - fileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - }) - } - if (newFileName) - return this.collectionPageService.rename(reqDetails, { - oldFileName: fileName, - newFileName, - collectionName, - content, - frontMatter, - sha, - }) - return this.collectionPageService.update(reqDetails, { - fileName, - collectionName, - content, - frontMatter, - sha, - }) - } - - async deletePage( - reqDetails, - { fileName, collectionName, subcollectionName, sha } - ) { - if (subcollectionName) - return this.subcollectionPageService.delete(reqDetails, { - fileName, - collectionName, - subcollectionName, - sha, - }) - return this.collectionPageService.delete(reqDetails, { - fileName, - collectionName, - sha, - }) - } -} - -module.exports = { CollectionController } diff --git a/controllers/__tests__/CollectionController.spec.js b/controllers/__tests__/CollectionController.spec.js deleted file mode 100644 index 62e510d82..000000000 --- a/controllers/__tests__/CollectionController.spec.js +++ /dev/null @@ -1,216 +0,0 @@ -const { CollectionController } = require("@controllers/CollectionController") - -describe("Collection Controller", () => { - const mockCollectionPageService = { - create: jest.fn(), - read: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - rename: jest.fn(), - } - - const mockSubcollectionPageService = { - create: jest.fn(), - read: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - rename: jest.fn(), - } - - const controller = new CollectionController({ - collectionPageService: mockCollectionPageService, - subcollectionPageService: mockSubcollectionPageService, - }) - - const siteName = "test-site" - const accessToken = "test-token" - const fileName = "test-file" - const collectionName = "collection" - const subcollectionName = "subcollection" - const content = "test" - const frontMatter = { - title: "fileTitle", - permalink: "file/permalink", - } - const sha = "12345" - - const reqDetails = { siteName, accessToken } - - beforeEach(() => { - jest.clearAllMocks() - }) - - describe("CreatePage", () => { - it("Routes page creation to collection page service correctly", async () => { - await controller.createPage(reqDetails, { - fileName, - collectionName, - content, - frontMatter, - }) - expect(mockCollectionPageService.create).toHaveBeenCalledWith( - reqDetails, - { fileName, collectionName, content, frontMatter } - ) - }) - - it("Routes page creation to subcollection page service correctly", async () => { - await controller.createPage(reqDetails, { - fileName, - collectionName, - subcollectionName, - content, - frontMatter, - }) - expect(mockSubcollectionPageService.create).toHaveBeenCalledWith( - reqDetails, - { - fileName, - collectionName, - subcollectionName, - content, - frontMatter, - } - ) - }) - }) - - describe("ReadPage", () => { - it("Routes page reading to collection page service correctly", async () => { - await controller.readPage(reqDetails, { fileName, collectionName }) - expect(mockCollectionPageService.read).toHaveBeenCalledWith(reqDetails, { - fileName, - collectionName, - }) - }) - - it("Routes page reading to subcollection page service correctly", async () => { - await controller.readPage(reqDetails, { - fileName, - collectionName, - subcollectionName, - }) - expect(mockSubcollectionPageService.read).toHaveBeenCalledWith( - reqDetails, - { - fileName, - collectionName, - subcollectionName, - } - ) - }) - }) - - describe("UpdatePage", () => { - const newFileName = "test-new-file" - it("Routes page modification to collection page service correctly", async () => { - await controller.updatePage(reqDetails, { - fileName, - collectionName, - content, - frontMatter, - sha, - }) - expect(mockCollectionPageService.update).toHaveBeenCalledWith( - reqDetails, - { fileName, collectionName, content, frontMatter, sha } - ) - }) - - it("Routes page renaming to collection page service correctly", async () => { - await controller.updatePage(reqDetails, { - fileName, - newFileName, - collectionName, - content, - frontMatter, - sha, - }) - expect(mockCollectionPageService.rename).toHaveBeenCalledWith( - reqDetails, - { - oldFileName: fileName, - newFileName, - collectionName, - content, - frontMatter, - sha, - } - ) - }) - - it("Routes page modification to subcollection page service correctly", async () => { - await controller.updatePage(reqDetails, { - fileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - }) - expect(mockSubcollectionPageService.update).toHaveBeenCalledWith( - reqDetails, - { - fileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - } - ) - }) - - it("Routes page renaming to subcollection page service correctly", async () => { - await controller.updatePage(reqDetails, { - fileName, - newFileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - }) - expect(mockSubcollectionPageService.rename).toHaveBeenCalledWith( - reqDetails, - { - oldFileName: fileName, - newFileName, - collectionName, - subcollectionName, - content, - frontMatter, - sha, - } - ) - }) - }) - - describe("DeletePage", () => { - it("Routes page deletion to collection page service correctly", async () => { - await controller.deletePage(reqDetails, { fileName, collectionName, sha }) - expect(mockCollectionPageService.delete).toHaveBeenCalledWith( - reqDetails, - { fileName, collectionName, sha } - ) - }) - - it("Routes page deletion to subcollection page service correctly", async () => { - await controller.deletePage(reqDetails, { - fileName, - collectionName, - subcollectionName, - sha, - }) - expect(mockSubcollectionPageService.delete).toHaveBeenCalledWith( - reqDetails, - { - fileName, - collectionName, - subcollectionName, - sha, - } - ) - }) - }) -}) diff --git a/jsconfig.json b/jsconfig.json index 6a9afdada..478859c7f 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -13,7 +13,6 @@ "@routes/*": ["./routes/*"], "@utils/*": ["./utils/*"], "@services/*": ["./services/*"], - "@controllers/*": ["./controllers/*"], "@validators/*": ["./validators/*"], } }, diff --git a/newroutes/__tests__/CollectionPages.spec.js b/newroutes/__tests__/CollectionPages.spec.js index d701dec0a..254fe874a 100644 --- a/newroutes/__tests__/CollectionPages.spec.js +++ b/newroutes/__tests__/CollectionPages.spec.js @@ -7,15 +7,25 @@ const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") const { CollectionPagesRouter } = require("../collectionPages") describe("Collection Pages Router", () => { - const mockController = { - createPage: jest.fn(), - readPage: jest.fn(), - updatePage: jest.fn(), - deletePage: jest.fn(), + const mockCollectionPageService = { + create: jest.fn(), + read: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + rename: jest.fn(), + } + + const mockSubcollectionPageService = { + create: jest.fn(), + read: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + rename: jest.fn(), } const router = new CollectionPagesRouter({ - collectionController: mockController, + collectionPageService: mockCollectionPageService, + subcollectionPageService: mockSubcollectionPageService, }) const app = express() @@ -91,7 +101,7 @@ describe("Collection Pages Router", () => { }) it("accepts valid collection page create requests and returns the details of the file created", async () => { - const expectedControllerInput = { + const expectedServiceInput = { fileName: pageDetails.newFileName, collectionName, content: pageDetails.content.pageBody, @@ -101,14 +111,14 @@ describe("Collection Pages Router", () => { .post(`/${siteName}/collections/${collectionName}/pages`) .send(pageDetails) .expect(200) - expect(mockController.createPage).toHaveBeenCalledWith( + expect(mockCollectionPageService.create).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) it("accepts valid subcollection page create requests and returns the details of the file created", async () => { - const expectedControllerInput = { + const expectedServiceInput = { fileName: pageDetails.newFileName, collectionName, subcollectionName, @@ -121,28 +131,32 @@ describe("Collection Pages Router", () => { ) .send(pageDetails) .expect(200) - expect(mockController.createPage).toHaveBeenCalledWith( + expect(mockSubcollectionPageService.create).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) }) describe("readCollectionPage", () => { - mockController.readPage.mockReturnValue({ + const expectedResponse = { sha: mockSha, content: mockContent, - }) + } + mockCollectionPageService.read.mockResolvedValue(expectedResponse) + + mockSubcollectionPageService.read.mockResolvedValue(expectedResponse) it("retrieves collection page details", async () => { const expectedControllerInput = { fileName, collectionName, } - await request(app) + const resp = await request(app) .get(`/${siteName}/collections/${collectionName}/pages/${fileName}`) .expect(200) - expect(mockController.readPage).toHaveBeenCalledWith( + expect(resp.body).toStrictEqual(expectedResponse) + expect(mockCollectionPageService.read).toHaveBeenCalledWith( reqDetails, expectedControllerInput ) @@ -154,12 +168,13 @@ describe("Collection Pages Router", () => { collectionName, subcollectionName, } - await request(app) + const resp = await request(app) .get( `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}/pages/${fileName}` ) .expect(200) - expect(mockController.readPage).toHaveBeenCalledWith( + expect(resp.body).toStrictEqual(expectedResponse) + expect(mockSubcollectionPageService.read).toHaveBeenCalledWith( reqDetails, expectedControllerInput ) @@ -191,7 +206,7 @@ describe("Collection Pages Router", () => { }) it("accepts valid collection page update requests and returns the details of the file updated", async () => { - const expectedControllerInput = { + const expectedServiceInput = { fileName, collectionName, content: updatePageDetails.content.pageBody, @@ -202,15 +217,15 @@ describe("Collection Pages Router", () => { .post(`/${siteName}/collections/${collectionName}/pages/${fileName}`) .send(updatePageDetails) .expect(200) - expect(mockController.updatePage).toHaveBeenCalledWith( + expect(mockCollectionPageService.update).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) it("accepts valid collection page rename requests and returns the details of the file updated", async () => { - const expectedControllerInput = { - fileName, + const expectedServiceInput = { + oldFileName: fileName, newFileName: renamePageDetails.newFileName, collectionName, content: renamePageDetails.content.pageBody, @@ -221,14 +236,14 @@ describe("Collection Pages Router", () => { .post(`/${siteName}/collections/${collectionName}/pages/${fileName}`) .send(renamePageDetails) .expect(200) - expect(mockController.updatePage).toHaveBeenCalledWith( + expect(mockCollectionPageService.rename).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) it("accepts valid subcollection page update requests and returns the details of the file updated", async () => { - const expectedControllerInput = { + const expectedServiceInput = { fileName, collectionName, subcollectionName, @@ -242,15 +257,15 @@ describe("Collection Pages Router", () => { ) .send(updatePageDetails) .expect(200) - expect(mockController.updatePage).toHaveBeenCalledWith( + expect(mockSubcollectionPageService.update).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) it("accepts valid subcollection page rename requests and returns the details of the file updated", async () => { - const expectedControllerInput = { - fileName, + const expectedServiceInput = { + oldFileName: fileName, newFileName: renamePageDetails.newFileName, collectionName, subcollectionName, @@ -264,9 +279,9 @@ describe("Collection Pages Router", () => { ) .send(renamePageDetails) .expect(200) - expect(mockController.updatePage).toHaveBeenCalledWith( + expect(mockSubcollectionPageService.rename).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) }) @@ -284,7 +299,7 @@ describe("Collection Pages Router", () => { }) it("accepts valid collection page delete requests", async () => { - const expectedControllerInput = { + const expectedServiceInput = { fileName, collectionName, sha: pageDetails.sha, @@ -293,14 +308,14 @@ describe("Collection Pages Router", () => { .delete(`/${siteName}/collections/${collectionName}/pages/${fileName}`) .send(pageDetails) .expect(200) - expect(mockController.deletePage).toHaveBeenCalledWith( + expect(mockCollectionPageService.delete).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) it("accepts valid subcollection page delete requests", async () => { - const expectedControllerInput = { + const expectedServiceInput = { fileName, collectionName, subcollectionName, @@ -312,9 +327,9 @@ describe("Collection Pages Router", () => { ) .send(pageDetails) .expect(200) - expect(mockController.deletePage).toHaveBeenCalledWith( + expect(mockSubcollectionPageService.delete).toHaveBeenCalledWith( reqDetails, - expectedControllerInput + expectedServiceInput ) }) }) diff --git a/newroutes/collectionPages.js b/newroutes/collectionPages.js index 7ca4bfed1..ebb4a932a 100644 --- a/newroutes/collectionPages.js +++ b/newroutes/collectionPages.js @@ -17,8 +17,9 @@ const { } = require("@validators/RequestSchema") class CollectionPagesRouter { - constructor({ collectionController }) { - this.collectionController = collectionController + constructor({ collectionPageService, subcollectionPageService }) { + this.collectionPageService = collectionPageService + this.subcollectionPageService = subcollectionPageService // We need to bind all methods because we don't invoke them from the class directly autoBind(this) } @@ -34,16 +35,24 @@ class CollectionPagesRouter { content: { frontMatter, pageBody }, newFileName, } = req.body - const createResp = await this.collectionController.createPage( - { siteName, accessToken }, - { + const reqDetails = { siteName, accessToken } + let createResp + if (subcollectionName) { + createResp = await this.subcollectionPageService.create(reqDetails, { fileName: newFileName, collectionName, content: pageBody, frontMatter, subcollectionName, - } - ) + }) + } else { + createResp = await this.collectionPageService.create(reqDetails, { + fileName: newFileName, + collectionName, + content: pageBody, + frontMatter, + }) + } return res.status(200).json(createResp) } @@ -53,12 +62,22 @@ class CollectionPagesRouter { const { accessToken } = req const { siteName, pageName, collectionName, subcollectionName } = req.params - const { sha, content } = await this.collectionController.readPage( - { siteName, accessToken }, - { fileName: pageName, collectionName, subcollectionName } - ) - return res.status(200).json({ collectionName, pageName, sha, content }) + const reqDetails = { siteName, accessToken } + let readResp + if (subcollectionName) { + readResp = await this.subcollectionPageService.read(reqDetails, { + fileName: pageName, + collectionName, + subcollectionName, + }) + } else { + readResp = await this.collectionPageService.read(reqDetails, { + fileName: pageName, + collectionName, + }) + } + return res.status(200).json(readResp) } // Update page in collection @@ -73,18 +92,48 @@ class CollectionPagesRouter { sha, newFileName, } = req.body - const updateResp = await this.collectionController.updatePage( - { siteName, accessToken }, - { - fileName: pageName, + const reqDetails = { siteName, accessToken } + let updateResp + if (subcollectionName) { + if (newFileName) { + updateResp = await this.subcollectionPageService.rename(reqDetails, { + oldFileName: pageName, + newFileName, + collectionName, + subcollectionName, + content: pageBody, + frontMatter, + sha, + }) + } else { + updateResp = await this.subcollectionPageService.update(reqDetails, { + fileName: pageName, + collectionName, + subcollectionName, + content: pageBody, + frontMatter, + sha, + }) + } + } + if (newFileName) { + updateResp = await this.collectionPageService.rename(reqDetails, { + oldFileName: pageName, newFileName, collectionName, - subcollectionName, content: pageBody, frontMatter, sha, - } - ) + }) + } else { + updateResp = await this.collectionPageService.update(reqDetails, { + fileName: pageName, + collectionName, + content: pageBody, + frontMatter, + sha, + }) + } return res.status(200).json(updateResp) } @@ -97,15 +146,21 @@ class CollectionPagesRouter { const { error } = DeletePageRequestSchema.validate(req.body) if (error) throw new BadRequestError(error) const { sha } = req.body - await this.collectionController.deletePage( - { siteName, accessToken }, - { + const reqDetails = { siteName, accessToken } + if (subcollectionName) { + await this.subcollectionPageService.delete(reqDetails, { fileName: pageName, collectionName, subcollectionName, sha, - } - ) + }) + } else { + await this.collectionPageService.delete(reqDetails, { + fileName: pageName, + collectionName, + sha, + }) + } return res.status(200).send("OK") } diff --git a/package.json b/package.json index 2596e38d1..7927a06c1 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "@routes": "routes", "@utils": "utils", "@services": "services", - "@controllers": "controllers", "@validators": "validators" }, "jest": { @@ -88,7 +87,6 @@ "^@routes/(.*)": "/routes/$1", "^@utils/(.*)": "/utils/$1", "^@services/(.*)": "/services/$1", - "^@controllers/(.*)": "/controllers/$1", "^@validators/(.*)": "/validators/$1" } }, diff --git a/server.js b/server.js index a73479f26..6d566e03b 100644 --- a/server.js +++ b/server.js @@ -48,7 +48,6 @@ axiosInstance.interceptors.request.use((config) => ({ }, })) -const { CollectionController } = require("@controllers/CollectionController") const { SubcollectionPageService, } = require("@root/services/fileServices/MdPageServices/SubcollectionPageService") @@ -81,16 +80,13 @@ const subcollectionPageService = new SubcollectionPageService({ }) const unlinkedPageService = new UnlinkedPageService({ gitHubService }) -const collectionController = new CollectionController({ - collectionPageService, - subcollectionPageService, -}) -const collectionPagesV2Router = new CollectionPagesRouter({ - collectionController, -}) const unlinkedPagesRouter = new UnlinkedPagesRouter({ unlinkedPageService, }) +const collectionPagesV2Router = new CollectionPagesRouter({ + collectionPageService, + subcollectionPageService, +}) const app = express() app.use(helmet()) diff --git a/services/db/__tests__/GitHubService.spec.js b/services/db/__tests__/GitHubService.spec.js index 0caad6386..bf0308933 100644 --- a/services/db/__tests__/GitHubService.spec.js +++ b/services/db/__tests__/GitHubService.spec.js @@ -112,7 +112,7 @@ describe("Github Service", () => { }, }, } - mockAxiosInstance.put.mockReturnValueOnce(resp) + mockAxiosInstance.put.mockResolvedValueOnce(resp) await expect( service.create(reqDetails, { content, @@ -166,7 +166,7 @@ describe("Github Service", () => { sha, }, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.read(reqDetails, { fileName, @@ -187,7 +187,7 @@ describe("Github Service", () => { const resp = { status: 404, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.read(reqDetails, { fileName, @@ -213,7 +213,7 @@ describe("Github Service", () => { const resp = { data, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.readDirectory(reqDetails, { fileName, @@ -231,7 +231,7 @@ describe("Github Service", () => { const resp = { status: 404, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.readDirectory(reqDetails, { fileName, @@ -264,7 +264,7 @@ describe("Github Service", () => { }, }, } - mockAxiosInstance.put.mockReturnValueOnce(resp) + mockAxiosInstance.put.mockResolvedValueOnce(resp) await expect( service.update(reqDetails, { fileName, @@ -322,8 +322,8 @@ describe("Github Service", () => { const readParams = { ref: BRANCH_REF, } - mockAxiosInstance.get.mockReturnValueOnce(getResp) - mockAxiosInstance.put.mockReturnValueOnce(putResp) + mockAxiosInstance.get.mockResolvedValueOnce(getResp) + mockAxiosInstance.put.mockResolvedValueOnce(putResp) await expect( service.update(reqDetails, { fileName, @@ -352,7 +352,7 @@ describe("Github Service", () => { const resp = { status: 404, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.update(reqDetails, { fileName, @@ -431,7 +431,7 @@ describe("Github Service", () => { }, ], } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await service.getRepoState(reqDetails) expect(mockAxiosInstance.get).toHaveBeenCalledWith(endpoint, { params, @@ -459,7 +459,7 @@ describe("Github Service", () => { tree, }, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.getTree({ accessToken, siteName, treeSha: sha }, {}) ).resolves.toEqual(tree) @@ -475,7 +475,7 @@ describe("Github Service", () => { tree, }, } - mockAxiosInstance.get.mockReturnValueOnce(resp) + mockAxiosInstance.get.mockResolvedValueOnce(resp) await expect( service.getTree( { accessToken, siteName, treeSha: sha }, @@ -513,8 +513,8 @@ describe("Github Service", () => { }, } mockAxiosInstance.post - .mockReturnValueOnce(firstResp) - .mockReturnValueOnce(secondResp) + .mockResolvedValueOnce(firstResp) + .mockResolvedValueOnce(secondResp) await expect( service.updateTree( { accessToken, siteName, currentCommitSha: sha }, diff --git a/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js b/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js index 0a4b745a3..cfef7b5fe 100644 --- a/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js +++ b/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js @@ -52,7 +52,7 @@ describe("Collection Page Service", () => { }) describe("Create", () => { - mockGithubService.create.mockReturnValue({ sha }) + mockGithubService.create.mockResolvedValue({ sha }) it("Creating pages works correctly", async () => { await expect( service.create(reqDetails, { @@ -114,7 +114,7 @@ describe("Collection Page Service", () => { }) describe("Read", () => { - mockGithubService.read.mockReturnValue({ + mockGithubService.read.mockResolvedValue({ content: mockMarkdownContent, sha, }), @@ -138,7 +138,7 @@ describe("Collection Page Service", () => { describe("Update", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValue({ newSha: sha }) + mockGithubService.update.mockResolvedValue({ newSha: sha }) it("Updating page content works correctly", async () => { await expect( service.update(reqDetails, { @@ -190,7 +190,7 @@ describe("Collection Page Service", () => { describe("Rename", () => { const oldSha = "54321" const oldFileName = "test-old-file" - mockGithubService.create.mockReturnValue({ sha }) + mockGithubService.create.mockResolvedValue({ sha }) it("Renaming pages works correctly", async () => { await expect( service.rename(reqDetails, { diff --git a/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js b/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js index 8f9a99c2e..ec86aed55 100644 --- a/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js +++ b/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js @@ -56,7 +56,7 @@ describe("Subcollection Page Service", () => { }) describe("Create", () => { - mockGithubService.create.mockReturnValue({ sha }) + mockGithubService.create.mockResolvedValue({ sha }) it("Creating pages works correctly", async () => { await expect( service.create(reqDetails, { @@ -120,7 +120,7 @@ describe("Subcollection Page Service", () => { }) describe("Read", () => { - mockGithubService.read.mockReturnValue({ + mockGithubService.read.mockResolvedValue({ content: mockMarkdownContent, sha, }), @@ -148,7 +148,7 @@ describe("Subcollection Page Service", () => { describe("Update", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValue({ newSha: sha }) + mockGithubService.update.mockResolvedValue({ newSha: sha }) it("Updating page content works correctly", async () => { await expect( service.update(reqDetails, { @@ -206,7 +206,7 @@ describe("Subcollection Page Service", () => { describe("Rename", () => { const oldSha = "54321" const oldFileName = "test-old-file" - mockGithubService.create.mockReturnValue({ sha }) + mockGithubService.create.mockResolvedValue({ sha }) it("Renaming pages works correctly", async () => { await expect( service.rename(reqDetails, { diff --git a/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js b/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js index 05217be73..0fdbadadf 100644 --- a/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js +++ b/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js @@ -50,7 +50,7 @@ describe("Collection Yml Service", () => { }) describe("Read", () => { - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha, }), @@ -67,7 +67,7 @@ describe("Collection Yml Service", () => { describe("Update", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) it("Updating raw content works correctly", async () => { await expect( service.update(reqDetails, { @@ -89,7 +89,7 @@ describe("Collection Yml Service", () => { describe("Create", () => { beforeEach(() => { - mockGithubService.create.mockReturnValueOnce({ sha }) + mockGithubService.create.mockResolvedValueOnce({ sha }) }) it("Creating a collection.yml file with no specified files works correctly", async () => { const content = yaml.stringify({ @@ -139,7 +139,7 @@ describe("Collection Yml Service", () => { }) describe("ListContents", () => { - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha, }) @@ -155,8 +155,8 @@ describe("Collection Yml Service", () => { describe("AddItemToOrder", () => { const oldSha = "54321" beforeEach(() => { - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) @@ -283,8 +283,8 @@ describe("Collection Yml Service", () => { describe("DeleteItemFromOrder", () => { const oldSha = "54321" beforeEach(() => { - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) @@ -352,8 +352,8 @@ describe("Collection Yml Service", () => { describe("UpdateItemInOrder", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) @@ -387,8 +387,8 @@ describe("Collection Yml Service", () => { describe("RenameCollectionInOrder", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) @@ -420,8 +420,8 @@ describe("Collection Yml Service", () => { describe("DeleteSubfolderFromOrder", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) @@ -452,8 +452,8 @@ describe("Collection Yml Service", () => { describe("RenameSubfolderInOrder", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) @@ -486,8 +486,8 @@ describe("Collection Yml Service", () => { describe("UpdateOrder", () => { const oldSha = "54321" - mockGithubService.update.mockReturnValueOnce({ newSha: sha }) - mockGithubService.read.mockReturnValueOnce({ + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + mockGithubService.read.mockResolvedValueOnce({ content: mockRawContent, sha: oldSha, }) From 28ab94c4e71996fa761c20ed94819bf63d784891 Mon Sep 17 00:00:00 2001 From: kwajiehao <31984694+kwajiehao@users.noreply.github.com> Date: Thu, 2 Sep 2021 09:54:13 +0800 Subject: [PATCH 05/25] test: e2e authentication scheme (#270) * test: add alternate authentication scheme for e2e tests This commit adds an alternate authentication scheme for e2e tests. It checks for an E2E test secret, which is shared with the frontend, and ensures that the repo being modified for these tests is only the E2E test repo. This is made possible by the implementation of Cypress on the frontend: secrets associated with Cypress (such as COOKIE_NAME, or BASEURL) are not bundled together with the deployment package, which means we are free to use a shared secret string to authenticate the frontend with the backend. This commit also introduces new env vars for authentication of E2E tests: E2E_TEST_REPO, and E2E_TEST_SECRET. * test: add E2E test github token This commit adds an environment variable, E2E_TEST_GH_TOKEN, which is a GitHub token which is used to make requests to the GitHub API on behalf of the E2E test user. * test: fix error in e2e validation logic * chore: clarify e2e validation checks * chore: add documentation * chore: rename variable for clarity --- .env-example | 10 ++++- README | 9 +++++ middleware/auth.js | 99 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 README diff --git a/.env-example b/.env-example index b94b7a5e7..b565d7309 100644 --- a/.env-example +++ b/.env-example @@ -11,7 +11,15 @@ export GITHUB_ORG_NAME="isomerpages" export GITHUB_BUILD_ORG_NAME="opengovsg" export GITHUB_BUILD_REPO_NAME="isomer-build" export MUTEX_TABLE_NAME="" +export E2E_TEST_REPO="e2e-test-repo" +export E2E_TEST_SECRET="blahblahblah" +export E2E_TEST_GH_TOKEN="" # Required to connect to DynamoDB export AWS_ACCESS_KEY_ID="" -export AWS_SECRET_ACCESS_KEY="" \ No newline at end of file +export AWS_SECRET_ACCESS_KEY="" + +# Required to run end-to-end tests +export E2E_TEST_REPO="e2e-test-repo" +export E2E_TEST_SECRET="" +export E2E_TEST_GH_TOKEN="" \ No newline at end of file diff --git a/README b/README new file mode 100644 index 000000000..dcff95505 --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +## E2E Tests +To run the E2E tests successfully, you will need to define the following environment variables: +``` +export E2E_TEST_REPO="e2e-test-repo" +export E2E_TEST_SECRET="blahblahblah" // this should match the value of CYPRESS_COOKIE_VALUE on +// the frontend +export E2E_TEST_GH_TOKEN="" // this can be your own personal GH access token, or the token from our +// specialized E2E test user +``` \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js index b20be3227..43e151cb1 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -9,47 +9,96 @@ const { AuthError } = require("@errors/AuthError") const jwtUtils = require("@utils/jwt-utils") +const { BadRequestError } = require("@root/errors/BadRequestError") + // Instantiate router object const auth = express.Router() +const { E2E_TEST_REPO, E2E_TEST_SECRET, E2E_TEST_GH_TOKEN } = process.env +const E2E_TEST_USER = "e2e-test" +const GENERAL_ACCESS_PATHS = ["/v1/sites", "/v1/auth/whoami"] + function noVerify(req, res, next) { next("router") } -const verifyJwt = (req, res, next) => { - try { - const { isomercms } = req.cookies - const { - access_token: retrievedToken, - user_id: retrievedId, - } = jwtUtils.verifyToken(isomercms) - req.accessToken = jwtUtils.decryptToken(retrievedToken) - req.userId = retrievedId - } catch (err) { - logger.error("Authentication error") - if (err.name === "TokenExpiredError") { - throw new AuthError("JWT token has expired") +function verifyE2E(req) { + const { isomercmsE2E } = req.cookies + const urlTokens = req.url.split("/") // urls take the form "/v1/sites//"" + let isValidE2E + + if (isomercmsE2E) { + if (isomercmsE2E !== E2E_TEST_SECRET) throw new AuthError("Bad credentials") + + // Throw an error if accessing a repo other than e2e-test-repo + // Otherwise, allow access only to paths available to all users + if (!GENERAL_ACCESS_PATHS.includes(req.url)) { + if (urlTokens.length >= 3) { + const repo = urlTokens[3] + if (repo !== E2E_TEST_REPO) + throw new AuthError( + `E2E tests can only access the ${E2E_TEST_REPO} repo` + ) + } else { + throw new BadRequestError("Invalid path") + } } - if (err.name === "JsonWebTokenError") { - throw new AuthError(err.message) + + isValidE2E = true + } + + return isValidE2E +} + +const verifyJwt = (req, res, next) => { + const { isomercms } = req.cookies + const isValidE2E = verifyE2E(req) + + if (isValidE2E) { + req.accessToken = E2E_TEST_GH_TOKEN + req.userId = E2E_TEST_USER + } else { + try { + const { + access_token: retrievedToken, + user_id: retrievedId, + } = jwtUtils.verifyToken(isomercms) + req.accessToken = jwtUtils.decryptToken(retrievedToken) + req.userId = retrievedId + } catch (err) { + logger.error("Authentication error") + if (err.name === "TokenExpiredError") { + throw new AuthError("JWT token has expired") + } + if (err.name === "JsonWebTokenError") { + throw new AuthError(err.message) + } + throw new Error(err) } - throw new Error(err) } return next("router") } // Extracts access_token if any, else set access_token to null const whoamiAuth = (req, res, next) => { - let retrievedToken - try { - const { isomercms } = req.cookies - const { access_token: verifiedToken } = jwtUtils.verifyToken(isomercms) - retrievedToken = jwtUtils.decryptToken(verifiedToken) - } catch (err) { - retrievedToken = undefined - } finally { - req.accessToken = retrievedToken + const isValidE2E = verifyE2E(req) + + if (isValidE2E) { + req.accessToken = E2E_TEST_GH_TOKEN + req.userId = E2E_TEST_USER + } else { + let retrievedToken + try { + const { isomercms } = req.cookies + const { access_token: verifiedToken } = jwtUtils.verifyToken(isomercms) + retrievedToken = jwtUtils.decryptToken(verifiedToken) + } catch (err) { + retrievedToken = undefined + } finally { + req.accessToken = retrievedToken + } } + return next("router") } From 22b10e08c3f0aa1d5404965f136546076cb26b10 Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Tue, 7 Sep 2021 14:51:47 +0800 Subject: [PATCH 06/25] fix: update ebextension to fix EB deploy --- .ebextensions/nginx-max-body-size.config | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.ebextensions/nginx-max-body-size.config b/.ebextensions/nginx-max-body-size.config index fdf7dacca..7788a9626 100644 --- a/.ebextensions/nginx-max-body-size.config +++ b/.ebextensions/nginx-max-body-size.config @@ -4,8 +4,4 @@ files: owner: root group: root content: | - client_max_body_size 10M; - -commands: - 00_reload_nginx: - command: "service nginx reload" \ No newline at end of file + client_max_body_size 10M; \ No newline at end of file From 28f23c5e14ad84d83890b079ee54703a0d77f2d4 Mon Sep 17 00:00:00 2001 From: Lam Kee Wei Date: Wed, 8 Sep 2021 13:45:17 +0800 Subject: [PATCH 07/25] chore: use new cicd user creds --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0c8ea01b..ad564ae89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,8 +99,8 @@ jobs: - name: Deploy to EB uses: opengovsg/beanstalk-deploy@v11 with: - aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID_FOR_DEPLOYMENT }} - aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY_FOR_DEPLOYMENT }} + aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID_FOR_CICD }} + aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY_FOR_CICD }} application_name: ${{ steps.select_eb_vars.outputs.eb_app }} environment_name: ${{ steps.select_eb_vars.outputs.eb_env }} version_description: ${{ steps.get_desc.output.desc }} From 9b4ada0f249c0ed3310d669947561911fb28e7fe Mon Sep 17 00:00:00 2001 From: kwajiehao Date: Wed, 8 Sep 2021 16:22:07 +0800 Subject: [PATCH 08/25] chore: add health check endpoint --- middleware/auth.js | 3 +++ server.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/middleware/auth.js b/middleware/auth.js index b20be3227..ee46fca26 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -53,6 +53,9 @@ const whoamiAuth = (req, res, next) => { return next("router") } +// Health check +auth.get("/v2/ping", noVerify) + // Login and logout auth.get("/v1/auth/github-redirect", noVerify) auth.get("/v1/auth", noVerify) diff --git a/server.js b/server.js index 87a075bd9..ff4bb0a83 100644 --- a/server.js +++ b/server.js @@ -126,6 +126,8 @@ app.use("/v1/sites", netlifyTomlRouter) app.use("/v2/sites", collectionPagesV2Router.getRouter()) +app.use("/v2/ping", (req, res, next) => res.status(200).send("Ok")) + // catch 404 and forward to error handler app.use((req, res, next) => { next(createError(404)) From 07298940674ed0d9f7313ee09445259d968ec7cc Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Thu, 9 Sep 2021 22:21:31 +0800 Subject: [PATCH 09/25] chore: use EB platform hooks instead of ebextensions --- .ebextensions/nginx-max-body-size.config | 7 ------- .platform/nginx/conf.d/proxy.conf | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 .ebextensions/nginx-max-body-size.config create mode 100644 .platform/nginx/conf.d/proxy.conf diff --git a/.ebextensions/nginx-max-body-size.config b/.ebextensions/nginx-max-body-size.config deleted file mode 100644 index 7788a9626..000000000 --- a/.ebextensions/nginx-max-body-size.config +++ /dev/null @@ -1,7 +0,0 @@ -files: - "/etc/nginx/conf.d/proxy.conf" : - mode: "000755" - owner: root - group: root - content: | - client_max_body_size 10M; \ No newline at end of file diff --git a/.platform/nginx/conf.d/proxy.conf b/.platform/nginx/conf.d/proxy.conf new file mode 100644 index 000000000..006d51170 --- /dev/null +++ b/.platform/nginx/conf.d/proxy.conf @@ -0,0 +1 @@ +client_max_body_size 10M; \ No newline at end of file From 04733a4335f135f7d592f94e9d21ac81d0d90fe5 Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Thu, 9 Sep 2021 22:21:46 +0800 Subject: [PATCH 10/25] chore: specify node version --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2596e38d1..3d0fdcdf0 100644 --- a/package.json +++ b/package.json @@ -97,5 +97,6 @@ "eslint --fix", "prettier --write" ] - } + }, + "engines": { "node" : ">=14.17.5" } } From 95c01d9c2172255614c893a390dc7746735b8515 Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Thu, 9 Sep 2021 22:21:59 +0800 Subject: [PATCH 11/25] ci: include .platform dir in deployment --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad564ae89..f3c7a7b04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - name: Install NPM run: npm ci - name: Zip application - run: zip -r "deploy.zip" * .ebextensions -x .env-example .gitignore package-lock.json + run: zip -r "deploy.zip" * .ebextensions .platform -x .env-example .gitignore package-lock.json - name: Get timestamp shell: bash run: echo "##[set-output name=timestamp;]$(env TZ=Asia/Singapore date '+%Y%m%d%H%M%S')" From aaa74b007c8a614dcdd132dc7be237686d26ac7c Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Fri, 10 Sep 2021 10:15:14 +0800 Subject: [PATCH 12/25] ci: update production eb env name (#298) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3c7a7b04..e78f7699e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ env: STAGING_BRANCH: refs/heads/staging DEV_BRANCH: refs/heads/staging-dev EB_APP: isomer-cms - EB_ENV_PRODUCTION: isomercms-backend-prod + EB_ENV_PRODUCTION: cms-backend-prod EB_ENV_STAGING: isomercms-backend-staging EB_ENV_DEV: isomercms-backend-staging-dev COMMIT_MESSAGE: ${{ github.event.head_commit.message }} From 103706e4b4ca8de923cae9d1940cf9e2408baf28 Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Fri, 10 Sep 2021 10:20:49 +0800 Subject: [PATCH 13/25] ci: update backend staging eb env --- .github/workflows/ci.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e78f7699e..aa7b80659 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,11 +6,9 @@ on: env: PRODUCTION_BRANCH: refs/heads/master STAGING_BRANCH: refs/heads/staging - DEV_BRANCH: refs/heads/staging-dev EB_APP: isomer-cms EB_ENV_PRODUCTION: cms-backend-prod - EB_ENV_STAGING: isomercms-backend-staging - EB_ENV_DEV: isomercms-backend-staging-dev + EB_ENV_STAGING: cms-backend-staging COMMIT_MESSAGE: ${{ github.event.head_commit.message }} jobs: gatekeep: @@ -27,8 +25,7 @@ jobs: ref = os.environ['GITHUB_REF'] prod = os.environ['PRODUCTION_BRANCH'] staging = os.environ['STAGING_BRANCH'] - dev = os.environ['DEV_BRANCH'] - if ref == prod or ref == staging or ref == dev: + if ref == prod or ref == staging: print('::set-output name=proceed::true') else: print('::set-output name=proceed::false') @@ -81,20 +78,15 @@ jobs: branch = os.environ['GITHUB_REF'] production = os.environ['PRODUCTION_BRANCH'] staging = os.environ['STAGING_BRANCH'] - dev = os.environ['DEV_BRANCH'] eb_app = os.environ['EB_APP'] eb_env_production = os.environ['EB_ENV_PRODUCTION'] eb_env_staging = os.environ['EB_ENV_STAGING'] - eb_env_dev = os.environ['EB_ENV_DEV'] if branch == production: print('::set-output name=eb_app::' + eb_app) print('::set-output name=eb_env::' + eb_env_production) elif branch == staging: print('::set-output name=eb_app::' + eb_app) print('::set-output name=eb_env::' + eb_env_staging) - elif branch == dev: - print('::set-output name=eb_app::' + eb_app) - print('::set-output name=eb_env::' + eb_env_dev) id: select_eb_vars - name: Deploy to EB uses: opengovsg/beanstalk-deploy@v11 From 9a471444d405180e042da2301a32fe02d39ababa Mon Sep 17 00:00:00 2001 From: Preston Lim Date: Fri, 10 Sep 2021 10:25:02 +0800 Subject: [PATCH 14/25] ci: remove .ebextensions from zip --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa7b80659..e0eca591f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: - name: Install NPM run: npm ci - name: Zip application - run: zip -r "deploy.zip" * .ebextensions .platform -x .env-example .gitignore package-lock.json + run: zip -r "deploy.zip" * .platform -x .env-example .gitignore package-lock.json - name: Get timestamp shell: bash run: echo "##[set-output name=timestamp;]$(env TZ=Asia/Singapore date '+%Y%m%d%H%M%S')" From 1b79f8bb7fa8a4d72a1f60e11a3aa8bc81b6e3a8 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Wed, 15 Sep 2021 17:00:10 +0800 Subject: [PATCH 15/25] Feat: add ability to edit description in settings (#280) --- classes/Settings.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/classes/Settings.js b/classes/Settings.js index fb6de8367..bee68f202 100644 --- a/classes/Settings.js +++ b/classes/Settings.js @@ -99,6 +99,7 @@ class Settings { const configFieldsRequired = { url: configContent.url, title: configContent.title, + description: configContent.description, favicon: configContent.favicon, shareicon: configContent.shareicon, is_government: configContent.is_government, @@ -211,11 +212,16 @@ class Settings { const newConfigContent = Base64.encode(yaml.stringify(configSettingsObj)) await configResp.update(newConfigContent, config.sha) - // Update title in homepage as well if it's changed - if (configContent.title !== configSettingsObj.title) { + // Update title and description in homepage as well if it's changed + const hasTitleChanged = configContent.title !== configSettingsObj.title + const hasDescriptionChanged = + configContent.description !== configSettingsObj.description + if (hasTitleChanged || hasDescriptionChanged) { const { content: homepageContentObj, sha } = homepage - homepageContentObj.title = configSettings.title + if (hasTitleChanged) homepageContentObj.title = configSettings.title + if (hasDescriptionChanged) + homepageContentObj.description = configSettings.description const homepageFrontMatter = yaml.stringify(homepageContentObj) const homepageContent = ["---\n", homepageFrontMatter, "---"].join("") From f35acfb2b9c84f990195cfc58b5039b35a57bf85 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Thu, 16 Sep 2021 12:51:36 +0800 Subject: [PATCH 16/25] Fix: prevent truncating of page body (#299) --- utils/markdown-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/markdown-utils.js b/utils/markdown-utils.js index 0157ce75b..a9dc54fef 100644 --- a/utils/markdown-utils.js +++ b/utils/markdown-utils.js @@ -2,9 +2,9 @@ const yaml = require("yaml") const retrieveDataFromMarkdown = (fileContent) => { // eslint-disable-next-line no-unused-vars - const [unused, encodedFrontMatter, pageContent] = fileContent.split("---") + const [unused, encodedFrontMatter, ...pageContent] = fileContent.split("---") const frontMatter = yaml.parse(encodedFrontMatter) - return { frontMatter, pageContent } + return { frontMatter, pageContent: pageContent.join("---") } } const convertDataToMarkdown = (frontMatter, pageContent) => { From 02d3b039cb90cd44252929c5837a8692dd040e60 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Mon, 20 Sep 2021 17:16:00 +0800 Subject: [PATCH 17/25] hotfix: properly handle subcollection file update --- newroutes/collectionPages.js | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/newroutes/collectionPages.js b/newroutes/collectionPages.js index ebb4a932a..26d9514ed 100644 --- a/newroutes/collectionPages.js +++ b/newroutes/collectionPages.js @@ -115,26 +115,28 @@ class CollectionPagesRouter { sha, }) } - } - if (newFileName) { - updateResp = await this.collectionPageService.rename(reqDetails, { - oldFileName: pageName, - newFileName, - collectionName, - content: pageBody, - frontMatter, - sha, - }) } else { - updateResp = await this.collectionPageService.update(reqDetails, { - fileName: pageName, - collectionName, - content: pageBody, - frontMatter, - sha, - }) + /* eslint-disable no-lonely-if */ + if (newFileName) { + updateResp = await this.collectionPageService.rename(reqDetails, { + oldFileName: pageName, + newFileName, + collectionName, + content: pageBody, + frontMatter, + sha, + }) + } else { + updateResp = await this.collectionPageService.update(reqDetails, { + fileName: pageName, + collectionName, + content: pageBody, + frontMatter, + sha, + }) + } } - + /* eslint-enable no-lonely-if */ return res.status(200).json(updateResp) } From d870f6e5d6112b5538990131d990f985df1594cc Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Thu, 23 Sep 2021 14:43:57 +0800 Subject: [PATCH 18/25] Fix: handle 409 errors when editing files (#303) * Fix: handle 409 errors when editing files * Fix: update v1 file endpoint * Fix: catch 409 errors for delete file operations * Fix: catch errors for media files --- classes/File.js | 8 ++++++ classes/MediaFile.js | 48 +++++++++++++++++++++++++----------- services/db/GitHubService.js | 8 ++++++ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/classes/File.js b/classes/File.js index d60c7f50f..39be6f38a 100644 --- a/classes/File.js +++ b/classes/File.js @@ -131,6 +131,10 @@ class File { } catch (err) { const { status } = err.response if (status === 404) throw new NotFoundError("File does not exist") + if (status === 409) + throw new ConflictError( + "File has been changed recently, please try again" + ) throw err } } @@ -155,6 +159,10 @@ class File { } catch (err) { const { status } = err.response if (status === 404) throw new NotFoundError("File does not exist") + if (status === 409) + throw new ConflictError( + "File has been changed recently, please try again" + ) throw err } } diff --git a/classes/MediaFile.js b/classes/MediaFile.js index d7be4ddd3..7a2b4c9ed 100644 --- a/classes/MediaFile.js +++ b/classes/MediaFile.js @@ -157,14 +157,24 @@ class MediaFile { sha, } - const resp = await axios.put(endpoint, params, { - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) + try { + const resp = await axios.put(endpoint, params, { + headers: { + Authorization: `token ${this.accessToken}`, + "Content-Type": "application/json", + }, + }) - return { newSha: resp.data.commit.sha } + return { newSha: resp.data.commit.sha } + } catch (err) { + const { status } = err.response + if (status === 404) throw new NotFoundError("File does not exist") + if (status === 409) + throw new ConflictError( + "File has been changed recently, please try again" + ) + throw err + } } async delete(fileName, sha) { @@ -176,13 +186,23 @@ class MediaFile { sha, } - await axios.delete(endpoint, { - data: params, - headers: { - Authorization: `token ${this.accessToken}`, - "Content-Type": "application/json", - }, - }) + try { + await axios.delete(endpoint, { + data: params, + headers: { + Authorization: `token ${this.accessToken}`, + "Content-Type": "application/json", + }, + }) + } catch (err) { + const { status } = err.response + if (status === 404) throw new NotFoundError("File does not exist") + if (status === 409) + throw new ConflictError( + "File has been changed recently, please try again" + ) + throw err + } } } diff --git a/services/db/GitHubService.js b/services/db/GitHubService.js index 940f621f5..cdd763204 100644 --- a/services/db/GitHubService.js +++ b/services/db/GitHubService.js @@ -140,6 +140,10 @@ class GitHubService { if (err instanceof NotFoundError) throw err const { status } = err.response if (status === 404) throw new NotFoundError("File does not exist") + if (status === 409) + throw new ConflictError( + "File has been changed recently, please try again" + ) throw err } } @@ -173,6 +177,10 @@ class GitHubService { } catch (err) { const { status } = err.response if (status === 404) throw new NotFoundError("File does not exist") + if (status === 409) + throw new ConflictError( + "File has been changed recently, please try again" + ) throw err } } From 89b58108732db6407f1fbcb30a56411a55020125 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Thu, 23 Sep 2021 15:16:52 +0800 Subject: [PATCH 19/25] Fix: change git tree object format (#305) * Fix: change git tree object format * Nit: add comments --- classes/Collection.js | 46 ++++++++++++++++++++++++++++------ classes/MediaSubfolder.js | 36 ++++++++++++++------------- classes/Resource.js | 34 ++++++++++++------------- classes/ResourceRoom.js | 34 ++++++++++++++++++------- routes/folders.js | 52 +++++++++++++++++---------------------- utils/utils.js | 2 ++ 6 files changed, 124 insertions(+), 80 deletions(-) diff --git a/classes/Collection.js b/classes/Collection.js index f0794a294..1999f4355 100644 --- a/classes/Collection.js +++ b/classes/Collection.js @@ -89,10 +89,24 @@ class Collection { async delete(collectionName, currentCommitSha, treeSha) { const commitMessage = `Delete collection ${collectionName}` - const gitTree = await getTree(this.siteName, this.accessToken, treeSha) - const newGitTree = gitTree.filter((item) => item.path !== `_${collectionName}`) + const gitTree = await getTree( + this.siteName, + this.accessToken, + treeSha, + true + ) + const newGitTree = gitTree + .filter( + (item) => + item.type !== "tree" && item.path.startsWith(`_${collectionName}/`) + ) + .map((item) => ({ + ...item, + sha: null, + })) await sendTree( newGitTree, + treeSha, currentCommitSha, this.siteName, this.accessToken, @@ -125,20 +139,36 @@ class Collection { ) { const commitMessage = `Rename collection from ${oldCollectionName} to ${newCollectionName}` - const gitTree = await getTree(this.siteName, this.accessToken, treeSha) + const gitTree = await getTree( + this.siteName, + this.accessToken, + treeSha, + true + ) const oldCollectionDirectoryName = `_${oldCollectionName}` const newCollectionDirectoryName = `_${newCollectionName}` - const newGitTree = gitTree.map((item) => { - if (item.path === oldCollectionDirectoryName) { - return { + const newGitTree = [] + gitTree.forEach((item) => { + if (item.path === oldCollectionDirectoryName && item.type === "tree") { + // Rename old subdirectory to new name + newGitTree.push({ ...item, path: newCollectionDirectoryName, - } + }) + } else if ( + item.path.startsWith(`${oldCollectionDirectoryName}/`) && + item.type !== "tree" + ) { + // Delete old subdirectory items + newGitTree.push({ + ...item, + sha: null, + }) } - return item }) await sendTree( newGitTree, + treeSha, currentCommitSha, this.siteName, this.accessToken, diff --git a/classes/MediaSubfolder.js b/classes/MediaSubfolder.js index 0bc81389c..92327d022 100644 --- a/classes/MediaSubfolder.js +++ b/classes/MediaSubfolder.js @@ -36,17 +36,17 @@ class MediaSubfolder { ) const directoryName = `${this.mediaFolderName}/${subfolderPath}` const newGitTree = gitTree - .filter((item) => { - return !item.path.includes(directoryName) - }) - .filter((item) => { - return !( - item.type === "tree" && item.path.includes(this.mediaFolderName) - ) - }) - + .filter( + (item) => + item.type !== "tree" && item.path.startsWith(`${directoryName}/`) + ) + .map((item) => ({ + ...item, + sha: null, + })) await sendTree( newGitTree, + treeSha, currentCommitSha, this.siteName, this.accessToken, @@ -67,24 +67,26 @@ class MediaSubfolder { const newDirectoryName = `${this.mediaFolderName}/${newSubfolderPath}` const newGitTree = [] gitTree.forEach((item) => { - if (item.path === oldDirectoryName) { + if (item.path === oldDirectoryName && item.type === "tree") { + // Rename old subdirectory to new name newGitTree.push({ ...item, path: newDirectoryName, }) - } else if (item.path.includes(oldDirectoryName)) { - // We don't want to include these because they use the old path, they are included with the renamed tree } else if ( - item.type === "tree" && - item.path.includes(this.mediaFolderName) + item.path.startsWith(`${oldDirectoryName}/`) && + item.type !== "tree" ) { - // We don't include any other trees - we reconstruct them by adding all their individual files instead - } else { - newGitTree.push(item) + // Delete old subdirectory items + newGitTree.push({ + ...item, + sha: null, + }) } }) await sendTree( newGitTree, + treeSha, currentCommitSha, this.siteName, this.accessToken, diff --git a/classes/Resource.js b/classes/Resource.js index 57cd3696a..b993fb412 100644 --- a/classes/Resource.js +++ b/classes/Resource.js @@ -59,38 +59,38 @@ class Resource { this.siteName, this.accessToken ) - const gitTree = await getTree(this.siteName, this.accessToken, treeSha) - const newGitTree = [] - let resourceRoomTreeSha - // Retrieve all git trees of other items - gitTree.forEach((item) => { - if (item.path === resourceRoomName) { - resourceRoomTreeSha = item.sha - } else { - newGitTree.push(item) - } - }) - const resourceRoomTree = await getTree( + const gitTree = await getTree( this.siteName, this.accessToken, - resourceRoomTreeSha + treeSha, + true ) - resourceRoomTree.forEach((item) => { + const newGitTree = [] + gitTree.forEach((item) => { // We need to append resource room to the file path because the path is relative to the subtree - if (item.path === resourceName) { + if ( + item.path === `${resourceRoomName}/${resourceName}` && + item.type === "tree" + ) { + // Rename old subdirectory to new name newGitTree.push({ ...item, path: `${resourceRoomName}/${newResourceName}`, }) - } else { + } else if ( + item.path.startsWith(`${resourceRoomName}/${resourceName}/`) && + item.type !== "tree" + ) { + // Delete old subdirectory items newGitTree.push({ ...item, - path: `${resourceRoomName}/${item.path}`, + sha: null, }) } }) await sendTree( newGitTree, + treeSha, currentCommitSha, this.siteName, this.accessToken, diff --git a/classes/ResourceRoom.js b/classes/ResourceRoom.js index 3a112028b..5eab979d2 100644 --- a/classes/ResourceRoom.js +++ b/classes/ResourceRoom.js @@ -121,18 +121,34 @@ class ResourceRoom { this.siteName, this.accessToken ) - const gitTree = await getTree(this.siteName, this.accessToken, treeSha) - const newGitTree = gitTree.map((item) => { - if (item.path === resourceRoomName) { - return { + const gitTree = await getTree( + this.siteName, + this.accessToken, + treeSha, + true + ) + const newGitTree = [] + gitTree.forEach((item) => { + if (item.path === resourceRoomName && item.type === "tree") { + // Rename old subdirectory to new name + newGitTree.push({ ...item, path: newResourceRoom, - } + }) + } else if ( + item.path.startsWith(`${resourceRoomName}/`) && + item.type !== "tree" + ) { + // Delete old subdirectory items + newGitTree.push({ + ...item, + sha: null, + }) } - return item }) await sendTree( newGitTree, + treeSha, currentCommitSha, this.siteName, this.accessToken, @@ -197,9 +213,9 @@ class ResourceRoom { const resources = await IsomerResource.list(resourceRoomName) if (!_.isEmpty(resources)) { - await Bluebird.map(resources, async (resource) => { - return IsomerResource.delete(resourceRoomName, resource.dirName) - }) + await Bluebird.map(resources, async (resource) => + IsomerResource.delete(resourceRoomName, resource.dirName) + ) } // Delete index file in resourceRoom diff --git a/routes/folders.js b/routes/folders.js index 5d7ea6f70..8de52df8f 100644 --- a/routes/folders.js +++ b/routes/folders.js @@ -12,8 +12,11 @@ const { Collection } = require("@classes/Collection") const { CollectionConfig } = require("@classes/Config") const { File, CollectionPageType } = require("@classes/File") -const { getTree, sendTree, deslugifyCollectionName } = require("@utils/utils.js") - +const { + getTree, + sendTree, + deslugifyCollectionName, +} = require("@utils/utils.js") const router = express.Router() @@ -45,31 +48,19 @@ async function deleteSubfolder(req, res) { const commitMessage = `Delete subfolder ${folderName}/${subfolderName}` const isRecursive = true const gitTree = await getTree(siteName, accessToken, treeSha, isRecursive) - const baseTreeWithoutFolder = gitTree.filter( - (item) => - // keep all root-level items except for tree object of folder whose subfolder is to be deleted - !item.path.includes("/") && item.path !== `_${folderName}` - ) - const folderTreeWithoutSubfolder = gitTree - .filter((item) => - // get all folder items - item.path.includes(`_${folderName}`) - ) + const newGitTree = gitTree .filter( (item) => - // remove tree objects of folder and subfolder to be renamed - item.path !== `_${folderName}` && - item.path !== `_${folderName}/${subfolderName}` + item.type !== "tree" && + item.path.startsWith(`_${folderName}/${subfolderName}/`) ) - .filter( - (item) => - // exclude all subfolder items - !item.path.includes(`_${folderName}/${subfolderName}`) - ) - - const newGitTree = [...baseTreeWithoutFolder, ...folderTreeWithoutSubfolder] + .map((item) => ({ + ...item, + sha: null, + })) await sendTree( newGitTree, + treeSha, currentCommitSha, siteName, accessToken, @@ -108,14 +99,14 @@ async function renameSubfolder(req, res) { const filesToBeModified = await CurrentIsomerFile.list() - await Bluebird.mapSeries(filesToBeModified, async(fileInfo) => { + await Bluebird.mapSeries(filesToBeModified, async (fileInfo) => { const { fileName } = fileInfo // Read existing file content const { content, sha } = await CurrentIsomerFile.read(fileName) // Handle keep file differently - if (fileName === '.keep') { + if (fileName === ".keep") { await NewIsomerFile.create(fileName, content) return CurrentIsomerFile.delete(fileName, sha) } @@ -128,21 +119,24 @@ async function renameSubfolder(req, res) { // Modify `third_nav_title` and save as new file in newSubfolderName const newFrontMatter = { ...frontMatter, - third_nav_title: deslugifyCollectionName(newSubfolderName) + third_nav_title: deslugifyCollectionName(newSubfolderName), } - const newContent = ["---\n", yaml.stringify(newFrontMatter), "---\n", mdBody].join("") + const newContent = [ + "---\n", + yaml.stringify(newFrontMatter), + "---\n", + mdBody, + ].join("") const encodedNewContent = Base64.encode(newContent) - + await NewIsomerFile.create(fileName, encodedNewContent) // Delete existing file in subfolderName return CurrentIsomerFile.delete(fileName, sha) - }) - // // Update collection config const collectionConfig = new CollectionConfig( accessToken, diff --git a/utils/utils.js b/utils/utils.js index 575357b55..bb33a1f4e 100644 --- a/utils/utils.js +++ b/utils/utils.js @@ -63,6 +63,7 @@ async function getTree( // send the new tree object back to Github and point the latest commit on the staging branch to it async function sendTree( gitTree, + baseTreeSha, currentCommitSha, repo, accessToken, @@ -77,6 +78,7 @@ async function sendTree( `https://api.github.com/repos/${GITHUB_ORG_NAME}/${repo}/git/trees`, { tree: gitTree, + base_tree: baseTreeSha, }, { headers, From f7f5a3337ea81b8580d16b433b9941f2985b44c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 04:10:12 +0000 Subject: [PATCH 20/25] build(deps): bump axios from 0.21.1 to 0.21.4 (#295) --- package-lock.json | 15 +++++++-------- package.json | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4b07eedb..84c8c1118 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1670,11 +1670,11 @@ } }, "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", + "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", "requires": { - "follow-redirects": "^1.10.0" + "follow-redirects": "^1.14.0" } }, "babel-jest": { @@ -4050,9 +4050,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", - "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" }, "for-in": { "version": "1.0.2", @@ -8892,7 +8892,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", diff --git a/package.json b/package.json index 637891b0b..e27c13248 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "auto-bind": "^4.0.0", "aws-sdk": "^2.946.0", - "axios": "^0.21.1", + "axios": "^0.21.2", "base-64": "^0.1.0", "bluebird": "^3.7.2", "body-parser": "^1.19.0", From d26be37e531fc156c2d42cfa5a4b16321492754e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 04:12:13 +0000 Subject: [PATCH 21/25] build(deps): bump tmpl from 1.0.4 to 1.0.5 (#304) --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84c8c1118..c64c415a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9688,9 +9688,9 @@ } }, "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, "to-fast-properties": { From 27d3f1cbac8fa973403fdaa0d619a75c40a76b5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 04:40:01 +0000 Subject: [PATCH 22/25] build(deps-dev): bump jest from 26.6.3 to 27.0.6 (#222) --- package-lock.json | 3664 ++++++++++++++------------------------------- package.json | 2 +- 2 files changed, 1112 insertions(+), 2554 deletions(-) diff --git a/package-lock.json b/package-lock.json index c64c415a8..94e531792 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,33 +13,65 @@ "@babel/highlight": "^7.12.13" } }, + "@babel/compat-data": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", + "dev": true + }, "@babel/core": { - "version": "7.12.16", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.16.tgz", - "integrity": "sha512-t/hHIB504wWceOeaOoONOhu+gX+hpjfeN6YRBT209X/4sibZQfSF1I0HFRRlBe97UZZosGx5XwUg1ZgNbelmNw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.15", - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helpers": "^7.12.13", - "@babel/parser": "^7.12.16", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13", + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", + "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.5", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", + "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "lodash": "^4.17.19", - "semver": "^5.4.1", + "semver": "^6.3.0", "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -50,118 +82,174 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, "@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", "dev": true, "requires": { - "@babel/types": "^7.12.13", + "@babel/types": "^7.15.4", "jsesc": "^2.5.1", "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.12.16", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.16.tgz", - "integrity": "sha512-zYoZC1uvebBFmj1wFAlXwt35JLEgecefATtKp20xalwEK8vHAixLBXTGxNrVGEmTT+gzOThUgr8UEdgtalc1BQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-module-imports": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", - "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-module-transforms": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz", - "integrity": "sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-simple-access": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13", - "lodash": "^4.17.19" + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + } } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", "dev": true }, "@babel/helper-replace-supers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", - "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-simple-access": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", - "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-validator-identifier": { @@ -170,15 +258,21 @@ "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", "dev": true }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true + }, "@babel/helpers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", - "integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/highlight": { @@ -193,9 +287,9 @@ } }, "@babel/parser": { - "version": "7.12.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz", - "integrity": "sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -298,46 +392,109 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "@babel/plugin-syntax-typescript": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", + "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", + "@babel/template": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + } + } + }, + "@babel/traverse": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "globals": "^11.1.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -352,14 +509,21 @@ } }, "@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", + "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + } } }, "@bcoe/v8-coverage": { @@ -368,16 +532,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, "@commitlint/execute-rule": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-13.0.0.tgz", @@ -636,16 +790,16 @@ "dev": true }, "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.2.2.tgz", + "integrity": "sha512-m7tbzPWyvSFfoanTknJoDnaeruDARsUe555tkVjG/qeaRDKwyPqqbgs4yFx583gmoETiAts1deeYozR5sVRhNA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", + "jest-message-util": "^27.2.2", + "jest-util": "^27.2.0", "slash": "^3.0.0" }, "dependencies": { @@ -659,9 +813,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -701,35 +855,36 @@ } }, "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.2.2.tgz", + "integrity": "sha512-4b9km/h9pAGdCkwWYtbfoeiOtajOlGmr5rL1Eq6JCAVbOevOqxWHxJ6daWxRJW9eF6keXJoJ1H+uVAVcdZu8Bg==", "dev": true, "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.2.2", + "@jest/reporters": "^27.2.2", + "@jest/test-result": "^27.2.2", + "@jest/transform": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", + "emittery": "^0.8.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", + "jest-changed-files": "^27.1.1", + "jest-config": "^27.2.2", + "jest-haste-map": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.2", + "jest-resolve-dependencies": "^27.2.2", + "jest-runner": "^27.2.2", + "jest-runtime": "^27.2.2", + "jest-snapshot": "^27.2.2", + "jest-util": "^27.2.0", + "jest-validate": "^27.2.2", + "jest-watcher": "^27.2.2", + "micromatch": "^4.0.4", "p-each-series": "^2.1.0", "rimraf": "^3.0.0", "slash": "^3.0.0", @@ -737,18 +892,18 @@ }, "dependencies": { "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -760,19 +915,10 @@ "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -794,50 +940,19 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "supports-color": { @@ -849,71 +964,62 @@ "has-flag": "^4.0.0" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } }, "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.2.2.tgz", + "integrity": "sha512-gO9gVnZfn5ldeOJ5q+35Kru9QWGHEqZCB7W/M+8mD6uCwOGC9HR6mzpLSNRuDsxY/KhaGBYHpgFqtpe4Rl1gDQ==", "dev": true, "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/fake-timers": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", - "jest-mock": "^26.6.2" + "jest-mock": "^27.1.1" } }, "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.2.2.tgz", + "integrity": "sha512-gDIIqs0yxyjyxEI9HlJ8SEJ4uCc8qr8BupG1Hcx7tvyk/SLocyXE63rFxL+HQ0ZLMvSyEcJUmYnvvHH1osWiGA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", + "@jest/types": "^27.1.1", + "@sinonjs/fake-timers": "^7.0.2", "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "jest-message-util": "^27.2.2", + "jest-mock": "^27.1.1", + "jest-util": "^27.2.0" } }, "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.2.2.tgz", + "integrity": "sha512-fWa/Luwod1hyehnuep+zCnOTqTVvyc4HLUU/1VpFNOEu0tCWNSODyvKSSOjtb1bGOpCNjgaDcyjzo5f7rl6a7g==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" + "@jest/environment": "^27.2.2", + "@jest/types": "^27.1.1", + "expect": "^27.2.2" } }, "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.2.2.tgz", + "integrity": "sha512-ufwZ8XoLChEfPffDeVGroYbhbcYPom3zKDiv4Flhe97rr/o2IfUXoWkDUDoyJ3/V36RFIMjokSu0IJ/pbFtbHg==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.2.2", + "@jest/test-result": "^27.2.2", + "@jest/transform": "^27.2.2", + "@jest/types": "^27.1.1", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", @@ -924,16 +1030,15 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", + "jest-haste-map": "^27.2.2", + "jest-resolve": "^27.2.2", + "jest-util": "^27.2.0", + "jest-worker": "^27.2.2", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" + "v8-to-istanbul": "^8.0.0" }, "dependencies": { "ansi-styles": { @@ -946,9 +1051,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -970,24 +1075,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1000,80 +1093,57 @@ } }, "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", + "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", "dev": true, "requires": { "callsites": "^3.0.0", "graceful-fs": "^4.2.4", "source-map": "^0.6.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.2.2.tgz", + "integrity": "sha512-yENoDEoWlEFI7l5z7UYyJb/y5Q8RqbPd4neAVhKr6l+vVaQOPKf8V/IseSMJI9+urDUIxgssA7RGNyCRhGjZvw==", "dev": true, "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.2.2", + "@jest/types": "^27.1.1", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.2.2.tgz", + "integrity": "sha512-YnJqwNQP2Zeu0S4TMqkxg6NN7Y1EFq715n/nThNKrvIS9wmRZjDt2XYqsHbuvhAFjshi0iKDQ813NewFITBH+Q==", "dev": true, "requires": { - "@jest/test-result": "^26.6.2", + "@jest/test-result": "^27.2.2", "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - } + "jest-haste-map": "^27.2.2", + "jest-runtime": "^27.2.2" } }, "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.2.2.tgz", + "integrity": "sha512-l4Z/7PpajrOjCiXLWLfMY7fgljY0H8EwW7qdzPXXuv2aQF8kY2+Uyj3O+9Popnaw1V7JCw32L8EeI/thqFDkPA==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "babel-plugin-istanbul": "^6.0.0", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", + "jest-haste-map": "^27.2.2", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.2.0", + "micromatch": "^4.0.4", "pirates": "^4.0.1", "slash": "^3.0.0", "source-map": "^0.6.1", @@ -1089,19 +1159,10 @@ "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -1123,49 +1184,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1174,28 +1198,19 @@ "requires": { "has-flag": "^4.0.0" } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", + "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^15.0.0", + "@types/yargs": "^16.0.0", "chalk": "^4.0.0" }, "dependencies": { @@ -1209,9 +1224,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -1269,18 +1284,18 @@ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, "@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -1297,9 +1312,9 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, "@types/babel__core": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", - "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", + "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -1310,18 +1325,18 @@ } }, "@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", + "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", "dev": true, "requires": { "@babel/types": "^7.0.0" } }, "@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -1329,9 +1344,9 @@ } }, "@types/babel__traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", - "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", + "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -1362,24 +1377,18 @@ } }, "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, "requires": { "@types/istanbul-lib-report": "*" } }, "@types/node": { - "version": "12.12.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.21.tgz", - "integrity": "sha512-8sRGhbpU+ck1n0PGAUgVrWrWdjSW2aqNeyC15W88GRsMpSwzv6RJGlLhE7s2RhVSOdyDmxbqlWSeThq4/7xqlA==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "version": "16.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz", + "integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==", "dev": true }, "@types/parse-json": { @@ -1389,30 +1398,30 @@ "dev": true }, "@types/prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.0.tgz", + "integrity": "sha512-WHRsy5nMpjXfU9B0LqOqPT06EI2+8Xv5NERy0pLxJLbU98q7uhcGogQzfX+rXpU7S5mgHsLxHrLCufZcV/P8TQ==", "dev": true }, "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, "@types/yargs": { - "version": "15.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", - "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, "abab": { @@ -1527,9 +1536,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -1545,24 +1554,6 @@ "sprintf-js": "~1.0.2" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1581,12 +1572,6 @@ "is-string": "^1.0.5" } }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "array.prototype.flat": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", @@ -1598,12 +1583,6 @@ "es-abstract": "^1.18.0-next.1" } }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, "ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -1635,12 +1614,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "auto-bind": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", @@ -1678,16 +1651,16 @@ } }, "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.2.tgz", + "integrity": "sha512-XNFNNfGKnZXzhej7TleVP4s9ktH5JjRW8Rmcbb223JJwKB/gmTyeWN0JfiPtSgnjIjDXtKNoixiy0QUHtv3vFA==", "dev": true, "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", + "@jest/transform": "^27.2.2", + "@jest/types": "^27.1.1", + "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", + "babel-preset-jest": "^27.2.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "slash": "^3.0.0" @@ -1703,9 +1676,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -1727,12 +1700,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1764,9 +1731,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.2.0.tgz", + "integrity": "sha512-TOux9khNKdi64mW+0OIhcmbAn75tTlzKhxmiNXevQaPbrBYK7YKjP1jl6NHTJ6XR5UgUrJbCnWlKVnJn29dfjw==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -1796,12 +1763,12 @@ } }, "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.2.0.tgz", + "integrity": "sha512-z7MgQ3peBwN5L5aCqBKnF6iqdlvZvFUQynEhu0J+X9nHLU72jO3iY331lcYrg+AssJ8q7xsv5/3AICzVmJ/wvg==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^26.6.2", + "babel-plugin-jest-hoist": "^27.2.0", "babel-preset-current-node-syntax": "^1.0.0" } }, @@ -1811,61 +1778,6 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "base-64": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", @@ -1946,32 +1858,12 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" } }, "browser-process-hrtime": { @@ -1979,6 +1871,19 @@ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, + "browserslist": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.1.tgz", + "integrity": "sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001259", + "electron-to-chromium": "^1.3.846", + "escalade": "^3.1.1", + "nanocolors": "^0.1.5", + "node-releases": "^1.1.76" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -2004,9 +1909,9 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "bytes": { @@ -2014,23 +1919,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, "cachedir": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.2.0.tgz", @@ -2058,13 +1946,13 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "caniuse-lite": { + "version": "1.0.30001260", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001260.tgz", + "integrity": "sha512-Fhjc/k8725ItmrvW5QomzxLeojewxvqiYCKeFcfFEhut28IVLdpHU19dneOmltZQIE5HNbawj1HYD+1f2bM1Dg==", "dev": true, "requires": { - "rsvp": "^4.8.4" + "nanocolors": "^0.1.0" } }, "chalk": { @@ -2091,40 +1979,17 @@ "dev": true }, "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", + "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", "dev": true }, "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -2226,20 +2091,44 @@ "dev": true }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "is-fullwidth-code-point": { @@ -2249,23 +2138,34 @@ "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } } } @@ -2282,16 +2182,6 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", @@ -2430,9 +2320,9 @@ "dev": true }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -2469,12 +2359,6 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2511,16 +2395,25 @@ } }, "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "crypto-js": { @@ -2586,12 +2479,6 @@ "ms": "2.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, "decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", @@ -2628,47 +2515,6 @@ "object-keys": "^1.0.12" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2703,9 +2549,9 @@ "dev": true }, "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", "dev": true }, "doctrine": { @@ -2755,10 +2601,16 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "electron-to-chromium": { + "version": "1.3.850", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.850.tgz", + "integrity": "sha512-ZzkDcdzePeF4dhoGZQT77V2CyJOpwfTZEOg4h0x6R/jQhGt/rIRpbRyVreWLtD7B/WsVxo91URm2WxMKR9JQZA==", + "dev": true + }, "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", "dev": true }, "emoji-regex": { @@ -2777,15 +2629,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -2839,6 +2682,12 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3495,31 +3344,42 @@ "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, - "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true - }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "dependencies": { - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "signal-exit": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", + "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", "dev": true } } @@ -3530,41 +3390,6 @@ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", "dev": true }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -3575,41 +3400,23 @@ } }, "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.2.tgz", + "integrity": "sha512-sjHBeEk47/eshN9oLbvPJZMgHQihOXXQzSMPCJ4MqKShbU9HOVFSNHEEU4dp4ujzxFSiNvPFzB2AMOFmkizhvA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" + "@jest/types": "^27.1.1", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.0.6", + "jest-matcher-utils": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-regex-util": "^27.0.6" }, "dependencies": { "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true } } @@ -3725,27 +3532,6 @@ } } }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -3757,71 +3543,6 @@ "tmp": "^0.0.33" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3898,26 +3619,12 @@ } }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "to-regex-range": "^5.0.1" } }, "filter-obj": { @@ -4054,12 +3761,6 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -4080,15 +3781,6 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -4195,13 +3887,10 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true }, "get-uri": { "version": "3.0.2", @@ -4236,12 +3925,6 @@ } } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, "glob": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", @@ -4310,13 +3993,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -4342,44 +4018,6 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "helmet": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", @@ -4493,9 +4131,9 @@ } }, "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, "husky": { @@ -4626,32 +4264,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -4680,12 +4292,12 @@ "dev": true }, "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", + "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", "dev": true, "requires": { - "ci-info": "^2.0.0" + "ci-info": "^3.1.1" } }, "is-core-module": { @@ -4697,70 +4309,12 @@ "has": "^1.0.3" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4795,30 +4349,10 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "is-number-object": { "version": "1.0.4", @@ -4832,15 +4366,6 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4914,16 +4439,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -4935,16 +4450,10 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz", + "integrity": "sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ==", "dev": true }, "istanbul-lib-instrument": { @@ -5007,9 +4516,9 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -5020,12 +4529,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true } } }, @@ -5040,14 +4543,14 @@ } }, "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.2.2.tgz", + "integrity": "sha512-XAB/9akDTe3/V0wPNKWfP9Y/NT1QPiCqyRBYGbC66EA9EvgAzdaFEqhFGLaDJ5UP2yIyXUMtju9a9IMrlYbZTQ==", "dev": true, "requires": { - "@jest/core": "^26.6.3", + "@jest/core": "^27.2.2", "import-local": "^3.0.2", - "jest-cli": "^26.6.3" + "jest-cli": "^27.2.2" }, "dependencies": { "ansi-styles": { @@ -5060,9 +4563,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5084,12 +4587,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5097,24 +4594,23 @@ "dev": true }, "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.2.2.tgz", + "integrity": "sha512-jbEythw22LR/IHYgNrjWdO74wO9wyujCxTMjbky0GLav4rC4y6qDQr4TqQ2JPP51eDYJ2awVn83advEVSs5Brg==", "dev": true, "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/core": "^27.2.2", + "@jest/test-result": "^27.2.2", + "@jest/types": "^27.1.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", + "jest-config": "^27.2.2", + "jest-util": "^27.2.0", + "jest-validate": "^27.2.2", "prompts": "^2.0.1", - "yargs": "^15.4.1" + "yargs": "^16.0.3" } }, "supports-color": { @@ -5129,133 +4625,121 @@ } }, "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.1.1.tgz", + "integrity": "sha512-5TV9+fYlC2A6hu3qtoyGHprBwCAn0AuGA77bZdUgYvVlRMjHXo063VcWTEAyx6XAZ85DYHqp0+aHKbPlfRDRvA==", + "dev": true, + "requires": { + "@jest/types": "^27.1.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + } + }, + "jest-circus": { + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.2.2.tgz", + "integrity": "sha512-8txlqs0EDrvPasCgwfLMkG0l3F4FxqQa6lxOsvYfOl04eSJjRw3F4gk9shakuC00nMD+VT+SMtFYXxe64f0VZw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" + "@jest/environment": "^27.2.2", + "@jest/test-result": "^27.2.2", + "@jest/types": "^27.1.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.2.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.2.2", + "jest-matcher-utils": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-runtime": "^27.2.2", + "jest-snapshot": "^27.2.2", + "jest-util": "^27.2.0", + "pretty-format": "^27.2.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" }, "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "color-convert": "^2.0.1" } }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "color-name": "~1.1.4" } }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "isexe": "^2.0.0" + "has-flag": "^4.0.0" } } } }, "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.2.2.tgz", + "integrity": "sha512-2nhms3lp52ZpU0636bB6zIFHjDVtYxzFQIOHZjBFUeXcb6b41sEkWojbHaJ4FEIO44UbccTLa7tvNpiFCgPE7w==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", + "@jest/test-sequencer": "^27.2.2", + "@jest/types": "^27.1.1", + "babel-jest": "^27.2.2", "chalk": "^4.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" + "is-ci": "^3.0.0", + "jest-circus": "^27.2.2", + "jest-environment-jsdom": "^27.2.2", + "jest-environment-node": "^27.2.2", + "jest-get-type": "^27.0.6", + "jest-jasmine2": "^27.2.2", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.2", + "jest-runner": "^27.2.2", + "jest-util": "^27.2.0", + "jest-validate": "^27.2.2", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.2" }, "dependencies": { "ansi-styles": { @@ -5267,19 +4751,10 @@ "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5301,43 +4776,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5346,28 +4790,19 @@ "requires": { "has-flag": "^4.0.0" } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.2.tgz", + "integrity": "sha512-o3LaDbQDSaMJif4yztJAULI4xVatxbBasbKLbEw3K8CiRdDdbxMrLArS9EKDHQFYh6Tgfrm1PC2mIYR1xhu0hQ==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.2" }, "dependencies": { "ansi-styles": { @@ -5380,9 +4815,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5422,25 +4857,25 @@ } }, "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", + "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", "dev": true, "requires": { "detect-newline": "^3.0.0" } }, "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.2.2.tgz", + "integrity": "sha512-ZCDhkvwHeXHsxoFxvW43fabL18iLiVDxaipG5XWG7dSd+XWXXpzMQvBWYT9Wvzhg5x4hvrLQ24LtiOKw3I09xA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" + "jest-get-type": "^27.0.6", + "jest-util": "^27.2.0", + "pretty-format": "^27.2.2" }, "dependencies": { "ansi-styles": { @@ -5453,9 +4888,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5495,137 +4930,85 @@ } }, "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.2.2.tgz", + "integrity": "sha512-mzCLEdnpGWDJmNB6WIPLlZM+hpXdeiya9TryiByqcUdpliNV1O+LGC2SewzjmB4IblabGfvr3KcPN0Nme2wnDw==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/environment": "^27.2.2", + "@jest/fake-timers": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" + "jest-mock": "^27.1.1", + "jest-util": "^27.2.0", + "jsdom": "^16.6.0" } }, "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.2.2.tgz", + "integrity": "sha512-XgUscWs6H6UNqC96/QJjmUGZzzpql/JyprLSXVu7wkgM8tjbJdEkSqwrVAvJPm1yu526ImrmsIoh2BTHxkwL/g==", "dev": true, "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/environment": "^27.2.2", + "@jest/fake-timers": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" + "jest-mock": "^27.1.1", + "jest-util": "^27.2.0" } }, "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", "dev": true }, "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.2.2.tgz", + "integrity": "sha512-kaKiq+GbAvk6/sq++Ymor4Vzk6+lr0vbKs2HDVPdkKsHX2lIJRyvhypZG/QsNfQnROKWIZSpUpGuv2HySSosvA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", + "fsevents": "^2.3.2", "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.2.0", + "jest-worker": "^27.2.2", + "micromatch": "^4.0.4", "walker": "^1.0.7" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } } }, "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.2.2.tgz", + "integrity": "sha512-SczhZNfmZID9HbJ1GHhO4EzeL/PMRGeAUw23ddPUdd6kFijEZpT2yOxyNCBUKAsVQ/14OB60kjgnbuFOboZUNg==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/environment": "^27.2.2", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^26.6.2", + "expect": "^27.2.2", "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" + "jest-each": "^27.2.2", + "jest-matcher-utils": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-runtime": "^27.2.2", + "jest-snapshot": "^27.2.2", + "jest-util": "^27.2.0", + "pretty-format": "^27.2.2", + "throat": "^6.0.1" }, "dependencies": { "ansi-styles": { @@ -5638,9 +5021,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5680,25 +5063,25 @@ } }, "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.2.2.tgz", + "integrity": "sha512-fQIYKkhXUs/4EpE4wO1AVsv7aNH3o0km1BGq3vxvSfYdwG9GLMf+b0z/ghLmBYNxb+tVpm/zv2caoKm3GfTazg==", "dev": true, "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.2" } }, "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.2.tgz", + "integrity": "sha512-xN3wT4p2i9DGB6zmL3XxYp5lJmq9Q6ff8XKlMtVVBS2SAshmgsPBALJFQ8dWRd2G/xf5q/N0SD0Mipt8QBA26A==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "jest-diff": "^27.2.2", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.2" }, "dependencies": { "ansi-styles": { @@ -5711,9 +5094,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5753,20 +5136,20 @@ } }, "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.2.2.tgz", + "integrity": "sha512-/iS5/m2FSF7Nn6APFoxFymJpyhB/gPf0CJa7uFSkbYaWvrADUfQ9NTsuyjpszKErOS2/huFs44ysWhlQTKvL8Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.1.1", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", + "micromatch": "^4.0.4", + "pretty-format": "^27.2.2", "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "stack-utils": "^2.0.3" }, "dependencies": { "ansi-styles": { @@ -5778,19 +5161,10 @@ "color-convert": "^2.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5812,43 +5186,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5857,25 +5200,16 @@ "requires": { "has-flag": "^4.0.0" } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.1.1.tgz", + "integrity": "sha512-SClsFKuYBf+6SSi8jtAYOuPw8DDMsTElUWEae3zq7vDhH01ayVSIHUSIa8UgbDOUalCFp6gNsaikN0rbxN4dbw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "@types/node": "*" } }, @@ -5886,24 +5220,26 @@ "dev": true }, "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", + "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", "dev": true }, "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.2.2.tgz", + "integrity": "sha512-tfbHcBs/hJTb3fPQ/3hLWR+TsLNTzzK98TU+zIAsrL9nNzWfWROwopUOmiSUqmHMZW5t9au/433kSF2/Af+tTw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "chalk": "^4.0.0", + "escalade": "^3.1.1", "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.2.2", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", + "jest-util": "^27.2.0", + "jest-validate": "^27.2.2", + "resolve": "^1.20.0", "slash": "^3.0.0" }, "dependencies": { @@ -5917,9 +5253,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -5941,12 +5277,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5965,42 +5295,44 @@ } }, "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.2.2.tgz", + "integrity": "sha512-nvJS+DyY51HHdZnMIwXg7fimQ5ylFx4+quQXspQXde2rXYy+4v75UYoX/J65Ln8mKCNc6YF8HEhfGaRBOrxxHg==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" + "@jest/types": "^27.1.1", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.2.2" } }, "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.2.2.tgz", + "integrity": "sha512-+bUFwBq+yTnvsOFuxetoQtkuOnqdAk2YuIGjlLmc7xLAXn/V1vjhXrLencgij0BUTTUvG9Aul3+5XDss4Wa8Eg==", "dev": true, "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/console": "^27.2.2", + "@jest/environment": "^27.2.2", + "@jest/test-result": "^27.2.2", + "@jest/transform": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", "chalk": "^4.0.0", - "emittery": "^0.7.1", + "emittery": "^0.8.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.2.2", + "jest-environment-node": "^27.2.2", + "jest-haste-map": "^27.2.2", + "jest-leak-detector": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-resolve": "^27.2.2", + "jest-runtime": "^27.2.2", + "jest-util": "^27.2.0", + "jest-worker": "^27.2.2", "source-map-support": "^0.5.6", - "throat": "^5.0.0" + "throat": "^6.0.1" }, "dependencies": { "ansi-styles": { @@ -6013,9 +5345,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6037,12 +5369,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6061,38 +5387,38 @@ } }, "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.2.2.tgz", + "integrity": "sha512-PtTHCK5jT+KrIpKpjJsltu/dK5uGhBtTNLOk1Z+ZD2Jrxam2qQsOqDFYLszcK0jc2TLTNsrVpclqSftn7y3aXA==", + "dev": true, + "requires": { + "@jest/console": "^27.2.2", + "@jest/environment": "^27.2.2", + "@jest/fake-timers": "^27.2.2", + "@jest/globals": "^27.2.2", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.2.2", + "@jest/transform": "^27.2.2", + "@jest/types": "^27.1.1", + "@types/yargs": "^16.0.0", "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", + "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", + "jest-haste-map": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-mock": "^27.1.1", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.2.2", + "jest-snapshot": "^27.2.2", + "jest-util": "^27.2.0", + "jest-validate": "^27.2.2", "slash": "^3.0.0", "strip-bom": "^4.0.0", - "yargs": "^15.4.1" + "yargs": "^16.0.3" }, "dependencies": { "ansi-styles": { @@ -6105,9 +5431,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6129,12 +5455,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6153,44 +5473,44 @@ } }, "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", + "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", "dev": true, "requires": { "@types/node": "*", "graceful-fs": "^4.2.4" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - } } }, "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.2.2.tgz", + "integrity": "sha512-7ODSvULMiiOVuuLvLZpDlpqqTqX9hDfdmijho5auVu9qRYREolvrvgH4kSNOKfcqV3EZOTuLKNdqsz1PM20PQA==", "dev": true, "requires": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", + "@jest/transform": "^27.2.2", + "@jest/types": "^27.1.1", "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^26.6.2", + "expect": "^27.2.2", "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", + "jest-diff": "^27.2.2", + "jest-get-type": "^27.0.6", + "jest-haste-map": "^27.2.2", + "jest-matcher-utils": "^27.2.2", + "jest-message-util": "^27.2.2", + "jest-resolve": "^27.2.2", + "jest-util": "^27.2.0", "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", + "pretty-format": "^27.2.2", "semver": "^7.3.2" }, "dependencies": { @@ -6204,9 +5524,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6228,12 +5548,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6250,9 +5564,9 @@ } }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6276,41 +5590,32 @@ } }, "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.2.0.tgz", + "integrity": "sha512-T5ZJCNeFpqcLBpx+Hl9r9KoxBCUqeWlJ1Htli+vryigZVJ1vuLB9j35grEBASp4R13KFkV7jM52bBGnArpJN6A==", "dev": true, "requires": { - "@jest/types": "^26.6.2", + "@jest/types": "^27.1.1", "@types/node": "*", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "is-ci": "^3.0.0", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "color-convert": "^2.0.1" } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6332,43 +5637,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6377,30 +5651,21 @@ "requires": { "has-flag": "^4.0.0" } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } } } }, "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.2.2.tgz", + "integrity": "sha512-01mwTAs2kgDuX98Ua3Xhdhp5lXsLU4eyg6k56adTtrXnU/GbLd9uAsh5nc4MWVtUXMeNmHUyEiD4ibLqE8MuNw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", + "@jest/types": "^27.1.1", + "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", + "jest-get-type": "^27.0.6", "leven": "^3.1.0", - "pretty-format": "^26.6.2" + "pretty-format": "^27.2.2" }, "dependencies": { "ansi-styles": { @@ -6419,9 +5684,9 @@ "dev": true }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6461,27 +5726,27 @@ } }, "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.2.2.tgz", + "integrity": "sha512-7HJwZq06BCfM99RacCVzXO90B20/dNJvq+Ouiu/VrFdFRCpbnnqlQUEk4KAhBSllgDrTPgKu422SCF5KKBHDRA==", "dev": true, "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", + "@jest/test-result": "^27.2.2", + "@jest/types": "^27.1.1", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^26.6.2", + "jest-util": "^27.2.0", "string-length": "^4.0.1" }, "dependencies": { "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" } }, "ansi-styles": { @@ -6494,9 +5759,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -6534,22 +5799,22 @@ } }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } }, "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.2.2.tgz", + "integrity": "sha512-aG1xq9KgWB2CPC8YdMIlI8uZgga2LFNcGbHJxO8ctfXAydSaThR4EewKQGg3tBOC+kS3vhPGgymsBdi9VINjPw==", "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" + "supports-color": "^8.0.0" }, "dependencies": { "has-flag": { @@ -6559,9 +5824,9 @@ "dev": true }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6769,12 +6034,6 @@ "safe-buffer": "^5.0.1" } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7434,21 +6693,6 @@ "tmpl": "1.0.x" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7477,24 +6721,13 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" } }, "mime": { @@ -7536,27 +6769,6 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "module-alias": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", @@ -7605,24 +6817,11 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } + "nanocolors": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.1.12.tgz", + "integrity": "sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==", + "dev": true }, "natural-compare": { "version": "1.4.0", @@ -7640,12 +6839,6 @@ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -7658,66 +6851,11 @@ "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", "dev": true }, - "node-notifier": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", - "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "optional": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "optional": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "optional": true - } - } + "node-releases": { + "version": "1.1.76", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.76.tgz", + "integrity": "sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==", + "dev": true }, "normalize-package-data": { "version": "2.5.0", @@ -7738,12 +6876,12 @@ "dev": true }, "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "^3.0.0" } }, "nwsapi": { @@ -7756,43 +6894,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "object-inspect": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", @@ -7804,15 +6905,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, "object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", @@ -7837,15 +6929,6 @@ "has": "^1.0.3" } }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, "object.values": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", @@ -7982,12 +7065,6 @@ "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "dev": true }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -8111,12 +7188,6 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8130,9 +7201,9 @@ "dev": true }, "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { @@ -8161,9 +7232,9 @@ "integrity": "sha512-7qmhptnR0WMSpxT5rMHG9bW/mYSR1uqaPFj2MHvT+y/aOUu6msJijpKt5SkTDKySwg65OWG2JwTMBlgcbwMHrQ==" }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, "pify": { @@ -8259,12 +7330,6 @@ "semver-compare": "^1.0.0" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -8286,45 +7351,27 @@ } }, "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "version": "27.2.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.2.tgz", + "integrity": "sha512-+DdLh+rtaElc2SQOE/YPH8k2g3Rf2OXWEpy06p8Szs3hdVSYD87QOOlYRHWAeb/59XTmeVmRKvDD0svHqf6ycA==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", + "@jest/types": "^27.1.1", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", "react-is": "^17.0.1" }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true } } @@ -8341,9 +7388,9 @@ "dev": true }, "prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", + "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", "dev": true, "requires": { "kleur": "^3.0.3", @@ -8399,16 +7446,6 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -8476,61 +7513,10 @@ } }, "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true }, "readable-stream": { "version": "3.6.0", @@ -8550,40 +7536,12 @@ "readable-stream": "^3.6.0" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -8596,12 +7554,6 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -8647,12 +7599,6 @@ "global-dirs": "^0.1.1" } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -8663,12 +7609,6 @@ "signal-exit": "^3.0.2" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -8678,12 +7618,6 @@ "glob": "^7.1.3" } }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -8704,58 +7638,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, "sax": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", @@ -8832,62 +7719,26 @@ "send": "0.16.2" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "^3.0.0" } }, "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -8979,119 +7830,6 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "socks": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.0.tgz", @@ -9127,48 +7865,21 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -9206,15 +7917,6 @@ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -9227,9 +7929,9 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", + "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" @@ -9243,27 +7945,6 @@ } } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -9281,9 +7962,9 @@ "dev": true }, "string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "requires": { "char-regex": "^1.0.2", @@ -9291,18 +7972,18 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } } } @@ -9397,12 +8078,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -9520,9 +8195,9 @@ } }, "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", "dev": true, "requires": { "has-flag": "^4.0.0", @@ -9628,18 +8303,18 @@ }, "dependencies": { "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" } }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } @@ -9667,9 +8342,9 @@ "dev": true }, "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", + "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, "through": { @@ -9699,52 +8374,13 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" } }, "toidentifier": { @@ -9884,18 +8520,6 @@ "which-boxed-primitive": "^1.0.1" } }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -9906,46 +8530,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -9963,12 +8547,6 @@ } } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -9978,12 +8556,6 @@ "querystring": "0.2.0" } }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10006,9 +8578,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", - "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", + "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -10114,12 +8686,6 @@ "is-symbol": "^1.0.3" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "winston": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", @@ -10357,9 +8923,9 @@ "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yallist": { @@ -10373,28 +8939,24 @@ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -10404,36 +8966,32 @@ "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } } } }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } } diff --git a/package.json b/package.json index e27c13248..50221238c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "eslint-plugin-only-warn": "^1.0.2", "eslint-plugin-prettier": "^3.4.0", "husky": "^6.0.0", - "jest": "^26.6.3", + "jest": "^27.2.2", "lint-staged": "^11.1.2", "prettier": "2.2.1" }, From e06abd98ee8a40baea4e728d600f26da9419c9c6 Mon Sep 17 00:00:00 2001 From: Alexander Lee Date: Thu, 14 Oct 2021 12:45:41 +0800 Subject: [PATCH 23/25] Refactor/collection refactor (#281) * Feat: add request schema for collection router * Feat: add mover service * Fix: updateOrder to not require sha * Feat: add base directory service * Feat: add collection directory service * Feat: add updateSubcollection method in SubcollectionPageService * Feat: subcollectionDirectoryService * Feat: add collection router * Feat: add v2 router to server * Fix: change const variable in collection directory service * Fix: update auth middleware for new endpoints * Fix: conversion of collection.yml to file object array * Fix: collectionPageService and subcollectionPageService tests This commit fixes an issue in the subcollection page tests for create. We previously expected the same object to be returned in the convertDataToMarkdown function, which would always succeed regardless of the actual value of the third_nav_title parameter in the front matter, since the object itself was modified in the method. * Feat: add test for update subcollection * Fix: change request schema for reorder directory * Fix: tighten check for path in base directory service * Fix: use new method arguments for moverService * Fix: update return of listAllCollections * Fix: only return file name for subcollectionDirectoryService.listFiles * Fix: githubService as dependency for SubcollectionDIrectoryService * Fix: handle placeholder file separately when renaming subdirectory * Fix: populate reqDetails object properly * Feat: add route for moving collection pages * Feat: add unlinked pages directory service * Feat: add unlinked page directory endpoints to unlinked page router This commit also changes the endpoint of unlinked pages to use `/:siteName/pages/pages`, to prevent overlap with the page moving endpoint * Fix: update auth middleware with new routes * Fix: retrieve target collection/subcollectionname from move request body * Fix: import move directory request schema for unlinked page router * Fix: variable name * Fix: add check for protected folder when deleting * Fix: do not change third_nav_title when renaming files * Fix: deslugify subcollection name on creation This commit deslugifies the subcollection name provided by the user on creation - the purpose of this is to remain consistent with the display on the cms vs on the actual site, since we currently have to deslugify the subcollection name when populating the third_nav_title for files on existing subfolders. * Fix: reorder subcollection * Fix: deslugify renamed subcollection * Fix: enforce that items and target are required for reorder and move directory schemas * Chore: install packages * Feat: slugify collection names when creating new collections * fix(services): change response schema for createDirectory services * Feat: change format of git tree request object * Nit: add comment * Nit: edit comment for directory * Feat: add additional validation for subcollection and page creation and renaming * Fix: rename subcollection check * Fix: slugify renamed collection * Fix: add check for invalid file types when renaming subcollection * Fix: modify validation to exclude .md for files * Feat: change titleSpecialCharCheck to use destructured arguments * Chore: move validators file under validators folder * Refactor/collection refactor tests (#283) * Feat: add tests for baseDirectoryService * Feat: add tests for collectionDirectoryService * Feat: add tests for subcollectionDirectoryService * Feat: add tests for unlinkedPagesDirectoryService * Feat: add tests for collections router * Feat: add tests for new routes in unlinked pages router * Feat: add tests for moverService * Feat: add tests for navYmlService * Fix: update schema for creating collection/subcollection in tests * Fix: update baseDirectoryService tests with new git tree format * Feat: add validation tests for names * Feat: add test for slugification when renaming collection * Fix: change file names in tests to include .md * Chore: update test command to use updated env vars Co-authored-by: Alexis --- middleware/auth.js | 105 +++- newroutes/__tests__/Collections.spec.js | 572 ++++++++++++++++++ newroutes/__tests__/UnlinkedPages.spec.js | 144 ++++- newroutes/collections.js | 276 +++++++++ newroutes/unlinkedPages.js | 55 +- package-lock.json | 5 + package.json | 9 +- server.js | 44 ++ services/db/GitHubService.js | 9 +- services/db/__tests__/GitHubService.spec.js | 16 +- .../directoryServices/BaseDirectoryService.js | 89 +++ .../CollectionDirectoryService.js | 208 +++++++ .../SubcollectionDirectoryService.js | 189 ++++++ .../UnlinkedPagesDirectoryService.js | 40 ++ .../__tests__/BaseDirectoryService.spec.js | 182 ++++++ .../CollectionDirectoryService.spec.js | 425 +++++++++++++ .../SubcollectionDirectoryService.spec.js | 454 ++++++++++++++ .../UnlinkedPagesDirectoryService.spec.js | 112 ++++ .../MdPageServices/CollectionPageService.js | 8 + .../SubcollectionPageService.js | 39 +- .../MdPageServices/UnlinkedPageService.js | 8 + .../__tests__/CollectionPageService.spec.js | 35 +- .../SubcollectionPageService.spec.js | 103 +++- .../__tests__/UnlinkedPageService.spec.js | 25 +- .../YmlFileServices/CollectionYmlService.js | 3 +- .../__tests__/CollectionYmlService.spec.js | 1 - .../__tests__/NavYmlService.spec.js | 215 +++++++ services/moverServices/MoverService.js | 97 +++ .../__tests__/MoverService.spec.js | 324 ++++++++++ utils/utils.js | 5 + validators/RequestSchema.js | 39 ++ validators/validators.js | 14 + 32 files changed, 3763 insertions(+), 87 deletions(-) create mode 100644 newroutes/__tests__/Collections.spec.js create mode 100644 newroutes/collections.js create mode 100644 services/directoryServices/BaseDirectoryService.js create mode 100644 services/directoryServices/CollectionDirectoryService.js create mode 100644 services/directoryServices/SubcollectionDirectoryService.js create mode 100644 services/directoryServices/UnlinkedPagesDirectoryService.js create mode 100644 services/directoryServices/__tests__/BaseDirectoryService.spec.js create mode 100644 services/directoryServices/__tests__/CollectionDirectoryService.spec.js create mode 100644 services/directoryServices/__tests__/SubcollectionDirectoryService.spec.js create mode 100644 services/directoryServices/__tests__/UnlinkedPagesDirectoryService.spec.js create mode 100644 services/fileServices/YmlFileServices/__tests__/NavYmlService.spec.js create mode 100644 services/moverServices/MoverService.js create mode 100644 services/moverServices/__tests__/MoverService.spec.js create mode 100644 validators/validators.js diff --git a/middleware/auth.js b/middleware/auth.js index c02f2dee4..764c2471d 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -157,37 +157,6 @@ auth.post( verifyJwt ) -// New collection pages -auth.post("/v2/sites/:siteName/collections/:collectionName/pages", verifyJwt) -auth.post( - "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages", - verifyJwt -) -auth.get( - "/v2/sites/:siteName/collections/:collectionName/pages/:pageName", - verifyJwt -) -auth.get( - "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages/:pageName", - verifyJwt -) -auth.post( - "/v2/sites/:siteName/collections/:collectionName/pages/:pageName", - verifyJwt -) -auth.post( - "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages/:pageName", - verifyJwt -) -auth.delete( - "/v2/sites/:siteName/collections/:collectionName/pages/:pageName", - verifyJwt -) -auth.delete( - "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages/:pageName", - verifyJwt -) - // Collections auth.get("/v1/sites/:siteName/collections", verifyJwt) auth.post("/v1/sites/:siteName/collections", verifyJwt) @@ -314,6 +283,80 @@ auth.get("/v1/sites/:siteName", verifyJwt) auth.get("/v1/sites/:siteName/lastUpdated", verifyJwt) auth.get("/v1/sites/:siteName/stagingUrl", verifyJwt) +// V2 Endpoints + +// Unlinked pages +auth.get("/v2/sites/:siteName/pages", verifyJwt) +auth.post("/v2/sites/:siteName/pages/pages", verifyJwt) +auth.get("/v2/sites/:siteName/pages/pages/:pageName", verifyJwt) +auth.post("/v2/sites/:siteName/pages/pages/:pageName", verifyJwt) +auth.delete("/v2/sites/:siteName/pages/pages/:pageName", verifyJwt) +auth.post("/v2/sites/:siteName/pages/move", verifyJwt) + +// Collection pages +auth.post("/v2/sites/:siteName/collections/:collectionName/pages", verifyJwt) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages", + verifyJwt +) +auth.get( + "/v2/sites/:siteName/collections/:collectionName/pages/:pageName", + verifyJwt +) +auth.get( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages/:pageName", + verifyJwt +) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/pages/:pageName", + verifyJwt +) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages/:pageName", + verifyJwt +) +auth.delete( + "/v2/sites/:siteName/collections/:collectionName/pages/:pageName", + verifyJwt +) +auth.delete( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/pages/:pageName", + verifyJwt +) + +// Collections +auth.get("/v2/sites/:siteName/collections", verifyJwt) +auth.get("/v2/sites/:siteName/collections/:collectionName", verifyJwt) +auth.get( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName", + verifyJwt +) +auth.post("/v2/sites/:siteName/collections", verifyJwt) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/subcollections", + verifyJwt +) +auth.post("/v2/sites/:siteName/collections/:collectionName", verifyJwt) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName", + verifyJwt +) +auth.delete("/v2/sites/:siteName/collections/:collectionName", verifyJwt) +auth.delete( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName", + verifyJwt +) +auth.post("/v2/sites/:siteName/collections/:collectionName/reorder", verifyJwt) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/reorder", + verifyJwt +) +auth.post("/v2/sites/:siteName/collections/:collectionName/move", verifyJwt) +auth.post( + "/v2/sites/:siteName/collections/:collectionName/subcollections/:subcollectionName/move", + verifyJwt +) + auth.use((req, res, next) => { if (!req.route) { return res.status(404).send("Unauthorised for unknown route") diff --git a/newroutes/__tests__/Collections.spec.js b/newroutes/__tests__/Collections.spec.js new file mode 100644 index 000000000..a2f3714b3 --- /dev/null +++ b/newroutes/__tests__/Collections.spec.js @@ -0,0 +1,572 @@ +const express = require("express") +const request = require("supertest") + +const { errorHandler } = require("@middleware/errorHandler") +const { attachReadRouteHandlerWrapper } = require("@middleware/routeHandler") + +const { CollectionsRouter } = require("../collections") + +describe("Collections Router", () => { + const mockCollectionDirectoryService = { + listAllCollections: jest.fn(), + listFiles: jest.fn(), + createDirectory: jest.fn(), + renameDirectory: jest.fn(), + deleteDirectory: jest.fn(), + reorderDirectory: jest.fn(), + movePages: jest.fn(), + } + + const mockSubcollectionDirectoryService = { + listFiles: jest.fn(), + createDirectory: jest.fn(), + renameDirectory: jest.fn(), + deleteDirectory: jest.fn(), + reorderDirectory: jest.fn(), + movePages: jest.fn(), + } + + const router = new CollectionsRouter({ + collectionDirectoryService: mockCollectionDirectoryService, + subcollectionDirectoryService: mockSubcollectionDirectoryService, + }) + + const app = express() + app.use(express.json({ limit: "7mb" })) + app.use(express.urlencoded({ extended: false })) + + // We can use read route handler here because we don't need to lock the repo + app.get( + "/:siteName/collections", + attachReadRouteHandlerWrapper(router.listAllCollections) + ) + app.get( + "/:siteName/collections/:collectionName", + attachReadRouteHandlerWrapper(router.listCollectionDirectoryFiles) + ) + app.get( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName", + attachReadRouteHandlerWrapper(router.listCollectionDirectoryFiles) + ) + app.post( + "/:siteName/collections", + attachReadRouteHandlerWrapper(router.createCollectionDirectory) + ) + app.post( + "/:siteName/collections/:collectionName/subcollections", + attachReadRouteHandlerWrapper(router.createCollectionDirectory) + ) + app.post( + "/:siteName/collections/:collectionName", + attachReadRouteHandlerWrapper(router.renameCollectionDirectory) + ) + app.post( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName", + attachReadRouteHandlerWrapper(router.renameCollectionDirectory) + ) + app.delete( + "/:siteName/collections/:collectionName", + attachReadRouteHandlerWrapper(router.deleteCollectionDirectory) + ) + app.delete( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName", + attachReadRouteHandlerWrapper(router.deleteCollectionDirectory) + ) + app.post( + "/:siteName/collections/:collectionName/reorder", + attachReadRouteHandlerWrapper(router.reorderCollectionDirectory) + ) + app.post( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName/reorder", + attachReadRouteHandlerWrapper(router.reorderCollectionDirectory) + ) + app.post( + "/:siteName/collections/:collectionName/move", + attachReadRouteHandlerWrapper(router.moveCollectionDirectoryPages) + ) + app.post( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName/move", + attachReadRouteHandlerWrapper(router.moveCollectionDirectoryPages) + ) + + app.use(errorHandler) + + const siteName = "test-site" + const collectionName = "collection" + const subcollectionName = "subcollection" + + // Can't set request fields - will always be undefined + const accessToken = undefined + const currentCommitSha = undefined + const treeSha = undefined + + const reqDetails = { siteName, accessToken } + const additionalReqDetails = { ...reqDetails, currentCommitSha, treeSha } + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("listAllCollections", () => { + it("lists the set of all collections", async () => { + const expectedResponse = [ + { + name: "test-col", + type: "dir", + }, + { + name: "test-col2", + type: "dir", + }, + ] + mockCollectionDirectoryService.listAllCollections.mockResolvedValueOnce( + expectedResponse + ) + const resp = await request(app) + .get(`/${siteName}/collections`) + .expect(200) + expect(resp.body).toStrictEqual(expectedResponse) + expect( + mockCollectionDirectoryService.listAllCollections + ).toHaveBeenCalledWith(reqDetails) + }) + }) + + describe("listCollectionDirectoryFiles", () => { + it("returns the details of all files in a collection", async () => { + const expectedResponse = [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + { + name: "testsub", + type: "dir", + children: ["file1", "file2"], + }, + { + name: "testfile2", + type: "file", + }, + ] + mockCollectionDirectoryService.listFiles.mockResolvedValueOnce( + expectedResponse + ) + const resp = await request(app) + .get(`/${siteName}/collections/${collectionName}`) + .expect(200) + expect(resp.body).toStrictEqual(expectedResponse) + expect( + mockCollectionDirectoryService.listFiles + ).toHaveBeenCalledWith(reqDetails, { collectionName }) + }) + it("returns all files in a subcollection", async () => { + const expectedResponse = [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + ] + mockSubcollectionDirectoryService.listFiles.mockResolvedValueOnce( + expectedResponse + ) + const resp = await request(app) + .get( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}` + ) + .expect(200) + expect(resp.body).toStrictEqual(expectedResponse) + expect( + mockSubcollectionDirectoryService.listFiles + ).toHaveBeenCalledWith(reqDetails, { collectionName, subcollectionName }) + }) + }) + + describe("createCollectionDirectory", () => { + it("rejects requests with invalid body", async () => { + await request(app).post(`/${siteName}/collections`).send({}).expect(400) + }) + + it("accepts valid collection create requests with no specified files and returns the details of the collection created", async () => { + mockCollectionDirectoryService.createDirectory.mockResolvedValueOnce([]) + const collectionDetails = { + newDirectoryName: collectionName, + } + const resp = await request(app) + .post(`/${siteName}/collections`) + .send(collectionDetails) + .expect(200) + expect(resp.body).toStrictEqual([]) + expect( + mockCollectionDirectoryService.createDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + objArray: undefined, + }) + }) + + it("accepts valid collection create requests with specified files and returns the details of the collection created", async () => { + const collectionDetails = { + newDirectoryName: collectionName, + items: [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + ], + } + mockCollectionDirectoryService.createDirectory.mockResolvedValueOnce( + collectionDetails.items + ) + const resp = await request(app) + .post(`/${siteName}/collections`) + .send(collectionDetails) + .expect(200) + expect(resp.body).toStrictEqual(collectionDetails.items) + expect( + mockCollectionDirectoryService.createDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + objArray: collectionDetails.items, + }) + }) + + it("accepts valid subcollection create requests with no specified files and returns the details of the subcollection created", async () => { + mockSubcollectionDirectoryService.createDirectory.mockResolvedValueOnce( + [] + ) + const collectionDetails = { + newDirectoryName: subcollectionName, + } + const resp = await request(app) + .post(`/${siteName}/collections/${collectionName}/subcollections`) + .send(collectionDetails) + .expect(200) + expect(resp.body).toStrictEqual([]) + expect( + mockSubcollectionDirectoryService.createDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + subcollectionName, + objArray: undefined, + }) + }) + + it("accepts valid collection create requests with specified files and returns the details of the collection created", async () => { + const collectionDetails = { + newDirectoryName: subcollectionName, + items: [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + ], + } + mockSubcollectionDirectoryService.createDirectory.mockResolvedValueOnce( + collectionDetails.items + ) + const resp = await request(app) + .post(`/${siteName}/collections/${collectionName}/subcollections`) + .send(collectionDetails) + .expect(200) + expect(resp.body).toStrictEqual(collectionDetails.items) + expect( + mockSubcollectionDirectoryService.createDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + subcollectionName, + objArray: collectionDetails.items, + }) + }) + }) + + describe("renameCollectionDirectory", () => { + const newDirectoryName = "new-dir" + + it("rejects requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}`) + .send({}) + .expect(400) + }) + + it("accepts valid collection rename requests", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}`) + .send({ newDirectoryName }) + .expect(200) + expect( + mockCollectionDirectoryService.renameDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + newDirectoryName, + }) + }) + + it("accepts valid subcollection rename requests", async () => { + await request(app) + .post( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}` + ) + .send({ newDirectoryName }) + .expect(200) + expect( + mockSubcollectionDirectoryService.renameDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + subcollectionName, + newDirectoryName, + }) + }) + }) + + describe("deleteCollectionDirectory", () => { + it("accepts valid collection delete requests", async () => { + await request(app) + .delete(`/${siteName}/collections/${collectionName}`) + .expect(200) + expect( + mockCollectionDirectoryService.deleteDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + }) + }) + + it("accepts valid subcollection delete requests", async () => { + await request(app) + .delete( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}` + ) + .expect(200) + expect( + mockSubcollectionDirectoryService.deleteDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + subcollectionName, + }) + }) + }) + + describe("reorderCollectionDirectory", () => { + const reorderDetails = { + items: [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + ], + } + it("rejects requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/reorder`) + .send({}) + .expect(400) + }) + + it("accepts valid collection reorder requests", async () => { + mockCollectionDirectoryService.reorderDirectory.mockResolvedValueOnce( + reorderDetails.items + ) + const resp = await request(app) + .post(`/${siteName}/collections/${collectionName}/reorder`) + .send(reorderDetails) + .expect(200) + expect(resp.body).toStrictEqual(reorderDetails.items) + expect( + mockCollectionDirectoryService.reorderDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + objArray: reorderDetails.items, + }) + }) + + it("accepts valid subcollection reorder requests", async () => { + mockSubcollectionDirectoryService.reorderDirectory.mockResolvedValueOnce( + reorderDetails.items + ) + const resp = await request(app) + .post( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}/reorder` + ) + .send(reorderDetails) + .expect(200) + expect(resp.body).toStrictEqual(reorderDetails.items) + expect( + mockSubcollectionDirectoryService.reorderDirectory + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + subcollectionName, + objArray: reorderDetails.items, + }) + }) + }) + + describe("moveCollectionDirectoryPages", () => { + const targetCollectionName = "collection" + const targetSubcollectionName = "subcollection" + const items = [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + ] + it("rejects move requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/move`) + .send({}) + .expect(400) + }) + + it("rejects move requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/move`) + .send({ + target: {}, + items: items.concat({ name: "testdir", type: "dir" }), + }) + .expect(400) + }) + + it("rejects move requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/move`) + .send({ items }) + .expect(400) + }) + + it("accepts valid collection page move requests to unlinked pages", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/move`) + .send({ items, target: {} }) + .expect(200) + expect(mockCollectionDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + objArray: items, + } + ) + }) + + it("accepts valid collection page move requests to another collection", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/move`) + .send({ items, target: { collectionName: targetCollectionName } }) + .expect(200) + expect(mockCollectionDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + targetCollectionName, + objArray: items, + } + ) + }) + + it("accepts valid collection page move requests to a subcollection", async () => { + await request(app) + .post(`/${siteName}/collections/${collectionName}/move`) + .send({ + items, + target: { + collectionName: targetCollectionName, + subCollectionName: targetSubcollectionName, + }, + }) + .expect(200) + expect(mockCollectionDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + targetCollectionName, + targetSubcollectionName, + objArray: items, + } + ) + }) + + it("accepts valid subcollection page move requests to unlinked pages", async () => { + await request(app) + .post( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}/move` + ) + .send({ items, target: {} }) + .expect(200) + expect(mockSubcollectionDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + subcollectionName, + objArray: items, + } + ) + }) + + it("accepts valid subcollection page move requests to a collection", async () => { + await request(app) + .post( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}/move` + ) + .send({ items, target: { collectionName: targetCollectionName } }) + .expect(200) + expect(mockSubcollectionDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + subcollectionName, + targetCollectionName, + objArray: items, + } + ) + }) + + it("accepts valid subcollection page move requests to another subcollection", async () => { + await request(app) + .post( + `/${siteName}/collections/${collectionName}/subcollections/${subcollectionName}/move` + ) + .send({ + items, + target: { + collectionName: targetCollectionName, + subCollectionName: targetSubcollectionName, + }, + }) + .expect(200) + expect(mockSubcollectionDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + subcollectionName, + targetCollectionName, + targetSubcollectionName, + objArray: items, + } + ) + }) + }) +}) diff --git a/newroutes/__tests__/UnlinkedPages.spec.js b/newroutes/__tests__/UnlinkedPages.spec.js index 463ce9496..868267732 100644 --- a/newroutes/__tests__/UnlinkedPages.spec.js +++ b/newroutes/__tests__/UnlinkedPages.spec.js @@ -15,8 +15,14 @@ describe("Unlinked Pages Router", () => { rename: jest.fn(), } + const mockUnlinkedPagesDirectoryService = { + listAllUnlinkedPages: jest.fn(), + movePages: jest.fn(), + } + const router = new UnlinkedPagesRouter({ unlinkedPageService: mockService, + unlinkedPagesDirectoryService: mockUnlinkedPagesDirectoryService, }) const app = express() @@ -24,22 +30,30 @@ describe("Unlinked Pages Router", () => { app.use(express.urlencoded({ extended: false })) // We can use read route handler here because we don't need to lock the repo - app.post( + app.get( "/:siteName/pages", + attachReadRouteHandlerWrapper(router.listAllUnlinkedPages) + ) + app.post( + "/:siteName/pages/pages", attachReadRouteHandlerWrapper(router.createUnlinkedPage) ) app.get( - "/:siteName/pages/:pageName", + "/:siteName/pages/pages/:pageName", attachReadRouteHandlerWrapper(router.readUnlinkedPage) ) app.post( - "/:siteName/pages/:pageName", + "/:siteName/pages/pages/:pageName", attachReadRouteHandlerWrapper(router.updateUnlinkedPage) ) app.delete( - "/:siteName/pages/:pageName", + "/:siteName/pages/pages/:pageName", attachReadRouteHandlerWrapper(router.deleteUnlinkedPage) ) + app.post( + "/:siteName/pages/move", + attachReadRouteHandlerWrapper(router.moveUnlinkedPages) + ) app.use(errorHandler) const siteName = "test-site" @@ -54,6 +68,39 @@ describe("Unlinked Pages Router", () => { jest.clearAllMocks() }) + describe("listAllUnlinkedPages", () => { + const listPageResp = [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + { + name: "testsub", + type: "dir", + children: ["file1", "file2"], + }, + { + name: "testfile2", + type: "file", + }, + ] + + it("returns the list of unlinked pages", async () => { + mockUnlinkedPagesDirectoryService.listAllUnlinkedPages.mockResolvedValueOnce( + listPageResp + ) + const resp = await request(app).get(`/${siteName}/pages`).expect(200) + expect(resp.body).toStrictEqual(listPageResp) + expect( + mockUnlinkedPagesDirectoryService.listAllUnlinkedPages + ).toHaveBeenCalledWith(reqDetails) + }) + }) + describe("createUnlinkedPage", () => { const createPageDetails = { newFileName: "newFile", @@ -67,7 +114,7 @@ describe("Unlinked Pages Router", () => { } it("rejects create requests with invalid body", async () => { - await request(app).post(`/${siteName}/pages`).send({}).expect(400) + await request(app).post(`/${siteName}/pages/pages`).send({}).expect(400) }) it("accepts valid unlinked page creation requests and returns the details of the file created", async () => { @@ -77,7 +124,7 @@ describe("Unlinked Pages Router", () => { frontMatter: createPageDetails.content.frontMatter, } await request(app) - .post(`/${siteName}/pages`) + .post(`/${siteName}/pages/pages`) .send(createPageDetails) .expect(200) expect(mockService.create).toHaveBeenCalledWith( @@ -97,7 +144,7 @@ describe("Unlinked Pages Router", () => { const expectedServiceInput = { fileName, } - await request(app).get(`/${siteName}/pages/${fileName}`).expect(200) + await request(app).get(`/${siteName}/pages/pages/${fileName}`).expect(200) expect(mockService.read).toHaveBeenCalledWith( reqDetails, expectedServiceInput @@ -123,7 +170,7 @@ describe("Unlinked Pages Router", () => { it("rejects update requests with invalid body", async () => { await request(app) - .post(`/${siteName}/pages/${fileName}`) + .post(`/${siteName}/pages/pages/${fileName}`) .send({}) .expect(400) }) @@ -136,7 +183,7 @@ describe("Unlinked Pages Router", () => { sha: updatePageDetails.sha, } await request(app) - .post(`/${siteName}/pages/${fileName}`) + .post(`/${siteName}/pages/pages/${fileName}`) .send(updatePageDetails) .expect(200) expect(mockService.update).toHaveBeenCalledWith( @@ -154,7 +201,7 @@ describe("Unlinked Pages Router", () => { sha: renamePageDetails.sha, } await request(app) - .post(`/${siteName}/pages/${fileName}`) + .post(`/${siteName}/pages/pages/${fileName}`) .send(renamePageDetails) .expect(200) expect(mockService.rename).toHaveBeenCalledWith( @@ -171,7 +218,7 @@ describe("Unlinked Pages Router", () => { it("rejects delete requests with invalid body", async () => { await request(app) - .delete(`/${siteName}/pages/${fileName}`) + .delete(`/${siteName}/pages/pages/${fileName}`) .send({}) .expect(400) }) @@ -182,7 +229,7 @@ describe("Unlinked Pages Router", () => { sha: deletePageDetails.sha, } await request(app) - .delete(`/${siteName}/pages/${fileName}`) + .delete(`/${siteName}/pages/pages/${fileName}`) .send(deletePageDetails) .expect(200) expect(mockService.delete).toHaveBeenCalledWith( @@ -192,5 +239,76 @@ describe("Unlinked Pages Router", () => { }) }) - // TO-DO: Add listUnlinkedPages tests + describe("moveUnlinkedPages", () => { + const collectionName = "collection" + const subCollectionName = "subcollection" + const items = [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + ] + const expectedRequestInput = { + target: { + collectionName, + }, + items, + } + it("rejects move requests with invalid body", async () => { + await request(app).post(`/${siteName}/pages/move`).send({}).expect(400) + }) + + it("rejects move requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/pages/move`) + .send({ + ...expectedRequestInput, + items: items.concat({ name: "testdir", type: "dir" }), + }) + .expect(400) + }) + + it("rejects move requests with invalid body", async () => { + await request(app) + .post(`/${siteName}/pages/move`) + .send({ items }) + .expect(400) + }) + + it("accepts valid unlinked page move requests to collections", async () => { + await request(app) + .post(`/${siteName}/pages/move`) + .send(expectedRequestInput) + .expect(200) + expect(mockUnlinkedPagesDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + targetCollectionName: collectionName, + objArray: items, + } + ) + }) + + it("accepts valid unlinked page move requests to subcollections", async () => { + await request(app) + .post(`/${siteName}/pages/move`) + .send({ + ...expectedRequestInput, + target: { collectionName, subCollectionName }, + }) + .expect(200) + expect(mockUnlinkedPagesDirectoryService.movePages).toHaveBeenCalledWith( + reqDetails, + { + targetCollectionName: collectionName, + targetSubcollectionName: subCollectionName, + objArray: items, + } + ) + }) + }) }) diff --git a/newroutes/collections.js b/newroutes/collections.js new file mode 100644 index 000000000..af9596da6 --- /dev/null +++ b/newroutes/collections.js @@ -0,0 +1,276 @@ +const autoBind = require("auto-bind") +const express = require("express") + +// Import middleware +const { BadRequestError } = require("@errors/BadRequestError") + +const { + attachReadRouteHandlerWrapper, + attachRollbackRouteHandlerWrapper, +} = require("@middleware/routeHandler") + +const { + CreateDirectoryRequestSchema, + RenameDirectoryRequestSchema, + ReorderDirectoryRequestSchema, + MoveDirectoryPagesRequestSchema, +} = require("@validators/RequestSchema") + +class CollectionsRouter { + constructor({ collectionDirectoryService, subcollectionDirectoryService }) { + this.collectionDirectoryService = collectionDirectoryService + this.subcollectionDirectoryService = subcollectionDirectoryService + // We need to bind all methods because we don't invoke them from the class directly + autoBind(this) + } + + // List all collections + async listAllCollections(req, res) { + const { accessToken } = req + + const { siteName } = req.params + const listResp = await this.collectionDirectoryService.listAllCollections({ + siteName, + accessToken, + }) + + return res.status(200).json(listResp) + } + + // List files in a collection/subcollection + async listCollectionDirectoryFiles(req, res) { + const { accessToken } = req + + const { siteName, collectionName, subcollectionName } = req.params + let listResp + if (subcollectionName) { + listResp = await this.subcollectionDirectoryService.listFiles( + { siteName, accessToken }, + { collectionName, subcollectionName } + ) + } else { + listResp = await this.collectionDirectoryService.listFiles( + { siteName, accessToken }, + { collectionName } + ) + } + return res.status(200).json(listResp) + } + + // Create new collection/subcollection + async createCollectionDirectory(req, res) { + const { accessToken } = req + + const { siteName, collectionName } = req.params + const { error } = CreateDirectoryRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error.message) + const { newDirectoryName, items } = req.body + let createResp + if (collectionName) { + // Creating subcollection + createResp = await this.subcollectionDirectoryService.createDirectory( + { siteName, accessToken }, + { + collectionName, + subcollectionName: newDirectoryName, + objArray: items, + } + ) + } else { + // Creating collection + createResp = await this.collectionDirectoryService.createDirectory( + { siteName, accessToken }, + { + collectionName: newDirectoryName, + objArray: items, + } + ) + } + + return res.status(200).json(createResp) + } + + // Rename collection/subcollection + async renameCollectionDirectory(req, res) { + const { accessToken, currentCommitSha, treeSha } = req + + const { siteName, collectionName, subcollectionName } = req.params + const { error } = RenameDirectoryRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error.message) + const { newDirectoryName } = req.body + if (subcollectionName) { + await this.subcollectionDirectoryService.renameDirectory( + { siteName, accessToken, currentCommitSha, treeSha }, + { + collectionName, + subcollectionName, + newDirectoryName, + } + ) + } else { + await this.collectionDirectoryService.renameDirectory( + { siteName, accessToken, currentCommitSha, treeSha }, + { + collectionName, + newDirectoryName, + } + ) + } + + return res.status(200).send("OK") + } + + // Delete collection/subcollection + async deleteCollectionDirectory(req, res) { + const { accessToken, currentCommitSha, treeSha } = req + + const { siteName, collectionName, subcollectionName } = req.params + if (subcollectionName) { + await this.subcollectionDirectoryService.deleteDirectory( + { siteName, accessToken, currentCommitSha, treeSha }, + { + collectionName, + subcollectionName, + } + ) + } else { + await this.collectionDirectoryService.deleteDirectory( + { siteName, accessToken, currentCommitSha, treeSha }, + { + collectionName, + } + ) + } + return res.status(200).send("OK") + } + + // Reorder collection/subcollection + async reorderCollectionDirectory(req, res) { + const { accessToken, currentCommitSha, treeSha } = req + + const { siteName, collectionName, subcollectionName } = req.params + const { error } = ReorderDirectoryRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error.message) + const { items } = req.body + let reorderResp + if (subcollectionName) { + reorderResp = await this.subcollectionDirectoryService.reorderDirectory( + { siteName, accessToken, currentCommitSha, treeSha }, + { + collectionName, + subcollectionName, + objArray: items, + } + ) + } else { + reorderResp = await this.collectionDirectoryService.reorderDirectory( + { siteName, accessToken, currentCommitSha, treeSha }, + { + collectionName, + objArray: items, + } + ) + } + return res.status(200).json(reorderResp) + } + + // Move collection/subcollection pages + async moveCollectionDirectoryPages(req, res) { + const { accessToken } = req + + const { siteName, collectionName, subcollectionName } = req.params + const { error } = MoveDirectoryPagesRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error.message) + const { + items, + target: { + collectionName: targetCollectionName, + subCollectionName: targetSubcollectionName, + }, + } = req.body + if (subcollectionName) { + await this.subcollectionDirectoryService.movePages( + { siteName, accessToken }, + { + collectionName, + subcollectionName, + targetCollectionName, + targetSubcollectionName, + objArray: items, + } + ) + } else { + await this.collectionDirectoryService.movePages( + { siteName, accessToken }, + { + collectionName, + targetCollectionName, + targetSubcollectionName, + objArray: items, + } + ) + } + return res.status(200).send("OK") + } + + getRouter() { + const router = express.Router() + + router.get( + "/:siteName/collections", + attachReadRouteHandlerWrapper(this.listAllCollections) + ) + router.get( + "/:siteName/collections/:collectionName", + attachReadRouteHandlerWrapper(this.listCollectionDirectoryFiles) + ) + router.get( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName", + attachReadRouteHandlerWrapper(this.listCollectionDirectoryFiles) + ) + router.post( + "/:siteName/collections", + attachRollbackRouteHandlerWrapper(this.createCollectionDirectory) + ) + router.post( + "/:siteName/collections/:collectionName/subcollections", + attachRollbackRouteHandlerWrapper(this.createCollectionDirectory) + ) + router.post( + "/:siteName/collections/:collectionName", + attachRollbackRouteHandlerWrapper(this.renameCollectionDirectory) + ) + router.post( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName", + attachRollbackRouteHandlerWrapper(this.renameCollectionDirectory) + ) + router.delete( + "/:siteName/collections/:collectionName", + attachRollbackRouteHandlerWrapper(this.deleteCollectionDirectory) + ) + router.delete( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName", + attachRollbackRouteHandlerWrapper(this.deleteCollectionDirectory) + ) + router.post( + "/:siteName/collections/:collectionName/reorder", + attachRollbackRouteHandlerWrapper(this.reorderCollectionDirectory) + ) + router.post( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName/reorder", + attachRollbackRouteHandlerWrapper(this.reorderCollectionDirectory) + ) + router.post( + "/:siteName/collections/:collectionName/move", + attachRollbackRouteHandlerWrapper(this.moveCollectionDirectoryPages) + ) + router.post( + "/:siteName/collections/:collectionName/subcollections/:subcollectionName/move", + attachRollbackRouteHandlerWrapper(this.moveCollectionDirectoryPages) + ) + + return router + } +} + +module.exports = { CollectionsRouter } diff --git a/newroutes/unlinkedPages.js b/newroutes/unlinkedPages.js index 69248798c..b554a9cc0 100644 --- a/newroutes/unlinkedPages.js +++ b/newroutes/unlinkedPages.js @@ -14,15 +14,28 @@ const { CreatePageRequestSchema, UpdatePageRequestSchema, DeletePageRequestSchema, + MoveDirectoryPagesRequestSchema, } = require("@validators/RequestSchema") class UnlinkedPagesRouter { - constructor({ unlinkedPageService }) { + constructor({ unlinkedPageService, unlinkedPagesDirectoryService }) { this.unlinkedPageService = unlinkedPageService + this.unlinkedPagesDirectoryService = unlinkedPagesDirectoryService // We need to bind all methods because we don't invoke them from the class directly autoBind(this) } + async listAllUnlinkedPages(req, res) { + const { accessToken } = req + + const { siteName } = req.params + const listResp = await this.unlinkedPagesDirectoryService.listAllUnlinkedPages( + { siteName, accessToken } + ) + + return res.status(200).json(listResp) + } + async createUnlinkedPage(req, res) { const { accessToken } = req @@ -114,25 +127,57 @@ class UnlinkedPagesRouter { return res.status(200).send("OK") } + async moveUnlinkedPages(req, res) { + const { accessToken } = req + + const { siteName } = req.params + const { error } = MoveDirectoryPagesRequestSchema.validate(req.body) + if (error) throw new BadRequestError(error.message) + const { + items, + target: { + collectionName: targetCollectionName, + subCollectionName: targetSubcollectionName, + }, + } = req.body + await this.unlinkedPagesDirectoryService.movePages( + { siteName, accessToken }, + { + targetCollectionName, + targetSubcollectionName, + objArray: items, + } + ) + return res.status(200).send("OK") + } + getRouter() { const router = express.Router() - router.post( + router.get( "/:siteName/pages", + attachReadRouteHandlerWrapper(this.listAllUnlinkedPages) + ) + router.post( + "/:siteName/pages/pages", attachRollbackRouteHandlerWrapper(this.createUnlinkedPage) ) router.get( - "/:siteName/pages/:pageName", + "/:siteName/pages/pages/:pageName", attachReadRouteHandlerWrapper(this.readUnlinkedPage) ) router.post( - "/:siteName/pages/:pageName", + "/:siteName/pages/pages/:pageName", attachWriteRouteHandlerWrapper(this.updateUnlinkedPage) ) router.delete( - "/:siteName/pages/:pageName", + "/:siteName/pages/pages/:pageName", attachRollbackRouteHandlerWrapper(this.deleteUnlinkedPage) ) + router.post( + "/:siteName/pages/move", + attachRollbackRouteHandlerWrapper(this.moveUnlinkedPages) + ) return router } diff --git a/package-lock.json b/package-lock.json index 94e531792..e683d5af1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7825,6 +7825,11 @@ } } }, + "slugify": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.0.tgz", + "integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==" + }, "smart-buffer": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", diff --git a/package.json b/package.json index 50221238c..de0e88518 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "node ./bin/www", "dev": "source .env && node ./bin/www", - "test": "jest", + "test": "source .env && jest", "lint": "npx eslint .", "format": "npx prettier --write .", "check-format": "npx prettier --check .", @@ -30,9 +30,9 @@ "helmet": "^4.6.0", "http-errors": "~1.8.0", "is-svg": "^4.3.1", + "joi": "^17.4.0", "js-base64": "^2.6.4", "jsdom": "^16.7.0", - "joi": "^17.4.0", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.21", "module-alias": "^2.2.2", @@ -40,6 +40,7 @@ "morgan": "~1.10.0", "query-string": "^6.14.1", "serialize-error": "^7.0.1", + "slugify": "^1.6.0", "supertest": "^6.1.3", "toml": "^3.0.0", "uuid": "^3.3.3", @@ -96,5 +97,7 @@ "prettier --write" ] }, - "engines": { "node" : ">=14.17.5" } + "engines": { + "node": ">=14.17.5" + } } diff --git a/server.js b/server.js index b434866e0..d398d78aa 100644 --- a/server.js +++ b/server.js @@ -52,6 +52,18 @@ const { SubcollectionPageService, } = require("@root/services/fileServices/MdPageServices/SubcollectionPageService") const { GitHubService } = require("@services/db/GitHubService") +const { + BaseDirectoryService, +} = require("@services/directoryServices/BaseDirectoryService") +const { + CollectionDirectoryService, +} = require("@services/directoryServices/CollectionDirectoryService") +const { + SubcollectionDirectoryService, +} = require("@services/directoryServices/SubcollectionDirectoryService") +const { + UnlinkedPagesDirectoryService, +} = require("@services/directoryServices/UnlinkedPagesDirectoryService") const { CollectionPageService, } = require("@services/fileServices/MdPageServices/CollectionPageService") @@ -64,12 +76,15 @@ const { const { NavYmlService, } = require("@services/fileServices/YmlFileServices/NavYmlService") +const { MoverService } = require("@services/moverServices/MoverService") const { CollectionPagesRouter } = require("./newroutes/collectionPages") +const { CollectionsRouter } = require("./newroutes/collections") const { UnlinkedPagesRouter } = require("./newroutes/unlinkedPages") const gitHubService = new GitHubService({ axiosInstance }) const collectionYmlService = new CollectionYmlService({ gitHubService }) +const navYmlService = new NavYmlService({ gitHubService }) const collectionPageService = new CollectionPageService({ gitHubService, collectionYmlService, @@ -79,14 +94,42 @@ const subcollectionPageService = new SubcollectionPageService({ collectionYmlService, }) const unlinkedPageService = new UnlinkedPageService({ gitHubService }) +const moverService = new MoverService({ + unlinkedPageService, + collectionPageService, + subcollectionPageService, +}) +const baseDirectoryService = new BaseDirectoryService({ gitHubService }) +const unlinkedPagesDirectoryService = new UnlinkedPagesDirectoryService({ + baseDirectoryService, + moverService, +}) +const collectionDirectoryService = new CollectionDirectoryService({ + baseDirectoryService, + navYmlService, + collectionYmlService, + moverService, +}) +const subcollectionDirectoryService = new SubcollectionDirectoryService({ + baseDirectoryService, + collectionYmlService, + moverService, + subcollectionPageService, + gitHubService, +}) const unlinkedPagesRouter = new UnlinkedPagesRouter({ unlinkedPageService, + unlinkedPagesDirectoryService, }) const collectionPagesV2Router = new CollectionPagesRouter({ collectionPageService, subcollectionPageService, }) +const collectionsV2Router = new CollectionsRouter({ + collectionDirectoryService, + subcollectionDirectoryService, +}) const app = express() app.use(helmet()) @@ -131,6 +174,7 @@ app.use("/v1/sites", netlifyTomlRouter) app.use("/v2/sites", collectionPagesV2Router.getRouter()) app.use("/v2/sites", unlinkedPagesRouter.getRouter()) +app.use("/v2/sites", collectionsV2Router.getRouter()) app.use("/v2/ping", (req, res, next) => res.status(200).send("Ok")) diff --git a/services/db/GitHubService.js b/services/db/GitHubService.js index cdd763204..e1b7265fc 100644 --- a/services/db/GitHubService.js +++ b/services/db/GitHubService.js @@ -100,7 +100,7 @@ class GitHubService { Authorization: `token ${accessToken}`, }, }) - if (resp.status === 404) throw new NotFoundError("File does not exist") + if (resp.status === 404) throw new NotFoundError("Directory does not exist") return resp.data } @@ -229,7 +229,7 @@ class GitHubService { } async updateTree( - { accessToken, currentCommitSha, siteName }, + { accessToken, currentCommitSha, treeSha, siteName }, { gitTree, message } ) { const url = `${siteName}/git/trees` @@ -240,7 +240,10 @@ class GitHubService { const resp = await this.axiosInstance.post( url, - { tree: gitTree }, + { + tree: gitTree, + base_tree: treeSha, + }, { headers } ) diff --git a/services/db/__tests__/GitHubService.spec.js b/services/db/__tests__/GitHubService.spec.js index bf0308933..91410c431 100644 --- a/services/db/__tests__/GitHubService.spec.js +++ b/services/db/__tests__/GitHubService.spec.js @@ -13,21 +13,14 @@ describe("Github Service", () => { const siteName = "test-site" const accessToken = "test-token" const fileName = "test-file" - const subcollectionFileName = "test-subcollection-file" const collectionName = "collection" const subcollectionName = "subcollection" const directoryName = `_${collectionName}` const sha = "12345" + const treeSha = "98765" const content = "test-content" const reqDetails = { siteName, accessToken } - const orderArray = [ - `${fileName}.md`, - `${subcollectionName}/.keep`, - `${subcollectionName}/${subcollectionFileName}.md`, - `${subcollectionName}/${subcollectionFileName}2.md`, - `${fileName}2.md`, - ] const authHeader = { headers: { @@ -517,13 +510,16 @@ describe("Github Service", () => { .mockResolvedValueOnce(secondResp) await expect( service.updateTree( - { accessToken, siteName, currentCommitSha: sha }, + { accessToken, siteName, currentCommitSha: sha, treeSha }, { gitTree, message } ) ).resolves.toEqual(secondSha) expect(mockAxiosInstance.post).toHaveBeenCalledWith( url, - { tree: gitTree }, + { + tree: gitTree, + base_tree: treeSha, + }, authHeader ) expect(mockAxiosInstance.post).toHaveBeenCalledWith( diff --git a/services/directoryServices/BaseDirectoryService.js b/services/directoryServices/BaseDirectoryService.js new file mode 100644 index 000000000..121adabf5 --- /dev/null +++ b/services/directoryServices/BaseDirectoryService.js @@ -0,0 +1,89 @@ +const _ = require("lodash") + +// Job is to deal with directory level operations to and from GitHub +class BaseDirectoryService { + constructor({ gitHubService }) { + this.gitHubService = gitHubService + } + + async list(reqDetails, { directoryName }) { + const directoryData = await this.gitHubService.readDirectory(reqDetails, { + directoryName, + }) + + const filesOrDirs = directoryData.map((fileOrDir) => { + const { name, path, sha, size, type } = fileOrDir + return { + name, + path, + sha, + size, + type, + } + }) + + return _.compact(filesOrDirs) + } + + async rename(reqDetails, { oldDirectoryName, newDirectoryName, message }) { + const gitTree = await this.gitHubService.getTree(reqDetails, { + isRecursive: true, + }) + + const newGitTree = [] + + gitTree.forEach((item) => { + if (item.path === oldDirectoryName && item.type === "tree") { + // Rename old subdirectory to new name + newGitTree.push({ + ...item, + path: newDirectoryName, + }) + } else if ( + item.path.startsWith(`${oldDirectoryName}/`) && + item.type !== "tree" + ) { + // Delete old files + newGitTree.push({ + ...item, + sha: null, + }) + } + }) + + const newCommitSha = await this.gitHubService.updateTree(reqDetails, { + gitTree: newGitTree, + message, + }) + await this.gitHubService.updateRepoState(reqDetails, { + commitSha: newCommitSha, + }) + } + + async delete(reqDetails, { directoryName, message }) { + const gitTree = await this.gitHubService.getTree(reqDetails, { + isRecursive: true, + }) + + // Retrieve removed items and set their sha to null + const newGitTree = gitTree + .filter( + (item) => + item.path.startsWith(`${directoryName}/`) && item.type !== "tree" + ) + .map((item) => ({ + ...item, + sha: null, + })) + + const newCommitSha = await this.gitHubService.updateTree(reqDetails, { + gitTree: newGitTree, + message, + }) + await this.gitHubService.updateRepoState(reqDetails, { + commitSha: newCommitSha, + }) + } +} + +module.exports = { BaseDirectoryService } diff --git a/services/directoryServices/CollectionDirectoryService.js b/services/directoryServices/CollectionDirectoryService.js new file mode 100644 index 000000000..0f5653a13 --- /dev/null +++ b/services/directoryServices/CollectionDirectoryService.js @@ -0,0 +1,208 @@ +const { BadRequestError } = require("@errors/BadRequestError") +const { + ConflictError, + protectedFolderConflictErrorMsg, +} = require("@errors/ConflictError") + +const { slugifyCollectionName } = require("@utils/utils") + +const ISOMER_TEMPLATE_DIRS = ["_data", "_includes", "_site", "_layouts"] +const ISOMER_TEMPLATE_PROTECTED_DIRS = [ + "data", + "includes", + "site", + "layouts", + "files", + "images", + "misc", + "pages", +] +const PLACEHOLDER_FILE_NAME = ".keep" + +class CollectionDirectoryService { + constructor({ + baseDirectoryService, + navYmlService, + collectionYmlService, + moverService, + }) { + this.baseDirectoryService = baseDirectoryService + this.navYmlService = navYmlService + this.collectionYmlService = collectionYmlService + this.moverService = moverService + } + + convertYmlToObjOrder(fileOrder) { + function addSubcollection() { + if (currSubcollectionName !== "") { + processedFiles.push({ + name: currSubcollectionName, + type: "dir", + children: currSubcollectionFiles, + }) + currSubcollectionName = "" + currSubcollectionFiles = [] + } + } + + let currSubcollectionName = "" + let currSubcollectionFiles = [] + const processedFiles = [] + + fileOrder.forEach((filePath) => { + if (filePath.includes("/")) { + const [subcollectionName, fileName] = filePath.split("/") + if (subcollectionName !== currSubcollectionName) { + addSubcollection() + currSubcollectionName = subcollectionName + } + if (fileName !== ".keep") currSubcollectionFiles.push(fileName) + } else { + addSubcollection() + processedFiles.push({ + name: filePath, + type: "file", + }) + } + }) + addSubcollection() + return processedFiles + } + + convertObjToYmlOrder(objArr) { + const fileOrder = [] + objArr.forEach((obj) => { + if (obj.type === "dir") { + const subcollectionName = obj.name + fileOrder.push(`${subcollectionName}/${PLACEHOLDER_FILE_NAME}`) + obj.children.forEach((fileName) => { + fileOrder.push(`${subcollectionName}/${fileName}`) + }) + } else { + fileOrder.push(obj.name) + } + }) + return fileOrder + } + + async listAllCollections(reqDetails) { + const filesOrDirs = await this.baseDirectoryService.list(reqDetails, { + directoryName: "", + }) + return filesOrDirs.reduce((acc, curr) => { + if ( + curr.type === "dir" && + !ISOMER_TEMPLATE_DIRS.includes(curr.name) && + curr.name.slice(0, 1) === "_" + ) + acc.push({ + name: curr.path.slice(1), + type: "dir", + }) + return acc + }, []) + } + + async listFiles(reqDetails, { collectionName }) { + const files = await this.collectionYmlService.listContents(reqDetails, { + collectionName, + }) + + return this.convertYmlToObjOrder(files) + } + + async createDirectory(reqDetails, { collectionName, objArray }) { + if (ISOMER_TEMPLATE_PROTECTED_DIRS.includes(collectionName)) + throw new ConflictError(protectedFolderConflictErrorMsg(collectionName)) + if (/[^a-zA-Z0-9- ]/g.test(collectionName)) { + // Contains non-allowed characters + throw new BadRequestError( + "Special characters not allowed in collection name" + ) + } + const slugifiedCollectionName = slugifyCollectionName(collectionName) + await this.collectionYmlService.create(reqDetails, { + collectionName: slugifiedCollectionName, + }) + if (objArray) { + const orderArray = this.convertObjToYmlOrder(objArray) + // We can't perform these operations concurrently because of conflict issues + /* eslint-disable no-await-in-loop, no-restricted-syntax */ + for (const fileName of orderArray) { + await this.moverService.movePage(reqDetails, { + fileName, + newFileCollection: slugifiedCollectionName, + }) + } + } + return { + newDirectoryName: slugifiedCollectionName, + items: objArray || [], + } + } + + async renameDirectory(reqDetails, { collectionName, newDirectoryName }) { + if (/[^a-zA-Z0-9- ]/g.test(newDirectoryName)) { + // Contains non-allowed characters + throw new BadRequestError( + "Special characters not allowed in collection name" + ) + } + if (ISOMER_TEMPLATE_PROTECTED_DIRS.includes(newDirectoryName)) + throw new ConflictError(protectedFolderConflictErrorMsg(newDirectoryName)) + const slugifiedNewCollectionName = slugifyCollectionName(newDirectoryName) + await this.baseDirectoryService.rename(reqDetails, { + oldDirectoryName: `_${collectionName}`, + newDirectoryName: `_${slugifiedNewCollectionName}`, + message: `Renaming collection ${collectionName} to ${slugifiedNewCollectionName}`, + }) + await this.collectionYmlService.renameCollectionInOrder(reqDetails, { + oldCollectionName: collectionName, + newCollectionName: slugifiedNewCollectionName, + }) + await this.navYmlService.renameCollectionInNav(reqDetails, { + oldCollectionName: collectionName, + newCollectionName: slugifiedNewCollectionName, + }) + } + + async deleteDirectory(reqDetails, { collectionName }) { + if (ISOMER_TEMPLATE_PROTECTED_DIRS.includes(collectionName)) + throw new ConflictError(protectedFolderConflictErrorMsg(collectionName)) + await this.baseDirectoryService.delete(reqDetails, { + directoryName: `_${collectionName}`, + message: `Deleting collection ${collectionName}`, + }) + await this.navYmlService.deleteCollectionInNav(reqDetails, { + collectionName, + }) + } + + async reorderDirectory(reqDetails, { collectionName, objArray }) { + const fileOrder = this.convertObjToYmlOrder(objArray) + await this.collectionYmlService.updateOrder(reqDetails, { + collectionName, + newOrder: fileOrder, + }) + return objArray + } + + async movePages( + reqDetails, + { collectionName, targetCollectionName, targetSubcollectionName, objArray } + ) { + // We can't perform these operations concurrently because of conflict issues + /* eslint-disable no-await-in-loop, no-restricted-syntax */ + for (const file of objArray) { + const fileName = file.name + await this.moverService.movePage(reqDetails, { + fileName, + oldFileCollection: collectionName, + newFileCollection: targetCollectionName, + newFileSubcollection: targetSubcollectionName, + }) + } + } +} + +module.exports = { CollectionDirectoryService } diff --git a/services/directoryServices/SubcollectionDirectoryService.js b/services/directoryServices/SubcollectionDirectoryService.js new file mode 100644 index 000000000..30a5a6002 --- /dev/null +++ b/services/directoryServices/SubcollectionDirectoryService.js @@ -0,0 +1,189 @@ +const { BadRequestError } = require("@errors/BadRequestError") + +const { deslugifyCollectionName } = require("@utils/utils") + +const { titleSpecialCharCheck } = require("@validators/validators") + +const PLACEHOLDER_FILE_NAME = ".keep" + +class SubcollectionDirectoryService { + constructor({ + baseDirectoryService, + collectionYmlService, + moverService, + subcollectionPageService, + gitHubService, + }) { + this.baseDirectoryService = baseDirectoryService + this.collectionYmlService = collectionYmlService + this.moverService = moverService + this.subcollectionPageService = subcollectionPageService + this.gitHubService = gitHubService + } + + async listFiles(reqDetails, { collectionName, subcollectionName }) { + const files = await this.collectionYmlService.listContents(reqDetails, { + collectionName, + }) + const subcollectionFiles = files.filter( + (fileName) => + fileName.startsWith(`${subcollectionName}/`) && + !fileName.includes(`/.keep`) + ) + + const subcollectionNameLength = `${subcollectionName}/`.length + return subcollectionFiles.map((fileName) => ({ + name: fileName.substring(subcollectionNameLength), + type: "file", + })) + } + + async createDirectory( + reqDetails, + { collectionName, subcollectionName, objArray } + ) { + if (titleSpecialCharCheck({ title: subcollectionName, isFile: false })) + throw new BadRequestError( + "Special characters not allowed in directory name" + ) + const parsedSubcollectionName = deslugifyCollectionName(subcollectionName) + const parsedDir = `_${collectionName}/${parsedSubcollectionName}` + await this.gitHubService.create(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: parsedDir, + }) + + await this.collectionYmlService.addItemToOrder(reqDetails, { + collectionName, + item: `${parsedSubcollectionName}/${PLACEHOLDER_FILE_NAME}`, + }) + + if (objArray) { + // We can't perform these operations concurrently because of conflict issues + /* eslint-disable no-await-in-loop, no-restricted-syntax */ + for (const file of objArray) { + const fileName = file.name + await this.moverService.movePage(reqDetails, { + fileName, + oldFileCollection: collectionName, + newFileCollection: collectionName, + newFileSubcollection: parsedSubcollectionName, + }) + } + } + return { + newDirectoryName: parsedSubcollectionName, + items: objArray || [], + } + } + + async renameDirectory( + reqDetails, + { collectionName, subcollectionName, newDirectoryName } + ) { + if (titleSpecialCharCheck({ title: newDirectoryName, isFile: false })) + throw new BadRequestError( + "Special characters not allowed in directory name" + ) + const parsedNewName = deslugifyCollectionName(newDirectoryName) + const dir = `_${collectionName}/${subcollectionName}` + const files = await this.baseDirectoryService.list(reqDetails, { + directoryName: dir, + }) + // We can't perform these operations concurrently because of conflict issues + /* eslint-disable no-await-in-loop, no-restricted-syntax */ + for (const file of files) { + if (file.type !== "file") continue + const fileName = file.name + if (fileName === PLACEHOLDER_FILE_NAME) { + await this.gitHubService.delete(reqDetails, { + sha: file.sha, + fileName, + directoryName: dir, + }) + continue + } + await this.subcollectionPageService.updateSubcollection(reqDetails, { + fileName, + collectionName, + oldSubcollectionName: subcollectionName, + newSubcollectionName: parsedNewName, + }) + } + await this.gitHubService.create(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: `_${collectionName}/${parsedNewName}`, + }) + await this.collectionYmlService.renameSubfolderInOrder(reqDetails, { + collectionName, + oldSubfolder: subcollectionName, + newSubfolder: parsedNewName, + }) + } + + async deleteDirectory(reqDetails, { collectionName, subcollectionName }) { + const dir = `_${collectionName}/${subcollectionName}` + await this.baseDirectoryService.delete(reqDetails, { + directoryName: dir, + message: `Deleting subcollection ${collectionName}/${subcollectionName}`, + }) + await this.collectionYmlService.deleteSubfolderFromOrder(reqDetails, { + collectionName, + subfolder: subcollectionName, + }) + } + + async reorderDirectory( + reqDetails, + { collectionName, subcollectionName, objArray } + ) { + const newSubcollectionOrder = [`${subcollectionName}/.keep`].concat( + objArray.map((obj) => `${subcollectionName}/${obj.name}`) + ) + const files = await this.collectionYmlService.listContents(reqDetails, { + collectionName, + }) + const insertPos = files.findIndex((fileName) => + fileName.includes(`${subcollectionName}/`) + ) + // We do this step separately to account for subcollections which may not have the .keep file + const filteredFiles = files.filter( + (fileName) => !fileName.includes(`${subcollectionName}/`) + ) + filteredFiles.splice(insertPos, 0, ...newSubcollectionOrder) + + await this.collectionYmlService.updateOrder(reqDetails, { + collectionName, + newOrder: filteredFiles, + }) + return objArray + } + + async movePages( + reqDetails, + { + collectionName, + subcollectionName, + targetCollectionName, + targetSubcollectionName, + objArray, + } + ) { + // We can't perform these operations concurrently because of conflict issues + /* eslint-disable no-await-in-loop, no-restricted-syntax */ + for (const file of objArray) { + const fileName = file.name + await this.moverService.movePage(reqDetails, { + fileName, + oldFileCollection: collectionName, + oldFileSubcollection: subcollectionName, + newFileCollection: targetCollectionName, + newFileSubcollection: targetSubcollectionName, + }) + } + } +} + +module.exports = { SubcollectionDirectoryService } diff --git a/services/directoryServices/UnlinkedPagesDirectoryService.js b/services/directoryServices/UnlinkedPagesDirectoryService.js new file mode 100644 index 000000000..19f849fff --- /dev/null +++ b/services/directoryServices/UnlinkedPagesDirectoryService.js @@ -0,0 +1,40 @@ +const UNLINKED_PAGE_DIRECTORY_NAME = "pages" + +class UnlinkedPagesDirectoryService { + constructor({ baseDirectoryService, moverService }) { + this.baseDirectoryService = baseDirectoryService + this.moverService = moverService + } + + async listAllUnlinkedPages(reqDetails) { + const filesOrDirs = await this.baseDirectoryService.list(reqDetails, { + directoryName: UNLINKED_PAGE_DIRECTORY_NAME, + }) + return filesOrDirs.reduce((acc, curr) => { + if (curr.type === "file") + acc.push({ + name: curr.name, + type: "file", + }) + return acc + }, []) + } + + async movePages( + reqDetails, + { targetCollectionName, targetSubcollectionName, objArray } + ) { + // We can't perform these operations concurrently because of conflict issues + /* eslint-disable no-await-in-loop, no-restricted-syntax */ + for (const file of objArray) { + const fileName = file.name + await this.moverService.movePage(reqDetails, { + fileName, + newFileCollection: targetCollectionName, + newFileSubcollection: targetSubcollectionName, + }) + } + } +} + +module.exports = { UnlinkedPagesDirectoryService } diff --git a/services/directoryServices/__tests__/BaseDirectoryService.spec.js b/services/directoryServices/__tests__/BaseDirectoryService.spec.js new file mode 100644 index 000000000..4aa2d6b4c --- /dev/null +++ b/services/directoryServices/__tests__/BaseDirectoryService.spec.js @@ -0,0 +1,182 @@ +describe("Base Directory Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + const collectionName = "collection" + const directoryName = `_${collectionName}` + const subcollectionName = `subcollection` + const sha = "12345" + const message = "message" + const currentCommitSha = "98765" + const treeSha = "00000" + + const mockedTree = [ + { + type: "tree", + path: "_normal", + }, + { + type: "tree", + path: `${directoryName}`, + }, + { + type: "tree", + path: `${directoryName}/${subcollectionName}`, + }, + { + type: "tree", + path: `_to-keep/${directoryName}/${subcollectionName}`, + }, + { + type: "file", + path: "_normal/file.md", + }, + { + type: "file", + path: `${directoryName}/file.md`, + }, + { + type: "file", + path: `${directoryName}/${subcollectionName}/file.md`, + }, + { + type: "file", + path: `_to-keep/${directoryName}/${subcollectionName}/file.md`, + }, + ] + + const reqDetails = { siteName, accessToken, currentCommitSha, treeSha } + + const mockGithubService = { + readDirectory: jest.fn(), + getTree: jest.fn(), + updateTree: jest.fn(), + updateRepoState: jest.fn(), + } + + const { + BaseDirectoryService, + } = require("@services/directoryServices/BaseDirectoryService") + const service = new BaseDirectoryService({ + gitHubService: mockGithubService, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("List", () => { + const readDirResp = [ + { + name: "test-name", + path: "test-path", + sha, + size: 10, + type: "file", + }, + { + name: "test-name2", + path: "test-path2", + sha: "test-sha", + size: 10, + type: "file", + }, + ] + const githubServiceResp = readDirResp.map((item) => ({ + ...item, + extra: "extra", + })) + mockGithubService.readDirectory.mockResolvedValueOnce(githubServiceResp) + it("Listing directory contents filters and returns only relevant data", async () => { + await expect( + service.list(reqDetails, { + directoryName, + }) + ).resolves.toMatchObject(readDirResp) + expect(mockGithubService.readDirectory).toHaveBeenCalledWith(reqDetails, { + directoryName, + }) + }) + }) + + describe("Rename", () => { + const renamedDir = "_renamed-dir" + const mockedRenamedTree = [ + { + type: "tree", + path: `${renamedDir}`, + }, + { + type: "file", + path: `${directoryName}/file.md`, + sha: null, + }, + { + type: "file", + path: `${directoryName}/${subcollectionName}/file.md`, + sha: null, + }, + ] + mockGithubService.getTree.mockResolvedValueOnce(mockedTree) + mockGithubService.updateTree.mockResolvedValueOnce(sha) + it("Renaming directories works correctly", async () => { + await expect( + service.rename(reqDetails, { + oldDirectoryName: directoryName, + newDirectoryName: renamedDir, + message, + }) + ).resolves.not.toThrow() + expect(mockGithubService.getTree).toHaveBeenCalledWith(reqDetails, { + isRecursive: true, + }) + expect(mockGithubService.updateTree).toHaveBeenCalledWith(reqDetails, { + gitTree: mockedRenamedTree, + message, + }) + expect(mockGithubService.updateRepoState).toHaveBeenCalledWith( + reqDetails, + { + commitSha: sha, + } + ) + }) + }) + + describe("Delete", () => { + const mockedDeletedTree = [ + { + type: "file", + path: `${directoryName}/file.md`, + sha: null, + }, + { + type: "file", + path: `${directoryName}/${subcollectionName}/file.md`, + sha: null, + }, + ] + mockGithubService.getTree.mockResolvedValueOnce(mockedTree) + mockGithubService.updateTree.mockResolvedValueOnce(sha) + it("Deleting directories works correctly", async () => { + await expect( + service.delete(reqDetails, { + directoryName, + message, + }) + ).resolves.not.toThrow() + expect(mockGithubService.getTree).toHaveBeenCalledWith(reqDetails, { + isRecursive: true, + }) + expect(mockGithubService.updateTree).toHaveBeenCalledWith(reqDetails, { + gitTree: mockedDeletedTree, + message, + }) + expect(mockGithubService.updateRepoState).toHaveBeenCalledWith( + reqDetails, + { + commitSha: sha, + } + ) + }) + }) +}) diff --git a/services/directoryServices/__tests__/CollectionDirectoryService.spec.js b/services/directoryServices/__tests__/CollectionDirectoryService.spec.js new file mode 100644 index 000000000..fe23859a4 --- /dev/null +++ b/services/directoryServices/__tests__/CollectionDirectoryService.spec.js @@ -0,0 +1,425 @@ +const { BadRequestError } = require("@errors/BadRequestError") +const { ConflictError } = require("@errors/ConflictError") + +describe("Collection Directory Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + const collectionName = "collection" + + const objArray = [ + { + type: "file", + name: "file.md", + }, + { + type: "file", + name: `file2.md`, + }, + ] + + const reqDetails = { siteName, accessToken } + + const mockBaseDirectoryService = { + list: jest.fn(), + rename: jest.fn(), + delete: jest.fn(), + } + + const mockNavYmlService = { + renameCollectionInNav: jest.fn(), + deleteCollectionInNav: jest.fn(), + } + + const mockCollectionYmlService = { + listContents: jest.fn(), + create: jest.fn(), + renameCollectionInOrder: jest.fn(), + updateOrder: jest.fn(), + } + + const mockMoverService = { + movePage: jest.fn(), + } + + const { + CollectionDirectoryService, + } = require("@services/directoryServices/CollectionDirectoryService") + const service = new CollectionDirectoryService({ + baseDirectoryService: mockBaseDirectoryService, + navYmlService: mockNavYmlService, + collectionYmlService: mockCollectionYmlService, + moverService: mockMoverService, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("ListAllCollections", () => { + const listResp = [ + { + name: "test-name", + path: "test-name", + sha: "test-sha0", + size: 10, + type: "file", + }, + { + name: "test-name2", + path: "test-name2", + sha: "test-sha", + size: 10, + type: "file", + }, + { + name: "_data", + path: "_data", + sha: "test-sha2", + size: 10, + type: "dir", + }, + { + name: "_test-col", + path: "_test-col", + sha: "test-sha3", + size: 10, + type: "dir", + }, + { + name: "_test-col2", + path: "_test-col2", + sha: "test-sha4", + size: 10, + type: "dir", + }, + ] + const expectedResp = [ + { + name: "test-col", + type: "dir", + }, + { + name: "test-col2", + type: "dir", + }, + ] + mockBaseDirectoryService.list.mockResolvedValueOnce(listResp) + it("Listing collections only returns collections and not protected folders", async () => { + await expect( + service.listAllCollections(reqDetails) + ).resolves.toMatchObject(expectedResp) + expect(mockBaseDirectoryService.list).toHaveBeenCalledWith(reqDetails, { + directoryName: "", + }) + }) + }) + + describe("ListFiles", () => { + const listResp = [ + "testfile", + "testfile1", + "testsub/.keep", + "testsub/file1", + "testsub/file2", + "testfile2", + ] + const expectedResp = [ + { + name: "testfile", + type: "file", + }, + { + name: "testfile1", + type: "file", + }, + { + name: "testsub", + type: "dir", + children: ["file1", "file2"], + }, + { + name: "testfile2", + type: "file", + }, + ] + mockCollectionYmlService.listContents.mockResolvedValueOnce(listResp) + it("ListFiles returns all files and collections properly formatted", async () => { + await expect( + service.listFiles(reqDetails, { collectionName }) + ).resolves.toMatchObject(expectedResp) + expect(mockCollectionYmlService.listContents).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + } + ) + }) + }) + + describe("CreateDirectory", () => { + it("rejects collections with the same name as protected folders", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName: "data", + }) + ).rejects.toThrowError(ConflictError) + }) + + it("rejects collections with special characters", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName: "dir/dir", + }) + ).rejects.toThrowError(BadRequestError) + }) + + it("Creating a directory with no specified files works correctly", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName, + }) + ).resolves.toMatchObject({ + newDirectoryName: collectionName, + items: [], + }) + expect(mockCollectionYmlService.create).toHaveBeenCalledWith(reqDetails, { + collectionName, + }) + }) + + it("Creating a collection directory slugifies the collection name", async () => { + const originalCollectionName = "Test Collection" + const slugifiedCollectionName = "test-collection" + await expect( + service.createDirectory(reqDetails, { + collectionName: originalCollectionName, + }) + ).resolves.toMatchObject({ + newDirectoryName: slugifiedCollectionName, + items: [], + }) + expect(mockCollectionYmlService.create).toHaveBeenCalledWith(reqDetails, { + collectionName: slugifiedCollectionName, + }) + }) + + it("Creating a directory with specified files works correctly", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName, + objArray, + }) + ).resolves.toMatchObject({ + newDirectoryName: collectionName, + items: objArray, + }) + expect(mockCollectionYmlService.create).toHaveBeenCalledWith(reqDetails, { + collectionName, + }) + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + newFileCollection: collectionName, + }) + }) + }) + + it("Creating a directory slugifies the name and adds the specified files correctly", async () => { + const originalCollectionName = "Test Collection" + const slugifiedCollectionName = "test-collection" + await expect( + service.createDirectory(reqDetails, { + collectionName: originalCollectionName, + objArray, + }) + ).resolves.toMatchObject({ + newDirectoryName: slugifiedCollectionName, + items: objArray, + }) + expect(mockCollectionYmlService.create).toHaveBeenCalledWith(reqDetails, { + collectionName: slugifiedCollectionName, + }) + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + newFileCollection: slugifiedCollectionName, + }) + }) + }) + }) + + describe("RenameDirectory", () => { + const newDirectoryName = "new-dir" + it("rejects renaming to a collection with the same name as protected folders", async () => { + await expect( + service.renameDirectory(reqDetails, { + collectionName, + newDirectoryName: "files", + }) + ).rejects.toThrowError(ConflictError) + }) + + it("rejects collections with special characters", async () => { + await expect( + service.renameDirectory(reqDetails, { + collectionName, + newDirectoryName: "dir/dir", + }) + ).rejects.toThrowError(BadRequestError) + }) + + it("Renaming a collection works correctly", async () => { + await expect( + service.renameDirectory(reqDetails, { + collectionName, + newDirectoryName, + }) + ).resolves.not.toThrowError() + expect(mockBaseDirectoryService.rename).toHaveBeenCalledWith(reqDetails, { + oldDirectoryName: `_${collectionName}`, + newDirectoryName: `_${newDirectoryName}`, + message: `Renaming collection ${collectionName} to ${newDirectoryName}`, + }) + expect( + mockCollectionYmlService.renameCollectionInOrder + ).toHaveBeenCalledWith(reqDetails, { + oldCollectionName: collectionName, + newCollectionName: newDirectoryName, + }) + expect(mockNavYmlService.renameCollectionInNav).toHaveBeenCalledWith( + reqDetails, + { + oldCollectionName: collectionName, + newCollectionName: newDirectoryName, + } + ) + }) + it("Renaming a collection slugifies the new name correctly", async () => { + const originalCollectionName = "Test Collection" + const slugifiedCollectionName = "test-collection" + await expect( + service.renameDirectory(reqDetails, { + collectionName: originalCollectionName, + newDirectoryName: slugifiedCollectionName, + }) + ).resolves.not.toThrowError() + expect(mockBaseDirectoryService.rename).toHaveBeenCalledWith(reqDetails, { + oldDirectoryName: `_${originalCollectionName}`, + newDirectoryName: `_${slugifiedCollectionName}`, + message: `Renaming collection ${originalCollectionName} to ${slugifiedCollectionName}`, + }) + expect( + mockCollectionYmlService.renameCollectionInOrder + ).toHaveBeenCalledWith(reqDetails, { + oldCollectionName: originalCollectionName, + newCollectionName: slugifiedCollectionName, + }) + expect(mockNavYmlService.renameCollectionInNav).toHaveBeenCalledWith( + reqDetails, + { + oldCollectionName: originalCollectionName, + newCollectionName: slugifiedCollectionName, + } + ) + }) + }) + + describe("DeleteDirectory", () => { + it("rejects deleting a collection with the same name as protected folders", async () => { + await expect( + service.deleteDirectory(reqDetails, { + collectionName: "data", + }) + ).rejects.toThrowError(ConflictError) + }) + + it("Deleting a directory works correctly", async () => { + await expect( + service.deleteDirectory(reqDetails, { + collectionName, + }) + ).resolves.not.toThrowError() + expect(mockBaseDirectoryService.delete).toHaveBeenCalledWith(reqDetails, { + directoryName: `_${collectionName}`, + message: `Deleting collection ${collectionName}`, + }) + expect(mockNavYmlService.deleteCollectionInNav).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + } + ) + }) + }) + + describe("ReorderDirectory", () => { + it("Reordering a directory works correctly", async () => { + await expect( + service.reorderDirectory(reqDetails, { + collectionName, + objArray, + }) + ).resolves.toMatchObject(objArray) + expect(mockCollectionYmlService.updateOrder).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + newOrder: objArray.map((file) => file.name), + } + ) + }) + }) + + describe("MovePages", () => { + const targetCollectionName = "target-collection" + const targetSubcollectionName = "target-subcollection" + it("Moving pages in a collection to unlinked pages works correctly", async () => { + await expect( + service.movePages(reqDetails, { + collectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + }) + }) + }) + it("Moving pages in a collection to another collection works correctly", async () => { + await expect( + service.movePages(reqDetails, { + collectionName, + targetCollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + newFileCollection: targetCollectionName, + }) + }) + }) + it("Moving pages in a collection to a subcollection works correctly", async () => { + await expect( + service.movePages(reqDetails, { + collectionName, + targetCollectionName, + targetSubcollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + newFileCollection: targetCollectionName, + newFileSubcollection: targetSubcollectionName, + }) + }) + }) + }) +}) diff --git a/services/directoryServices/__tests__/SubcollectionDirectoryService.spec.js b/services/directoryServices/__tests__/SubcollectionDirectoryService.spec.js new file mode 100644 index 000000000..3848381d6 --- /dev/null +++ b/services/directoryServices/__tests__/SubcollectionDirectoryService.spec.js @@ -0,0 +1,454 @@ +const { BadRequestError } = require("@errors/BadRequestError") + +const PLACEHOLDER_FILE_NAME = ".keep" + +describe("Subcollection Directory Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + const collectionName = "collection" + const subcollectionName = "Subcollection name" + + const objArray = [ + { + type: "file", + name: "file.md", + }, + { + type: "file", + name: `file2.md`, + }, + ] + + const reqDetails = { siteName, accessToken } + + const mockBaseDirectoryService = { + list: jest.fn(), + delete: jest.fn(), + } + + const mockCollectionYmlService = { + listContents: jest.fn(), + addItemToOrder: jest.fn(), + renameSubfolderInOrder: jest.fn(), + deleteSubfolderFromOrder: jest.fn(), + updateOrder: jest.fn(), + } + + const mockMoverService = { + movePage: jest.fn(), + } + + const mockSubcollectionPageService = { + updateSubcollection: jest.fn(), + } + + const mockGitHubService = { + create: jest.fn(), + delete: jest.fn(), + } + + const { + SubcollectionDirectoryService, + } = require("@services/directoryServices/SubcollectionDirectoryService") + const service = new SubcollectionDirectoryService({ + baseDirectoryService: mockBaseDirectoryService, + collectionYmlService: mockCollectionYmlService, + moverService: mockMoverService, + subcollectionPageService: mockSubcollectionPageService, + gitHubService: mockGitHubService, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("ListFiles", () => { + const listResp = [ + "testfile", + "testfile1", + `${subcollectionName}/.keep`, + `${subcollectionName}/file1`, + `${subcollectionName}/file2`, + "testfile2", + ] + const expectedResp = [ + { + name: "file1", + type: "file", + }, + { + name: "file2", + type: "file", + }, + ] + mockCollectionYmlService.listContents.mockResolvedValueOnce(listResp) + it("ListFiles returns all files properly formatted", async () => { + await expect( + service.listFiles(reqDetails, { collectionName, subcollectionName }) + ).resolves.toMatchObject(expectedResp) + expect(mockCollectionYmlService.listContents).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + } + ) + }) + }) + + describe("CreateDirectory", () => { + const parsedDir = `_${collectionName}/${subcollectionName}` + it("rejects subcollection names with special characters", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName, + subcollectionName: "dir/dir", + }) + ).rejects.toThrowError(BadRequestError) + }) + it("Creating a directory with no specified files works correctly", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName, + subcollectionName, + }) + ).resolves.toMatchObject({ + newDirectoryName: subcollectionName, + items: [], + }) + expect(mockGitHubService.create).toHaveBeenCalledWith(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: parsedDir, + }) + expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + item: `${subcollectionName}/${PLACEHOLDER_FILE_NAME}`, + } + ) + }) + + it("Creating a directory with specified files works correctly", async () => { + await expect( + service.createDirectory(reqDetails, { + collectionName, + subcollectionName, + objArray, + }) + ).resolves.toMatchObject({ + newDirectoryName: subcollectionName, + items: objArray, + }) + expect(mockGitHubService.create).toHaveBeenCalledWith(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: parsedDir, + }) + expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + item: `${subcollectionName}/${PLACEHOLDER_FILE_NAME}`, + } + ) + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + newFileCollection: collectionName, + newFileSubcollection: subcollectionName, + }) + }) + }) + + it("Creating a directory deslugifies the title", async () => { + const originalTitle = `hEllo there` + const expectedTitle = `HEllo there` + await expect( + service.createDirectory(reqDetails, { + collectionName, + subcollectionName: originalTitle, + objArray, + }) + ).resolves.toMatchObject({ + newDirectoryName: expectedTitle, + items: objArray, + }) + expect(mockGitHubService.create).toHaveBeenCalledWith(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: `_${collectionName}/${expectedTitle}`, + }) + expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + item: `${expectedTitle}/${PLACEHOLDER_FILE_NAME}`, + } + ) + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + newFileCollection: collectionName, + newFileSubcollection: expectedTitle, + }) + }) + }) + }) + + describe("RenameDirectory", () => { + const dir = `_${collectionName}/${subcollectionName}` + const readDirResp = [ + { + name: "test-name", + path: "test-name", + sha: "12345", + size: 10, + type: "file", + }, + { + name: "test-name2", + path: "test-name2", + sha: "12345", + size: 10, + type: "file", + }, + { + name: PLACEHOLDER_FILE_NAME, + path: PLACEHOLDER_FILE_NAME, + sha: "test-sha", + size: 10, + type: "file", + }, + ] + + it("rejects subcollection names with special characters", async () => { + await expect( + service.renameDirectory(reqDetails, { + collectionName, + subcollectionName, + newDirectoryName: "dir/dir", + }) + ).rejects.toThrowError(BadRequestError) + }) + + it("Renaming a subcollection works correctly", async () => { + const newDirectoryName = "New Dir" + mockBaseDirectoryService.list.mockResolvedValueOnce(readDirResp) + await expect( + service.renameDirectory(reqDetails, { + collectionName, + subcollectionName, + newDirectoryName, + }) + ).resolves.not.toThrowError() + expect(mockBaseDirectoryService.list).toHaveBeenCalledWith(reqDetails, { + directoryName: dir, + }) + readDirResp.forEach((file) => { + if (file.name === PLACEHOLDER_FILE_NAME) { + expect(mockGitHubService.delete).toHaveBeenCalledWith(reqDetails, { + sha: file.sha, + fileName: file.name, + directoryName: dir, + }) + } else { + expect( + mockSubcollectionPageService.updateSubcollection + ).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + collectionName, + oldSubcollectionName: subcollectionName, + newSubcollectionName: newDirectoryName, + }) + } + }) + expect(mockGitHubService.create).toHaveBeenCalledWith(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: `_${collectionName}/${newDirectoryName}`, + }) + expect( + mockCollectionYmlService.renameSubfolderInOrder(reqDetails, { + collectionName, + oldSubfolder: subcollectionName, + newSubfolder: newDirectoryName, + }) + ) + }) + + it("Renaming a subcollection slugifies the title correctly", async () => { + const originalTitle = `hEllo there` + const expectedTitle = `HEllo there` + mockBaseDirectoryService.list.mockResolvedValueOnce(readDirResp) + await expect( + service.renameDirectory(reqDetails, { + collectionName, + subcollectionName, + newDirectoryName: originalTitle, + }) + ).resolves.not.toThrowError() + expect(mockBaseDirectoryService.list).toHaveBeenCalledWith(reqDetails, { + directoryName: dir, + }) + readDirResp.forEach((file) => { + if (file.name === PLACEHOLDER_FILE_NAME) { + expect(mockGitHubService.delete).toHaveBeenCalledWith(reqDetails, { + sha: file.sha, + fileName: file.name, + directoryName: dir, + }) + } else { + expect( + mockSubcollectionPageService.updateSubcollection + ).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + collectionName, + oldSubcollectionName: subcollectionName, + newSubcollectionName: expectedTitle, + }) + } + }) + expect(mockGitHubService.create).toHaveBeenCalledWith(reqDetails, { + content: "", + fileName: PLACEHOLDER_FILE_NAME, + directoryName: `_${collectionName}/${expectedTitle}`, + }) + expect( + mockCollectionYmlService.renameSubfolderInOrder(reqDetails, { + collectionName, + oldSubfolder: subcollectionName, + newSubfolder: expectedTitle, + }) + ) + }) + }) + + describe("DeleteDirectory", () => { + it("Deleting a directory works correctly", async () => { + await expect( + service.deleteDirectory(reqDetails, { + collectionName, + subcollectionName, + }) + ).resolves.not.toThrowError() + expect(mockBaseDirectoryService.delete).toHaveBeenCalledWith(reqDetails, { + directoryName: `_${collectionName}/${subcollectionName}`, + message: `Deleting subcollection ${collectionName}/${subcollectionName}`, + }) + expect( + mockCollectionYmlService.deleteSubfolderFromOrder + ).toHaveBeenCalledWith(reqDetails, { + collectionName, + subfolder: subcollectionName, + }) + }) + }) + + describe("ReorderDirectory", () => { + const listResp = [ + "testfile", + "testfile1", + `${subcollectionName}/.keep`, + `${subcollectionName}/file1`, + `${subcollectionName}/file2`, + "testfile2", + ] + const expectedNewOrder = [ + "testfile", + "testfile1", + `${subcollectionName}/.keep`, + `${subcollectionName}/file2`, + `${subcollectionName}/file1`, + "testfile2", + ] + const newObjArray = [ + { + name: "file2", + type: "file", + }, + { + name: "file1", + type: "file", + }, + ] + mockCollectionYmlService.listContents.mockResolvedValueOnce(listResp) + it("Reordering a directory works correctly", async () => { + await expect( + service.reorderDirectory(reqDetails, { + collectionName, + subcollectionName, + objArray: newObjArray, + }) + ).resolves.toMatchObject(newObjArray) + expect(mockCollectionYmlService.updateOrder).toHaveBeenCalledWith( + reqDetails, + { + collectionName, + newOrder: expectedNewOrder, + } + ) + }) + }) + + describe("MovePages", () => { + const targetCollectionName = "target-collection" + const targetSubcollectionName = "target-subcollection" + it("Moving pages in a subcollection to unlinked pages works correctly", async () => { + await expect( + service.movePages(reqDetails, { + collectionName, + subcollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + oldFileSubcollection: subcollectionName, + }) + }) + }) + it("Moving pages in a subcollection to a collection works correctly", async () => { + await expect( + service.movePages(reqDetails, { + collectionName, + subcollectionName, + targetCollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + oldFileSubcollection: subcollectionName, + newFileCollection: targetCollectionName, + }) + }) + }) + it("Moving pages in a subcollection to another subcollection works correctly", async () => { + await expect( + service.movePages(reqDetails, { + collectionName, + subcollectionName, + targetCollectionName, + targetSubcollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + oldFileCollection: collectionName, + oldFileSubcollection: subcollectionName, + newFileCollection: targetCollectionName, + newFileSubcollection: targetSubcollectionName, + }) + }) + }) + }) +}) diff --git a/services/directoryServices/__tests__/UnlinkedPagesDirectoryService.spec.js b/services/directoryServices/__tests__/UnlinkedPagesDirectoryService.spec.js new file mode 100644 index 000000000..254019f27 --- /dev/null +++ b/services/directoryServices/__tests__/UnlinkedPagesDirectoryService.spec.js @@ -0,0 +1,112 @@ +const UNLINKED_PAGE_DIRECTORY_NAME = "pages" + +describe("Unlinked Pages Directory Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + + const objArray = [ + { + type: "file", + name: "file.md", + }, + { + type: "file", + name: `file2.md`, + }, + ] + + const reqDetails = { siteName, accessToken } + + const mockBaseDirectoryService = { + list: jest.fn(), + } + + const mockMoverService = { + movePage: jest.fn(), + } + + const { + UnlinkedPagesDirectoryService, + } = require("@services/directoryServices/UnlinkedPagesDirectoryService") + const service = new UnlinkedPagesDirectoryService({ + baseDirectoryService: mockBaseDirectoryService, + moverService: mockMoverService, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("ListAllUnlinkedPages", () => { + const listResp = [ + { + name: "test-name", + path: "pages/test-name", + sha: "test-sha0", + size: 10, + type: "file", + }, + { + name: "test-name2", + path: "pages/test-name2", + sha: "test-sha", + size: 10, + type: "file", + }, + ] + const expectedResp = [ + { + name: "test-name", + type: "file", + }, + { + name: "test-name2", + type: "file", + }, + ] + mockBaseDirectoryService.list.mockResolvedValueOnce(listResp) + it("Listing all unlinked pages works correctly", async () => { + await expect( + service.listAllUnlinkedPages(reqDetails) + ).resolves.toMatchObject(expectedResp) + expect(mockBaseDirectoryService.list).toHaveBeenCalledWith(reqDetails, { + directoryName: UNLINKED_PAGE_DIRECTORY_NAME, + }) + }) + }) + + describe("MovePages", () => { + const targetCollectionName = "target-collection" + const targetSubcollectionName = "target-subcollection" + it("Moving unlinked pages to a collection works correctly", async () => { + await expect( + service.movePages(reqDetails, { + targetCollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + newFileCollection: targetCollectionName, + }) + }) + }) + it("Moving unlinked pages to a subcollection works correctly", async () => { + await expect( + service.movePages(reqDetails, { + targetCollectionName, + targetSubcollectionName, + objArray, + }) + ).resolves.not.toThrowError() + objArray.forEach((file) => { + expect(mockMoverService.movePage).toHaveBeenCalledWith(reqDetails, { + fileName: file.name, + newFileCollection: targetCollectionName, + newFileSubcollection: targetSubcollectionName, + }) + }) + }) + }) +}) diff --git a/services/fileServices/MdPageServices/CollectionPageService.js b/services/fileServices/MdPageServices/CollectionPageService.js index 1b2598af4..ba1cbb27b 100644 --- a/services/fileServices/MdPageServices/CollectionPageService.js +++ b/services/fileServices/MdPageServices/CollectionPageService.js @@ -1,8 +1,12 @@ +const { BadRequestError } = require("@errors/BadRequestError") + const { retrieveDataFromMarkdown, convertDataToMarkdown, } = require("@utils/markdown-utils") +const { titleSpecialCharCheck } = require("@validators/validators") + class CollectionPageService { constructor({ gitHubService, collectionYmlService }) { this.gitHubService = gitHubService @@ -10,6 +14,8 @@ class CollectionPageService { } async create(reqDetails, { fileName, collectionName, content, frontMatter }) { + if (titleSpecialCharCheck({ title: fileName, isFile: true })) + throw new BadRequestError("Special characters not allowed in file name") const parsedCollectionName = `_${collectionName}` await this.collectionYmlService.addItemToOrder(reqDetails, { @@ -81,6 +87,8 @@ class CollectionPageService { reqDetails, { oldFileName, newFileName, collectionName, content, frontMatter, sha } ) { + if (titleSpecialCharCheck({ title: newFileName, isFile: true })) + throw new BadRequestError("Special characters not allowed in file name") const parsedCollectionName = `_${collectionName}` await this.collectionYmlService.updateItemInOrder(reqDetails, { diff --git a/services/fileServices/MdPageServices/SubcollectionPageService.js b/services/fileServices/MdPageServices/SubcollectionPageService.js index e2ca8c02f..9db4d6972 100644 --- a/services/fileServices/MdPageServices/SubcollectionPageService.js +++ b/services/fileServices/MdPageServices/SubcollectionPageService.js @@ -1,5 +1,9 @@ +const { BadRequestError } = require("@errors/BadRequestError") + const { deslugifyCollectionName } = require("@utils/utils") +const { titleSpecialCharCheck } = require("@validators/validators") + const { retrieveDataFromMarkdown, convertDataToMarkdown, @@ -15,6 +19,8 @@ class SubcollectionPageService { reqDetails, { fileName, collectionName, subcollectionName, content, frontMatter } ) { + if (titleSpecialCharCheck({ title: fileName, isFile: true })) + throw new BadRequestError("Special characters not allowed in file name") const parsedDirectoryName = `_${collectionName}/${subcollectionName}` await this.collectionYmlService.addItemToOrder(reqDetails, { @@ -96,6 +102,8 @@ class SubcollectionPageService { sha, } ) { + if (titleSpecialCharCheck({ title: newFileName, isFile: true })) + throw new BadRequestError("Special characters not allowed in file name") const parsedDirectoryName = `_${collectionName}/${subcollectionName}` await this.collectionYmlService.updateItemInOrder(reqDetails, { @@ -110,7 +118,6 @@ class SubcollectionPageService { directoryName: parsedDirectoryName, }) - frontMatter.third_nav_title = deslugifyCollectionName(subcollectionName) const newContent = convertDataToMarkdown(frontMatter, content) const { sha: newSha } = await this.gitHubService.create(reqDetails, { @@ -126,6 +133,36 @@ class SubcollectionPageService { newSha, } } + + // Used for updating the third_nav_title only without touching the collection.yml + async updateSubcollection( + reqDetails, + { fileName, collectionName, oldSubcollectionName, newSubcollectionName } + ) { + const { + sha, + content: { frontMatter, pageBody }, + } = await this.read(reqDetails, { + fileName, + collectionName, + subcollectionName: oldSubcollectionName, + }) + + const parsedOldDirectoryName = `_${collectionName}/${oldSubcollectionName}` + const parsedNewDirectoryName = `_${collectionName}/${newSubcollectionName}` + frontMatter.third_nav_title = deslugifyCollectionName(newSubcollectionName) + const newContent = convertDataToMarkdown(frontMatter, pageBody) + await this.gitHubService.delete(reqDetails, { + sha, + fileName, + directoryName: parsedOldDirectoryName, + }) + return this.gitHubService.create(reqDetails, { + content: newContent, + fileName, + directoryName: parsedNewDirectoryName, + }) + } } module.exports = { SubcollectionPageService } diff --git a/services/fileServices/MdPageServices/UnlinkedPageService.js b/services/fileServices/MdPageServices/UnlinkedPageService.js index ac71bb9a2..c9bbfcd98 100644 --- a/services/fileServices/MdPageServices/UnlinkedPageService.js +++ b/services/fileServices/MdPageServices/UnlinkedPageService.js @@ -1,8 +1,12 @@ +const { BadRequestError } = require("@errors/BadRequestError") + const { retrieveDataFromMarkdown, convertDataToMarkdown, } = require("@utils/markdown-utils") +const { titleSpecialCharCheck } = require("@validators/validators") + const UNLINKED_PAGES_DIRECTORY_NAME = "pages" class UnlinkedPageService { @@ -12,6 +16,8 @@ class UnlinkedPageService { async create(reqDetails, { fileName, content, frontMatter }) { // Ensure that third_nav_title is removed for files that are being moved from collections + if (titleSpecialCharCheck({ title: fileName, isFile: true })) + throw new BadRequestError("Special characters not allowed in file name") delete frontMatter.third_nav_title const newContent = convertDataToMarkdown(frontMatter, content) const { sha } = await this.gitHubService.create(reqDetails, { @@ -62,6 +68,8 @@ class UnlinkedPageService { reqDetails, { oldFileName, newFileName, content, frontMatter, sha } ) { + if (titleSpecialCharCheck({ title: newFileName, isFile: true })) + throw new BadRequestError("Special characters not allowed in file name") const newContent = convertDataToMarkdown(frontMatter, content) await this.gitHubService.delete(reqDetails, { sha, diff --git a/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js b/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js index cfef7b5fe..1c21a3e8b 100644 --- a/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js +++ b/services/fileServices/MdPageServices/__tests__/CollectionPageService.spec.js @@ -1,7 +1,9 @@ +const { BadRequestError } = require("@errors/BadRequestError") + describe("Collection Page Service", () => { const siteName = "test-site" const accessToken = "test-token" - const fileName = "test-file" + const fileName = "test file.md" const collectionName = "collection" const directoryName = `_${collectionName}` const mockContent = "test" @@ -53,13 +55,24 @@ describe("Collection Page Service", () => { describe("Create", () => { mockGithubService.create.mockResolvedValue({ sha }) + + it("rejects page names with special characters", async () => { + await expect( + service.create(reqDetails, { + fileName: "file/file.md", + collectionName, + content: mockContent, + frontMatter: { ...mockFrontMatter }, + }) + ).rejects.toThrowError(BadRequestError) + }) it("Creating pages works correctly", async () => { await expect( service.create(reqDetails, { fileName, collectionName, content: mockContent, - frontMatter: mockFrontMatter, + frontMatter: { ...mockFrontMatter }, }) ).resolves.toMatchObject({ fileName, @@ -67,7 +80,7 @@ describe("Collection Page Service", () => { sha, }) expect(convertDataToMarkdown).toHaveBeenCalledWith( - mockFrontMatter, + { ...mockFrontMatter }, mockContent ) expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( @@ -98,7 +111,7 @@ describe("Collection Page Service", () => { sha, }) expect(convertDataToMarkdown).toHaveBeenCalledWith( - mockFrontMatter, + { ...mockFrontMatter }, mockContent ) expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( @@ -189,8 +202,20 @@ describe("Collection Page Service", () => { describe("Rename", () => { const oldSha = "54321" - const oldFileName = "test-old-file" + const oldFileName = "test-old-file.md" mockGithubService.create.mockResolvedValue({ sha }) + + it("rejects renaming to page names with special characters", async () => { + await expect( + service.rename(reqDetails, { + oldFileName, + newFileName: "file/file.md", + collectionName, + content: mockContent, + frontMatter: { ...mockFrontMatter }, + }) + ).rejects.toThrowError(BadRequestError) + }) it("Renaming pages works correctly", async () => { await expect( service.rename(reqDetails, { diff --git a/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js b/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js index ec86aed55..ff5a946c4 100644 --- a/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js +++ b/services/fileServices/MdPageServices/__tests__/SubcollectionPageService.spec.js @@ -1,7 +1,11 @@ +const { BadRequestError } = require("@errors/BadRequestError") + +const { deslugifyCollectionName } = require("@utils/utils") + describe("Subcollection Page Service", () => { const siteName = "test-site" const accessToken = "test-token" - const fileName = "test-file" + const fileName = "test file.md" const collectionName = "collection" const subcollectionName = "subcollection" const directoryName = `_${collectionName}/${subcollectionName}` @@ -56,15 +60,26 @@ describe("Subcollection Page Service", () => { }) describe("Create", () => { - mockGithubService.create.mockResolvedValue({ sha }) - it("Creating pages works correctly", async () => { + it("rejects page names with special characters", async () => { + await expect( + service.create(reqDetails, { + fileName: "file/file.md", + collectionName, + subcollectionName, + content: mockContent, + frontMatter: { ...mockFrontMatter }, + }) + ).rejects.toThrowError(BadRequestError) + }) + it("Creating a page with no third nav title in the front matter correctly adds it in", async () => { + mockGithubService.create.mockResolvedValueOnce({ sha }) await expect( service.create(reqDetails, { fileName, collectionName, subcollectionName, content: mockContent, - frontMatter: mockFrontMatter, + frontMatter: { ...mockFrontMatter }, }) ).resolves.toMatchObject({ fileName, @@ -72,7 +87,10 @@ describe("Subcollection Page Service", () => { sha, }) expect(convertDataToMarkdown).toHaveBeenCalledWith( - mockFrontMatter, + { + ...mockFrontMatter, + third_nav_title: deslugifyCollectionName(subcollectionName), + }, mockContent ) expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( @@ -85,7 +103,8 @@ describe("Subcollection Page Service", () => { directoryName, }) }) - it("Creating a page which specifies a subcollection in the front matter removes the third_nav_title parameter", async () => { + it("Creating a page which specifies a different subcollection in the front matter works correctly", async () => { + mockGithubService.create.mockResolvedValueOnce({ sha }) const mockFrontMatterWithSubcollection = { ...mockFrontMatter, third_nav_title: "mock-third-nav", @@ -96,7 +115,7 @@ describe("Subcollection Page Service", () => { collectionName, subcollectionName, content: mockContent, - frontMatter: mockFrontMatterWithSubcollection, + frontMatter: { ...mockFrontMatterWithSubcollection }, }) ).resolves.toMatchObject({ fileName, @@ -104,7 +123,10 @@ describe("Subcollection Page Service", () => { sha, }) expect(convertDataToMarkdown).toHaveBeenCalledWith( - mockFrontMatter, + { + ...mockFrontMatterWithSubcollection, + third_nav_title: deslugifyCollectionName(subcollectionName), + }, mockContent ) expect(mockCollectionYmlService.addItemToOrder).toHaveBeenCalledWith( @@ -120,7 +142,7 @@ describe("Subcollection Page Service", () => { }) describe("Read", () => { - mockGithubService.read.mockResolvedValue({ + mockGithubService.read.mockResolvedValueOnce({ content: mockMarkdownContent, sha, }), @@ -148,7 +170,7 @@ describe("Subcollection Page Service", () => { describe("Update", () => { const oldSha = "54321" - mockGithubService.update.mockResolvedValue({ newSha: sha }) + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) it("Updating page content works correctly", async () => { await expect( service.update(reqDetails, { @@ -205,8 +227,21 @@ describe("Subcollection Page Service", () => { describe("Rename", () => { const oldSha = "54321" - const oldFileName = "test-old-file" - mockGithubService.create.mockResolvedValue({ sha }) + const oldFileName = "test-old-file.md" + mockGithubService.create.mockResolvedValueOnce({ sha }) + + it("rejects renaming to page names with special characters", async () => { + await expect( + service.rename(reqDetails, { + oldFileName, + newFileName: "file/file.md", + collectionName, + subcollectionName, + content: mockContent, + frontMatter: { ...mockFrontMatter }, + }) + ).rejects.toThrowError(BadRequestError) + }) it("Renaming pages works correctly", async () => { await expect( service.rename(reqDetails, { @@ -244,4 +279,48 @@ describe("Subcollection Page Service", () => { }) }) }) + + describe("Update Subcollection", () => { + const oldSha = "54321" + const newSubcollectionName = "new-subcollection" + const newDirectory = `_${collectionName}/${newSubcollectionName}` + mockGithubService.read.mockResolvedValueOnce({ + content: mockMarkdownContent, + sha: oldSha, + }) + mockGithubService.create.mockResolvedValueOnce({ sha }) + mockGithubService.delete.mockResolvedValueOnce({ + content: mockMarkdownContent, + sha: oldSha, + }), + it("Updating the subcollection of a page works correctly", async () => { + await expect( + service.updateSubcollection(reqDetails, { + fileName, + collectionName, + oldSubcollectionName: subcollectionName, + newSubcollectionName, + }) + ).resolves.toMatchObject({ + sha, + }) + expect(convertDataToMarkdown).toHaveBeenCalledWith( + { + ...mockFrontMatter, + third_nav_title: deslugifyCollectionName(newSubcollectionName), + }, + mockContent + ) + expect(mockGithubService.delete).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + sha: oldSha, + }) + expect(mockGithubService.create).toHaveBeenCalledWith(reqDetails, { + content: mockMarkdownContent, + fileName, + directoryName: newDirectory, + }) + }) + }) }) diff --git a/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js b/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js index 7094a2f9f..d4163a835 100644 --- a/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js +++ b/services/fileServices/MdPageServices/__tests__/UnlinkedPageService.spec.js @@ -1,7 +1,9 @@ +const { BadRequestError } = require("@errors/BadRequestError") + describe("Unlinked Page Service", () => { const siteName = "test-site" const accessToken = "test-token" - const fileName = "test-file" + const fileName = "test file.md" const directoryName = "pages" const mockContent = "test" const mockMarkdownContent = "---test---" @@ -44,6 +46,15 @@ describe("Unlinked Page Service", () => { describe("Create", () => { mockGithubService.create.mockResolvedValue({ sha }) + it("rejects page names with special characters", async () => { + await expect( + service.create(reqDetails, { + fileName: "file/file.md", + content: mockContent, + frontMatter: { ...mockFrontMatter }, + }) + ).rejects.toThrowError(BadRequestError) + }) it("Creating pages works correctly", async () => { await expect( service.create(reqDetails, { @@ -134,8 +145,18 @@ describe("Unlinked Page Service", () => { describe("Rename", () => { const oldSha = "54321" - const oldFileName = "test-old-file" + const oldFileName = "test-old-file.md" mockGithubService.create.mockResolvedValue({ sha }) + it("rejects renaming to page names with special characters", async () => { + await expect( + service.rename(reqDetails, { + oldFileName, + newFileName: "file/file.md", + content: mockContent, + frontMatter: { ...mockFrontMatter }, + }) + ).rejects.toThrowError(BadRequestError) + }) it("Renaming pages works correctly", async () => { await expect( service.rename(reqDetails, { diff --git a/services/fileServices/YmlFileServices/CollectionYmlService.js b/services/fileServices/YmlFileServices/CollectionYmlService.js index cb9a44196..c7818bc1b 100644 --- a/services/fileServices/YmlFileServices/CollectionYmlService.js +++ b/services/fileServices/YmlFileServices/CollectionYmlService.js @@ -166,7 +166,8 @@ class CollectionYmlService { }) } - async updateOrder(reqDetails, { collectionName, newOrder, sha }) { + async updateOrder(reqDetails, { collectionName, newOrder }) { + const { sha } = await this.read(reqDetails, { collectionName }) const contentObject = { collections: { [collectionName]: { diff --git a/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js b/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js index 0fdbadadf..6fa6934ce 100644 --- a/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js +++ b/services/fileServices/YmlFileServices/__tests__/CollectionYmlService.spec.js @@ -507,7 +507,6 @@ describe("Collection Yml Service", () => { service.updateOrder(reqDetails, { collectionName, newOrder, - sha: oldSha, }) ).resolves.toMatchObject({ newSha: sha, diff --git a/services/fileServices/YmlFileServices/__tests__/NavYmlService.spec.js b/services/fileServices/YmlFileServices/__tests__/NavYmlService.spec.js new file mode 100644 index 000000000..31c750e0e --- /dev/null +++ b/services/fileServices/YmlFileServices/__tests__/NavYmlService.spec.js @@ -0,0 +1,215 @@ +const { deslugifyCollectionName } = require("@utils/utils") + +const { + NavYmlService, +} = require("@services/fileServices/YmlFileServices/NavYmlService") + +const NAV_FILE_NAME = "navigation.yml" +const NAV_FILE_DIR = "_data" + +const yaml = require("yaml") +const _ = require("lodash") + +describe("Nav Yml Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + const fileName = NAV_FILE_NAME + const collectionName = "collection" + const directoryName = NAV_FILE_DIR + const sha = "12345" + + const reqDetails = { siteName, accessToken } + const mockParsedContent = { + links: [ + { + title: "Page", + url: "/page/", + }, + { + title: "Resource room", + resource_room: true, + }, + { + title: "Collection 1", + collection: collectionName, + }, + { + title: "Collection 2", + collection: "extra-collection", + }, + { + title: "Menu", + url: `/menu`, + sublinks: [ + { + title: "Submenu", + url: "/submenu", + }, + { + title: "Submenu 2", + url: "/submenu-2", + }, + ], + }, + { + title: "Page 2", + url: "/page-2/", + }, + ], + } + const mockRawContent = yaml.stringify(mockParsedContent) + + const mockGithubService = { + read: jest.fn(), + update: jest.fn(), + } + + const service = new NavYmlService({ + gitHubService: mockGithubService, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("Read", () => { + mockGithubService.read.mockResolvedValueOnce({ + content: mockRawContent, + sha, + }), + it("Reading the navigation.yml file works correctly", async () => { + await expect(service.read(reqDetails)).resolves.toMatchObject({ + content: mockParsedContent, + sha, + }) + expect(mockGithubService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + }) + }) + }) + + describe("Update", () => { + const oldSha = "54321" + mockGithubService.update.mockResolvedValueOnce({ newSha: sha }) + it("Updating raw content works correctly", async () => { + await expect( + service.update(reqDetails, { + fileContent: mockParsedContent, + sha: oldSha, + }) + ).resolves.toMatchObject({ + newSha: sha, + }) + expect(mockGithubService.update).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + fileContent: mockRawContent, + sha: oldSha, + }) + }) + }) + + describe("createCollectionInNav", () => { + const newSha = "54321" + const newCollection = `new-collection` + mockGithubService.read.mockResolvedValueOnce({ + content: mockRawContent, + sha, + }) + const updatedMockParsedContent = { + links: mockParsedContent.links.concat({ + title: deslugifyCollectionName(newCollection), + collection: newCollection, + }), + } + mockGithubService.update.mockResolvedValueOnce({ newSha }) + it("Adds new collections to the end of the navigation file and returns the new sha", async () => { + await expect( + service.createCollectionInNav(reqDetails, { + collectionName: newCollection, + }) + ).resolves.toMatchObject({ newSha }) + expect(mockGithubService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + }) + expect(mockGithubService.update).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + fileContent: yaml.stringify(updatedMockParsedContent), + sha, + }) + }) + }) + + describe("renameCollectionInNav", () => { + const newSha = "54321" + const newCollection = `new-collection` + mockGithubService.read.mockResolvedValueOnce({ + content: mockRawContent, + sha, + }) + const updatedMockParsedContent = { + links: mockParsedContent.links.map((link) => { + if (link.collection === collectionName) { + return { + title: deslugifyCollectionName(newCollection), + collection: newCollection, + } + } + return link + }), + } + mockGithubService.update.mockResolvedValueOnce({ newSha }) + it("Adds new collections to the end of the navigation file and returns the new sha", async () => { + await expect( + service.renameCollectionInNav(reqDetails, { + oldCollectionName: collectionName, + newCollectionName: newCollection, + }) + ).resolves.toMatchObject({ newSha }) + expect(mockGithubService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + }) + expect(mockGithubService.update).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + fileContent: yaml.stringify(updatedMockParsedContent), + sha, + }) + }) + }) + + describe("deleteCollectionInNav", () => { + const newSha = "54321" + mockGithubService.read.mockResolvedValueOnce({ + content: mockRawContent, + sha, + }) + const updatedMockParsedContent = { + links: mockParsedContent.links.filter( + (link) => link.collection !== collectionName + ), + } + mockGithubService.update.mockResolvedValueOnce({ newSha }) + it("Removes selected collection from navigation file", async () => { + await expect( + service.deleteCollectionInNav(reqDetails, { + collectionName, + }) + ).resolves.toMatchObject({ newSha }) + expect(mockGithubService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + }) + expect(mockGithubService.update).toHaveBeenCalledWith(reqDetails, { + fileName, + directoryName, + fileContent: yaml.stringify(updatedMockParsedContent), + sha, + }) + }) + }) +}) diff --git a/services/moverServices/MoverService.js b/services/moverServices/MoverService.js new file mode 100644 index 000000000..ae8bbe13e --- /dev/null +++ b/services/moverServices/MoverService.js @@ -0,0 +1,97 @@ +class MoverService { + constructor({ + unlinkedPageService, + collectionPageService, + subcollectionPageService, + }) { + this.unlinkedPageService = unlinkedPageService + this.collectionPageService = collectionPageService + this.subcollectionPageService = subcollectionPageService + } + + async movePage( + reqDetails, + { + fileName, + oldFileCollection, + oldFileSubcollection, + newFileCollection, + newFileSubcollection, + } + ) { + let fileFrontMatter + let fileBody + if (oldFileSubcollection) { + const { + content: { frontMatter, pageBody }, + sha, + } = await this.subcollectionPageService.read(reqDetails, { + fileName, + collectionName: oldFileCollection, + subcollectionName: oldFileSubcollection, + }) + fileFrontMatter = frontMatter + fileBody = pageBody + await this.subcollectionPageService.delete(reqDetails, { + fileName, + collectionName: oldFileCollection, + subcollectionName: oldFileSubcollection, + sha, + }) + } else if (oldFileCollection) { + const { + content: { frontMatter, pageBody }, + sha, + } = await this.collectionPageService.read(reqDetails, { + fileName, + collectionName: oldFileCollection, + }) + fileFrontMatter = frontMatter + fileBody = pageBody + await this.collectionPageService.delete(reqDetails, { + fileName, + collectionName: oldFileCollection, + sha, + }) + } else { + const { + content: { frontMatter, pageBody }, + sha, + } = await this.unlinkedPageService.read(reqDetails, { + fileName, + }) + fileFrontMatter = frontMatter + fileBody = pageBody + await this.unlinkedPageService.delete(reqDetails, { fileName, sha }) + } + + let createResp + if (newFileSubcollection) { + createResp = await this.subcollectionPageService.create(reqDetails, { + fileName, + collectionName: newFileCollection, + subcollectionName: newFileSubcollection, + content: fileBody, + frontMatter: fileFrontMatter, + }) + } else if (newFileCollection) { + createResp = await this.collectionPageService.create(reqDetails, { + fileName, + collectionName: newFileCollection, + content: fileBody, + frontMatter: fileFrontMatter, + }) + } else { + createResp = await this.unlinkedPageService.create(reqDetails, { + fileName, + content: fileBody, + frontMatter: fileFrontMatter, + }) + } + return createResp + } +} + +module.exports = { + MoverService, +} diff --git a/services/moverServices/__tests__/MoverService.spec.js b/services/moverServices/__tests__/MoverService.spec.js new file mode 100644 index 000000000..d5b90d4b3 --- /dev/null +++ b/services/moverServices/__tests__/MoverService.spec.js @@ -0,0 +1,324 @@ +describe("Mover Service", () => { + const siteName = "test-site" + const accessToken = "test-token" + const fileName = "test-file" + const oldCollectionName = "old-col" + const oldSubcollectionName = "old-subcol" + const collectionName = "collection" + const subcollectionName = "collection" + const mockContent = "test" + const mockFrontMatter = { + title: "fileTitle", + permalink: "file/permalink", + } + const mockNewContent = "new test" + const mockNewFrontMatter = { + title: "newfileTitle", + permalink: "file/newpermalink", + } + const sha = "12345" + + const reqDetails = { siteName, accessToken } + + const mockUnlinkedPageService = { + create: jest.fn(), + read: jest.fn(), + delete: jest.fn(), + } + const mockCollectionPageService = { + create: jest.fn(), + read: jest.fn(), + delete: jest.fn(), + } + const mockSubcollectionPageService = { + create: jest.fn(), + read: jest.fn(), + delete: jest.fn(), + } + + const { MoverService } = require("@services/moverServices/MoverService") + const service = new MoverService({ + unlinkedPageService: mockUnlinkedPageService, + collectionPageService: mockCollectionPageService, + subcollectionPageService: mockSubcollectionPageService, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe("MovePage", () => { + const createResp = { + content: { frontMatter: mockNewFrontMatter, pageBody: mockNewContent }, + sha, + } + mockUnlinkedPageService.read.mockResolvedValue({ + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + sha, + }) + mockUnlinkedPageService.create.mockResolvedValue(createResp) + mockCollectionPageService.read.mockResolvedValue({ + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + sha, + }) + mockCollectionPageService.create.mockResolvedValue(createResp) + mockSubcollectionPageService.read.mockResolvedValue({ + content: { frontMatter: mockFrontMatter, pageBody: mockContent }, + sha, + }) + mockSubcollectionPageService.create.mockResolvedValue(createResp) + it("Moving unlinked page to a collection works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + // oldFileCollection, + // oldFileSubcollection, + newFileCollection: collectionName, + // newFileSubcollection, + }) + ).resolves.toMatchObject(createResp) + expect(mockUnlinkedPageService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + }) + expect(mockUnlinkedPageService.delete).toHaveBeenCalledWith(reqDetails, { + fileName, + sha, + }) + expect(mockCollectionPageService.create).toHaveBeenCalledWith( + reqDetails, + { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + collectionName, + } + ) + }) + it("Moving unlinked page to a subcollection works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + // oldFileCollection, + // oldFileSubcollection, + newFileCollection: collectionName, + newFileSubcollection: subcollectionName, + }) + ).resolves.toMatchObject(createResp) + expect(mockUnlinkedPageService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + }) + expect(mockUnlinkedPageService.delete).toHaveBeenCalledWith(reqDetails, { + fileName, + sha, + }) + expect(mockSubcollectionPageService.create).toHaveBeenCalledWith( + reqDetails, + { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + collectionName, + subcollectionName, + } + ) + }) + it("Moving collection page to unlinked pages works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + oldFileCollection: oldCollectionName, + // oldFileSubcollection, + // newFileCollection: collectionName, + // newFileSubcollection, + }) + ).resolves.toMatchObject(createResp) + expect(mockCollectionPageService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + collectionName: oldCollectionName, + }) + expect(mockCollectionPageService.delete).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + sha, + } + ) + expect(mockUnlinkedPageService.create).toHaveBeenCalledWith(reqDetails, { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + }) + }) + it("Moving collection page to another collection works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + oldFileCollection: oldCollectionName, + // oldFileSubcollection, + newFileCollection: collectionName, + // newFileSubcollection, + }) + ).resolves.toMatchObject(createResp) + expect(mockCollectionPageService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + collectionName: oldCollectionName, + }) + expect(mockCollectionPageService.delete).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + sha, + } + ) + expect(mockCollectionPageService.create).toHaveBeenCalledWith( + reqDetails, + { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + collectionName, + } + ) + }) + it("Moving collection page to a subcollection works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + oldFileCollection: oldCollectionName, + // oldFileSubcollection, + newFileCollection: collectionName, + newFileSubcollection: subcollectionName, + }) + ).resolves.toMatchObject(createResp) + expect(mockCollectionPageService.read).toHaveBeenCalledWith(reqDetails, { + fileName, + collectionName: oldCollectionName, + }) + expect(mockCollectionPageService.delete).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + sha, + } + ) + expect(mockSubcollectionPageService.create).toHaveBeenCalledWith( + reqDetails, + { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + collectionName, + subcollectionName, + } + ) + }) + it("Moving subcollection page to unlinked pages works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + oldFileCollection: oldCollectionName, + oldFileSubcollection: oldSubcollectionName, + }) + ).resolves.toMatchObject(createResp) + expect(mockSubcollectionPageService.read).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + subcollectionName: oldSubcollectionName, + } + ) + expect(mockSubcollectionPageService.delete).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + subcollectionName: oldSubcollectionName, + sha, + } + ) + expect(mockUnlinkedPageService.create).toHaveBeenCalledWith(reqDetails, { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + }) + }) + it("Moving subcollection page to a collection works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + oldFileCollection: oldCollectionName, + oldFileSubcollection: oldSubcollectionName, + newFileCollection: collectionName, + }) + ).resolves.toMatchObject(createResp) + expect(mockSubcollectionPageService.read).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + subcollectionName: oldSubcollectionName, + } + ) + expect(mockSubcollectionPageService.delete).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + subcollectionName: oldSubcollectionName, + sha, + } + ) + expect(mockCollectionPageService.create).toHaveBeenCalledWith( + reqDetails, + { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + collectionName, + } + ) + }) + it("Moving subcollection page to another subcollection works correctly", async () => { + await expect( + service.movePage(reqDetails, { + fileName, + oldFileCollection: oldCollectionName, + oldFileSubcollection: oldSubcollectionName, + newFileCollection: collectionName, + newFileSubcollection: subcollectionName, + }) + ).resolves.toMatchObject(createResp) + expect(mockSubcollectionPageService.read).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + subcollectionName: oldSubcollectionName, + } + ) + expect(mockSubcollectionPageService.delete).toHaveBeenCalledWith( + reqDetails, + { + fileName, + collectionName: oldCollectionName, + subcollectionName: oldSubcollectionName, + sha, + } + ) + expect(mockSubcollectionPageService.create).toHaveBeenCalledWith( + reqDetails, + { + content: mockContent, + frontMatter: mockFrontMatter, + fileName, + collectionName, + subcollectionName, + } + ) + }) + }) +}) diff --git a/utils/utils.js b/utils/utils.js index bb33a1f4e..4ee9d368f 100644 --- a/utils/utils.js +++ b/utils/utils.js @@ -1,4 +1,5 @@ const axios = require("axios") +const slugify = require("slugify") const { GITHUB_ORG_NAME } = process.env @@ -153,6 +154,9 @@ async function revertCommit( ) } +function slugifyCollectionName(collectionName) { + return slugify(collectionName, { lower: true }).replace(/[^a-zA-Z0-9-]/g, "") +} /** * A function to deslugify a collection's name */ @@ -164,6 +168,7 @@ function deslugifyCollectionName(collectionName) { } module.exports = { + slugifyCollectionName, deslugifyCollectionName, getCommitAndTreeSha, getTree, diff --git a/validators/RequestSchema.js b/validators/RequestSchema.js index ea603f5aa..baef7f9bc 100644 --- a/validators/RequestSchema.js +++ b/validators/RequestSchema.js @@ -1,5 +1,6 @@ const Joi = require("joi") +// Pages const FrontMatterSchema = Joi.object({ title: Joi.string().required(), permalink: Joi.string().required(), @@ -26,8 +27,46 @@ const DeletePageRequestSchema = Joi.object().keys({ sha: Joi.string().required(), }) +// Collections +const FileSchema = Joi.object().keys({ + name: Joi.string().required(), + type: Joi.string().valid("file").required(), +}) + +const ItemSchema = FileSchema.keys({ + type: Joi.string().valid("file", "dir").required(), + children: Joi.array().items(Joi.string()), +}) + +const CreateDirectoryRequestSchema = Joi.object().keys({ + newDirectoryName: Joi.string().required(), + items: Joi.array().items(FileSchema), +}) + +const RenameDirectoryRequestSchema = Joi.object().keys({ + newDirectoryName: Joi.string().required(), +}) + +const ReorderDirectoryRequestSchema = Joi.object().keys({ + items: Joi.array().items(ItemSchema).required(), +}) + +const MoveDirectoryPagesRequestSchema = Joi.object().keys({ + target: Joi.object() + .keys({ + collectionName: Joi.string(), + subCollectionName: Joi.string(), + }) + .required(), + items: Joi.array().items(FileSchema).required(), +}) + module.exports = { CreatePageRequestSchema, UpdatePageRequestSchema, DeletePageRequestSchema, + CreateDirectoryRequestSchema, + RenameDirectoryRequestSchema, + ReorderDirectoryRequestSchema, + MoveDirectoryPagesRequestSchema, } diff --git a/validators/validators.js b/validators/validators.js new file mode 100644 index 000000000..f518dc097 --- /dev/null +++ b/validators/validators.js @@ -0,0 +1,14 @@ +const specialCharactersRegexTest = /[~%^*_+\-./\\`;~{}[\]"<>]/ + +const titleSpecialCharCheck = ({ title, isFile = false }) => { + let testTitle = title + if (isFile) { + // Remove .md + testTitle = title.replace(/.md$/, "") + } + return specialCharactersRegexTest.test(testTitle) +} + +module.exports = { + titleSpecialCharCheck, +} From 38e1ac551056a3efc6b35e07d2c02bf8927f85ee Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 14 Oct 2021 06:56:23 +0200 Subject: [PATCH 24/25] fix: package.json & package-lock.json to reduce vulnerabilities (#311) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-PACRESOLVER-1564857 --- package-lock.json | 138 ++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 67 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index e683d5af1..597bd1c6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1592,9 +1592,9 @@ }, "dependencies": { "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" } } }, @@ -2515,6 +2515,17 @@ "object-keys": "^1.0.12" } }, + "degenerator": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-3.0.1.tgz", + "integrity": "sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ==", + "requires": { + "ast-types": "^0.13.2", + "escodegen": "^1.8.1", + "esprima": "^4.0.0", + "vm2": "^3.9.3" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2709,14 +2720,6 @@ "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - } } }, "eslint": { @@ -3618,6 +3621,11 @@ "token-types": "^4.1.1" } }, + "file-uri-to-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", + "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3906,18 +3914,13 @@ }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } }, - "file-uri-to-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", - "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==" - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7099,9 +7102,9 @@ "dev": true }, "pac-proxy-agent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-4.1.0.tgz", - "integrity": "sha512-ejNgYm2HTXSIYX9eFlkvqFp8hyJ374uDf0Zq5YUAifiSh1D6fo+iBivQZirGvVv8dCYUsLhmLBRhlAYvBKI5+Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz", + "integrity": "sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==", "requires": { "@tootallnate/once": "1", "agent-base": "6", @@ -7109,15 +7112,15 @@ "get-uri": "3", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "5", - "pac-resolver": "^4.1.0", + "pac-resolver": "^5.0.0", "raw-body": "^2.2.0", "socks-proxy-agent": "5" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -7130,25 +7133,13 @@ } }, "pac-resolver": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-4.2.0.tgz", - "integrity": "sha512-rPACZdUyuxT5Io/gFKUeeZFfE5T7ve7cAkE5TUZRRfuKP0u5Hocwe48X7ZEm6mYB+bTB0Qf+xlVlA/RM/i6RCQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.0.tgz", + "integrity": "sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA==", "requires": { - "degenerator": "^2.2.0", + "degenerator": "^3.0.1", "ip": "^1.1.5", "netmask": "^2.0.1" - }, - "dependencies": { - "degenerator": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-2.2.0.tgz", - "integrity": "sha512-aiQcQowF01RxFI4ZLFMpzyotbQonhNpBao6dkI8JPk5a+hmSjR5ErHp2CQySmQe8os3VBqLCIh87nDBgZXvsmg==", - "requires": { - "ast-types": "^0.13.2", - "escodegen": "^1.8.1", - "esprima": "^4.0.0" - } - } } }, "parent-module": { @@ -7407,24 +7398,24 @@ } }, "proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.1.tgz", - "integrity": "sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-5.0.0.tgz", + "integrity": "sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g==", "requires": { "agent-base": "^6.0.0", "debug": "4", "http-proxy-agent": "^4.0.0", "https-proxy-agent": "^5.0.0", "lru-cache": "^5.1.1", - "pac-proxy-agent": "^4.1.0", + "pac-proxy-agent": "^5.0.0", "proxy-from-env": "^1.0.0", "socks-proxy-agent": "^5.0.0" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -7831,33 +7822,33 @@ "integrity": "sha512-FkMq+MQc5hzYgM86nLuHI98Acwi3p4wX+a5BO9Hhw4JdK4L7WueIiZ4tXEobImPqBz2sVcV0+Mu3GRB30IGang==" }, "smart-buffer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, "socks": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.0.tgz", - "integrity": "sha512-mNmr9owlinMplev0Wd7UHFlqI4ofnBnNzFuzrm63PPaHgbkqCFe4T5LzwKmtQ/f2tX0NTpcdVLyD/FHxFBstYw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", "requires": { "ip": "^1.1.5", "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", - "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", + "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", "requires": { - "agent-base": "6", + "agent-base": "^6.0.2", "debug": "4", "socks": "^2.3.3" }, "dependencies": { "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -7872,8 +7863,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-support": { "version": "0.5.20", @@ -8616,6 +8606,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "vm2": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.3.tgz", + "integrity": "sha512-smLS+18RjXYMl9joyJxMNI9l4w7biW8ilSDaVRvFBDwOH8P0BK1ognFQTpg0wyQ6wIKLTblHJvROW692L/E53Q==" + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -8708,19 +8703,18 @@ } }, "winston-cloudwatch": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/winston-cloudwatch/-/winston-cloudwatch-2.5.2.tgz", - "integrity": "sha512-8HcDE6ey546Vb26gV/YPNFOrzff/RCPwbhVnV7e8oPW5c0JU8g8j1OLChmOpqgUEz3EA7RQAkYHjK0DkvKvyQQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/winston-cloudwatch/-/winston-cloudwatch-3.1.0.tgz", + "integrity": "sha512-zwkuZTFk5nybf42JAp1RGtfCnyGhPDFR3oMbl3BMFgt/a/TD30GAtzZqjvqZOlhB23gtNhC4+YIZySsozVCWCg==", "requires": { "async": "^3.1.0", - "aws-sdk": "^2.553.0", "chalk": "^4.0.0", "fast-safe-stringify": "^2.0.7", "lodash.assign": "^4.2.0", "lodash.find": "^4.6.0", "lodash.isempty": "^4.4.0", "lodash.iserror": "^3.1.1", - "proxy-agent": "^4.0.1" + "proxy-agent": "^5.0.0" }, "dependencies": { "ansi-styles": { @@ -8732,9 +8726,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" diff --git a/package.json b/package.json index de0e88518..e1dbbc1f8 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "toml": "^3.0.0", "uuid": "^3.3.3", "winston": "^3.3.3", - "winston-cloudwatch": "^2.5.2", + "winston-cloudwatch": "^3.1.0", "yaml": "^1.10.2" }, "devDependencies": { From 8763f409b8a9806b29ba626456723a1a73a12a87 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Thu, 14 Oct 2021 07:07:07 +0200 Subject: [PATCH 25/25] fix: package.json & package-lock.json to reduce vulnerabilities (#285) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-AXIOS-1579269 --- package-lock.json | 12 +++++++++--- package.json | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 597bd1c6a..c0d5ff075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1643,9 +1643,9 @@ } }, "axios": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", - "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.3.tgz", + "integrity": "sha512-JtoZ3Ndke/+Iwt5n+BgSli/3idTvpt5OjKyoCmz4LX5+lPiY5l7C1colYezhlxThjNa/NhngCUWZSZFypIFuaA==", "requires": { "follow-redirects": "^1.14.0" } @@ -3769,6 +3769,12 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==" }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", diff --git a/package.json b/package.json index e1dbbc1f8..21e352c79 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "auto-bind": "^4.0.0", "aws-sdk": "^2.946.0", - "axios": "^0.21.2", + "axios": "^0.21.3", "base-64": "^0.1.0", "bluebird": "^3.7.2", "body-parser": "^1.19.0",