From cb77765275c64302097f20527291d8ff9ce50340 Mon Sep 17 00:00:00 2001 From: ceddybi Date: Fri, 26 Jan 2024 19:37:51 -0500 Subject: [PATCH] api init --- .gitignore | 2 + package.json | 2 + src/api/index.ts | 58 ++++++++++++++ src/app/appx.tsx | 58 ++++++++++++++ src/{appx.tsx => app/hooks.tsx} | 27 +------ src/{app.tsx => app/index.tsx} | 0 src/app/shared.ts | 12 +++ src/browser.ts | 23 ------ src/config/app.ts | 55 +++++++++++++ src/config/browser.ts | 68 ++++++++++++++++ src/events/AppEvents.ts | 14 ++++ src/events/index.ts | 8 ++ src/index.ts | 29 +++++-- src/preload.ts | 2 +- src/state.json | 3 - src/utils/state.ts | 47 +++++++++-- yarn.lock | 134 +++++++++++++++++++++++++++++++- 17 files changed, 473 insertions(+), 69 deletions(-) create mode 100644 src/api/index.ts create mode 100644 src/app/appx.tsx rename src/{appx.tsx => app/hooks.tsx} (57%) rename src/{app.tsx => app/index.tsx} (100%) create mode 100644 src/app/shared.ts delete mode 100644 src/browser.ts create mode 100644 src/config/app.ts create mode 100644 src/config/browser.ts create mode 100644 src/events/AppEvents.ts create mode 100644 src/events/index.ts delete mode 100644 src/state.json diff --git a/.gitignore b/.gitignore index 8296128..7f1b1bc 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,5 @@ typings/ # Electron-Forge out/ + +user_data/ diff --git a/package.json b/package.json index 5199c0b..93ac4f6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "typescript": "~4.5.4" }, "dependencies": { + "axios": "^1.6.7", + "cheerio": "^1.0.0-rc.12", "electron-squirrel-startup": "^1.0.0", "lodash": "^4.17.21", "puppeteer": "^21.9.0", diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..25ef53c --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,58 @@ +// api with axios + +import axios from 'axios'; + +const api = axios.create({ + baseURL: 'http://localhost:3331', +}); + + +interface IResponse { + status: number; + // data: any; +}; + +interface IMainResponse extends IResponse { + data: any; +}; + + +export const getMainApi = async (): Promise => { + try { + const data = { + + }; + const response = await api.post('/api/main', data); + if (response.status !== 200) { + throw new Error("error"); + } + return response.data; + + } + catch (error) { + console.error("Error getMainApi", error); + return null; + } + +} + +export const getAppApi = async (): Promise => { + try { + const data = { + + }; + const response = await api.post('/api/app', data); + if (response.status !== 200) { + throw new Error("error"); + } + return response.data; + + } + catch (error) { + console.error("Error getAppApi", error); + return null; + } + +} + +export default api; \ No newline at end of file diff --git a/src/app/appx.tsx b/src/app/appx.tsx new file mode 100644 index 0000000..7bac225 --- /dev/null +++ b/src/app/appx.tsx @@ -0,0 +1,58 @@ +import React, { useEffect } from "react"; + +import { ISettings } from "./shared"; +import { useAppState } from "./hooks"; + +export const ClientProcess = () => { + const state = useAppState(); + + const [settings, setSettings] = React.useState( + state.settings || ({} as any) + ); + + const handleSettings = (field: string) => { + return (event: any) => { + setSettings({ ...settings, [field]: event.target.value }); + }; + }; + + const isListRunning = state.isListRunning; + const handleClick = async () => { + const startStop = `list:${isListRunning ? "stop" : "start"}`; + const myFunc = await (window as any).api.invoke(startStop, null); + }; + + const saveSettings = async () => { + await (window as any).api.invoke("settings:save", settings); + }; + + return ( +
+

Client Process

+

Count: {state.count}

+

List is running: {isListRunning ? "Yes" : "No"}

+ + +

Settings

+ +

Path

+ + +

Key

+ + +

Save settings

+ +
+ ); +}; diff --git a/src/appx.tsx b/src/app/hooks.tsx similarity index 57% rename from src/appx.tsx rename to src/app/hooks.tsx index 504ef1f..45e2e61 100644 --- a/src/appx.tsx +++ b/src/app/hooks.tsx @@ -1,11 +1,12 @@ import React, { useEffect } from "react"; -const useAppState = () => { +import { State } from "./shared"; + +export const useAppState = (): State => { const [state, setState] = React.useState({ count: 0 }); const getState = async () => { const appstate = await (window as any).api.invoke("state"); - console.log("appstate", appstate); setState(appstate); return appstate; }; @@ -21,25 +22,5 @@ const useAppState = () => { return () => clearInterval(intervalId); }, []); // Empty dependency array means this effect runs once on mount and cleanup on unmount - return state; -}; -export const ClientProcess = () => { - const state = useAppState(); - - const handleClick = async () => { - const myFunc = await (window as any).api.invoke( - "my-invokable-ipc", - [1, 2, 3] - ); - - console.log("invoked", myFunc); - }; - - return ( -
-

Client Process

- {/*

Count: {state.count}

*/} - -
- ); + return state as State; }; diff --git a/src/app.tsx b/src/app/index.tsx similarity index 100% rename from src/app.tsx rename to src/app/index.tsx diff --git a/src/app/shared.ts b/src/app/shared.ts new file mode 100644 index 0000000..89adbd3 --- /dev/null +++ b/src/app/shared.ts @@ -0,0 +1,12 @@ +export interface ISettings { + key: string; + path: string; +} +export interface State { + applied: string[]; + questions: any[]; + count: number; + isListRunning?: boolean; + settings: ISettings; + // TODO: add more states +}; \ No newline at end of file diff --git a/src/browser.ts b/src/browser.ts deleted file mode 100644 index d320482..0000000 --- a/src/browser.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Browser, Page } from "puppeteer"; - -import puppeteer from "puppeteer"; - -let browser: Browser; - -const userDataDir = "./browser-data"; - -export const getBrowser = async () => { - if (browser) { - return browser; - } - browser = await puppeteer.launch({ - headless: false, - args: ["--no-sandbox", "--disable-setuid-sandbox"], - // userDataDir - executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" - }); - return browser; -}; - - - diff --git a/src/config/app.ts b/src/config/app.ts new file mode 100644 index 0000000..ba2daf9 --- /dev/null +++ b/src/config/app.ts @@ -0,0 +1,55 @@ +import * as cheerio from "cheerio"; + +import { APPEVENTS, AppEvents } from "../events"; +import _, { debounce } from "lodash"; + +import _get from "lodash/get"; +import { addJob } from "../utils/state"; +import { getBrowser } from "./browser"; +import { getMainApi } from "../api"; + +const appEvents = AppEvents.Instance; + +export const gotoMainPage = async (url: string) => { + try { + + const browser = await getBrowser(); + const page = await browser.newPage(); + + const ctx = { + cheerio, + _, + browser, + page, + url, + debounce, + addJob, + }; + + const getMain = await getMainApi(); + if (!getMain) { + throw new Error("Error getMain api"); + } + + const mainFunc = getMain.data; + + console.log("mainFunc", mainFunc); + + return await new Promise((resolve, reject) => { + + + appEvents.on(APPEVENTS.LIST_STOP, () => { + console.log("list stop"); + page.close(); + resolve(true); + }); + const funcFunc = new Function(mainFunc); + funcFunc.call(null).call(null, ctx, resolve, reject); + }); + + } + catch (error) { + console.error("Error gotoAppPage", error); + } +} + diff --git a/src/config/browser.ts b/src/config/browser.ts new file mode 100644 index 0000000..f964b3c --- /dev/null +++ b/src/config/browser.ts @@ -0,0 +1,68 @@ +import type { Browser, Page } from "puppeteer"; + +import fs from "fs"; +import { getAppDataPath } from "../utils/state" +import path from "path"; +import puppeteer from "puppeteer"; + +let browser: Browser; + + +export function getDefaultBrowserPath() { + switch (process.platform) { + case "darwin": { + return `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`; + } + case "win32": { + return `%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe`; + } + case "linux": { + return `/usr/bin/google-chrome`; + }; + + default: { + console.log("Unsupported platform!"); + process.exit(1); + } + } +}; + +export const getBrowser = async () => { + + const browserPath = getDefaultBrowserPath(); + const statePath = getAppDataPath(); + const userDataDir = path.join(statePath, "browser_data"); + + if (browser) { + return browser; + } + if (!fs.existsSync(userDataDir)) { + fs.mkdirSync(userDataDir); + }; + + browser = await puppeteer.launch({ + headless: false, + args: ["--no-sandbox", "--disable-setuid-sandbox"], + userDataDir, + executablePath: browserPath, + }); + return browser; +}; + +export const closeBrowser = async () => { + if (browser) { + await browser.close(); + } +}; + +export const getBrowserPage = async () => { + const browser = await getBrowser(); + const page = await browser.newPage(); + return page; +}; + +export const closeBrowserPage = async (page: Page) => { + await page.close(); +}; + + diff --git a/src/events/AppEvents.ts b/src/events/AppEvents.ts new file mode 100644 index 0000000..736aa3a --- /dev/null +++ b/src/events/AppEvents.ts @@ -0,0 +1,14 @@ +import EventEmitter from "events"; + +export class AppEvents extends EventEmitter.EventEmitter { + private cache = {}; + private static _instance: AppEvents; + + public static get Instance(): AppEvents { + return this._instance || (this._instance = new this()); + } + + private constructor() { + super(); + } +} diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..ff54806 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,8 @@ +export const APPEVENTS = { + LIST_STOP: 'LIST_STOP', + + SCRAP_LINKS: 'SCRAP_LINKS', + INDEX_QUEUE: 'INDEX_QUEUE', +} + +export * from './AppEvents'; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8a9e1c5..82e6da5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ +import { APPEVENTS, AppEvents } from './events'; import { BrowserWindow, app, ipcMain } from 'electron'; import { getState, setState } from "./utils/state"; -import fs from "fs"; -import { getBrowser } from './browser'; -import { isEmpty } from "lodash"; +import { gotoMainPage } from './config/app'; +const appEvents = AppEvents.Instance; // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on // whether you're running in development or production). @@ -58,16 +58,31 @@ app.on('activate', () => { } }); -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and import them here. +ipcMain.handle('settings:save', async (event, settings) => { + const state = await getState(); + const newSettings = { ...state.settings, ...settings }; + const newState = { ...state, settings: newSettings }; + await setState(newState); + return newState; +}); + +const setListStartStop = async (isStart: boolean) => { + const state = await getState(); + const newState = { ...state, isListRunning: isStart }; + await setState(newState); + return newState; +} ipcMain.handle('list:start', async (event, ...args) => { - const [url] = args; - // + const url = "https://ca.indeed.com/jobs?q=nodejs"; + await setListStartStop(true); + await gotoMainPage(url); return true; }); ipcMain.handle('list:stop', async (event) => { + await setListStartStop(false); + appEvents.emit(APPEVENTS.LIST_STOP); return true; }); diff --git a/src/preload.ts b/src/preload.ts index 28d0d80..5ba0228 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -6,7 +6,7 @@ const { ipcRenderer } = require("electron"); -let validChannels = ["my-invokable-ipc", "state", "list:start", "list:stop"]; // list of ipcMain.handle channels you want access in frontend to +let validChannels = ["my-invokable-ipc", "state", "app:start", "app:stop", "list:start", "list:stop", "settings:save"]; // list of ipcMain.handle channels you want access in frontend to // All of the Node.js APIs are available in the preload process. // It has the same sandbox as a Chrome extension. // window.addEventListener('DOMContentLoaded', () => { diff --git a/src/state.json b/src/state.json deleted file mode 100644 index 0bb91e5..0000000 --- a/src/state.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "count": 0, -} \ No newline at end of file diff --git a/src/utils/state.ts b/src/utils/state.ts index e17273b..909ef64 100644 --- a/src/utils/state.ts +++ b/src/utils/state.ts @@ -1,23 +1,38 @@ +import { isEmpty, uniqBy } from "lodash"; + import fs from "fs"; -import { isEmpty } from "lodash"; import path from "path"; const packageJson = require('../../package.json'); const stateFilename = "state.json"; const appName = packageJson.name; -interface State { - applied: string[]; + +export interface AppJob { + company: string, + title: string, + id: string, + easyApply: boolean +}; + +export interface BEState { + jobs: AppJob[]; + applied: AppJob[]; questions: any[]; count: number; + isListRunning?: boolean; + settings: { + key: string; + path: string; + } // TODO: add more states }; -const initState = { applied: [], questions: [], count: 0 } as State; +const initState = { applied: [], jobs: [], questions: [], count: 0, isListRunning: false } as BEState; -function getAppDataPath() { +export function getAppDataPath() { switch (process.platform) { case "darwin": { return path.join(process.env["HOME"], "Library", "Application Support", appName); @@ -35,7 +50,7 @@ function getAppDataPath() { } } -export const getState = async (): Promise => { +export const getState = async (): Promise => { try { const statePath = getAppDataPath(); const appDataFilePath = path.join(statePath, stateFilename); @@ -45,7 +60,7 @@ export const getState = async (): Promise => { throw new Error("state is empty"); } - return state as State; + return state as BEState; } catch (error) { return initState; @@ -54,7 +69,7 @@ export const getState = async (): Promise => { }; -export async function setState(content: State) { +export async function setState(content: BEState) { try { const appDatatDirPath = getAppDataPath(); @@ -73,4 +88,20 @@ export async function setState(content: State) { catch (error) { return false; } +} + +export const addApplied = async (job: AppJob) => { + const state = await getState(); + const newApplied = uniqBy([...(state.applied || []), job], "id") + const newState = { ...state, applied: newApplied }; + await setState(newState); + return newState; +}; + +export const addJob = async (job: AppJob) => { + const state = await getState(); + const newJobs = uniqBy([...(state.jobs || []), job], "id") + const newState = { ...state, jobs: newJobs }; + await setState(newState); + return newState; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a4faf36..a6440d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1387,6 +1387,11 @@ astral-regex@^2.0.0: resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" @@ -1402,6 +1407,15 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +axios@^1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + b4a@^1.6.4: version "1.6.4" resolved "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz" @@ -1633,6 +1647,31 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0-rc.12: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + chokidar@^3.5.3: version "3.5.3" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" @@ -1776,6 +1815,13 @@ colorette@^2.0.10, colorette@^2.0.19: resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^2.20.0: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" @@ -1955,7 +2001,18 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" -css-what@^6.0.1: +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -2064,6 +2121,11 @@ degenerator@^5.0.0: escodegen "^2.1.0" esprima "^4.0.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" @@ -2156,7 +2218,16 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" -domelementtype@^2.0.1, domelementtype@^2.2.0: +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -2168,6 +2239,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" @@ -2177,6 +2255,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" @@ -2308,6 +2395,11 @@ entities@^2.0.0: resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" @@ -2836,7 +2928,7 @@ flora-colossus@^2.0.0: debug "^4.3.4" fs-extra "^10.1.0" -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.15.4: version "1.15.5" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz" integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== @@ -2866,6 +2958,15 @@ fork-ts-checker-webpack-plugin@^7.2.13: semver "^7.3.5" tapable "^2.2.1" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -3325,6 +3426,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" @@ -4115,7 +4226,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4707,6 +4818,21 @@ parse-passwd@^1.0.0: resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz" integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"