├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src ├── backup-markdown-files │ ├── helper.ts │ └── index.ts ├── index.ts └── roam-export │ ├── const.ts │ ├── download.ts │ ├── helpers.ts │ ├── index.ts │ └── stage.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .DS_Store 107 | export-test/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2020 Richard B 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # note-tools 2 | 3 | A collection of my tools related to notetaking. Features include: 4 | 5 | - Backuping up your Roam Research graphs 6 | - Backing up embedded images and pdf from markdown files, and replacing their paths. 7 | 8 | 9 | ## Using as a Github Action 10 | 11 | You can use the above two commands together with github actions to automatically backup your roam notes: 12 | 13 | ```yaml 14 | name: "Roam Research backup" 15 | 16 | on: 17 | schedule: 18 | - cron: "17 1 * * *" 19 | workflow_dispatch: 20 | 21 | jobs: 22 | backup: 23 | runs-on: ubuntu-latest 24 | name: Backup 25 | timeout-minutes: 15 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up Node 29 | uses: actions/setup-node@v2-beta 30 | - name: Export from roam 31 | run: npx note-tools roam-export --output roamresearch/private roamresearch --email $ROAMRESEARCH_USER --password $ROAMRESEARCH_PASSWORD --graph $ROAMRESEARCH_DATABASE --formats JSON --formats Markdown --extract 32 | env: 33 | ROAMRESEARCH_USER: ${{ secrets.ROAMRESEARCH_USER }} 34 | ROAMRESEARCH_PASSWORD: ${{ secrets.ROAMRESEARCH_PASSWORD }} 35 | ROAMRESEARCH_DATABASE: ${{ secrets.ROAMRESEARCH_DATABASE }} 36 | - name: Download images and files 37 | run: npx note-tools backup-markdown-files --source roamresearch/private/markdown --files roamresearch/private/files --output roamresearch/private/formatted 38 | - uses: EndBug/add-and-commit@v5 39 | with: 40 | author_name: Your name 41 | author_email: youremail@example.com 42 | message: "Automated snapshot" 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | ``` 46 | 47 | This will run at 01:17, and: 48 | 49 | - export your Roam Research notes in json and markdown formats 50 | - Download all embedded PDFs and images, and rewrite the paths to them, so you can them locallt (and on github) 51 | At least with images. PDF embeds are not displayed, but they're saved too! 52 | - Commit these with the given parameters 53 | See `author_name` etc. 54 | 55 | 56 | 57 | ## Usage locally 58 | 59 | You'll need at least node version 12. To run it, just use `npx note-tools`. 60 | 61 | 62 | ## Available Commands 63 | 64 | All commands include embedded help! Just run them without any parameters. to see, or view below. 65 | 66 | ### `note-tools roam-export` 67 | 68 | Export your notes from Roam Research 69 | 70 | ``` 71 | Options: 72 | --version Show version number [boolean] 73 | --help Show help [boolean] 74 | --email The email address of your Roam Research account. 75 | [string] [required] 76 | --password The password of your Roam Research account. Only sent to Roam. 77 | [string] [required] 78 | --graph The name of the graph you want to export [string] [required] 79 | --formats [array] [choices: "JSON", "EDN", "Markdown"] [default: ["JSON"]] 80 | --extract [boolean] [default: true] 81 | --output [string] 82 | --debug Open the browser instead of using a headless version. 83 | [boolean] [default: false] 84 | ``` 85 | 86 | ### `note-tools backup-markdown-files` 87 | 88 | ``` 89 | Options: 90 | --version Show version number [boolean] 91 | --help Show help [boolean] 92 | --source The folder containing markdown files to search in 93 | [string] [required] 94 | --files The output folder, where the downloaded files will be written 95 | [string] [required] 96 | --replace Replace the links in the files with the relative local paths 97 | [boolean] 98 | --output The folder where the markdown files with replaced output will be 99 | written. Only one of replace or output can be set. [string] 100 | ``` 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "note-tools", 3 | "description": "Tools for working with roamresearch.com notes.", 4 | "version": "0.2.0", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "note-tools": "dist/index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/rbrcsk/note-tools.git" 12 | }, 13 | "keyword": [ 14 | "markdown-backup", 15 | "markdown", 16 | "Note tools", 17 | "Roam Research" 18 | ], 19 | "author": "Richard B ", 20 | "license": "MIT", 21 | "scripts": { 22 | "dev": "ts-node src/index.ts", 23 | "build": "tsc" 24 | }, 25 | "dependencies": { 26 | "del": "^6.0.0", 27 | "extract-zip": "^2.0.1", 28 | "fs-extra": "^9.0.1", 29 | "glob": "^7.1.6", 30 | "node-fetch": "^2.6.1", 31 | "playwright": "^1.5.1", 32 | "prettier": "^2.1.2", 33 | "string-replace-async": "^2.0.0", 34 | "yargs": "^16.1.0" 35 | }, 36 | "devDependencies": { 37 | "@types/fs-extra": "^9.0.2", 38 | "@types/glob": "^7.1.3", 39 | "@types/node": "^14.14.2", 40 | "@types/node-fetch": "^2.5.7", 41 | "@types/prettier": "^2.1.5", 42 | "@types/yargs": "^15.0.9", 43 | "ts-node": "^9.0.0", 44 | "typescript": "^4.0.3" 45 | } 46 | } -------------------------------------------------------------------------------- /src/backup-markdown-files/helper.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs from "fs"; 3 | import { createHash } from "crypto"; 4 | 5 | import fetch from "node-fetch"; 6 | 7 | export const getFolderOrCwd = (outDir?: string) => { 8 | let folder = outDir ?? process.cwd(); 9 | 10 | folder = path.resolve(folder); 11 | 12 | if (!fs.existsSync(folder)) { 13 | fs.mkdirSync(folder); 14 | } 15 | 16 | return folder; 17 | }; 18 | 19 | export const getPredictableFileName = (url: string) => { 20 | const parsedUrl = new URL(url); 21 | 22 | const pathname = parsedUrl.pathname; 23 | const parsedPath = path.parse(pathname); 24 | const extension = parsedPath.ext; 25 | 26 | const hash = createHash("md5").update(url).digest("hex"); 27 | 28 | return `${hash}${extension}`; 29 | }; 30 | 31 | export const getFileLocalPath = ( 32 | mdFile: string, 33 | fileFolder: string, 34 | fileFileName: string 35 | ) => { 36 | const filePath = path.join(fileFolder, fileFileName); 37 | 38 | return path.relative(path.dirname(mdFile), filePath); 39 | }; 40 | 41 | export const downloadFile = async (url: string, path: string) => { 42 | const res = await fetch(url); 43 | const fileStream = fs.createWriteStream(path); 44 | await new Promise((resolve, reject) => { 45 | res.body.pipe(fileStream); 46 | res.body.on("error", reject); 47 | fileStream.on("finish", resolve); 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /src/backup-markdown-files/index.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import fs, { rmSync } from "fs"; 3 | import fse from "fs-extra"; 4 | import { glob } from "glob"; 5 | 6 | import sra from "string-replace-async"; 7 | import prettier from "prettier"; 8 | import del from "del"; 9 | import { 10 | getFolderOrCwd, 11 | getPredictableFileName, 12 | getFileLocalPath, 13 | downloadFile, 14 | } from "./helper"; 15 | 16 | const MD_IMAGE_REGEX = /!\[[^\]\r\n]*\]\((?[^)\r\n]*)\)/g; 17 | const ROAM_PDF_REGEX = /{{pdf: (?[^}]*)}}/g; 18 | 19 | export const backupMarkdownFiles = async ( 20 | mdFolderParam?: string, 21 | filesFolderParam?: string, 22 | replace?: boolean 23 | ) => { 24 | const mdFolder = getFolderOrCwd(mdFolderParam); 25 | const filesFolder = getFolderOrCwd(filesFolderParam); 26 | const mappingFilePath = path.join(filesFolder, "mappings.json"); 27 | 28 | const markdownFileNames = glob.sync(`${mdFolder}/**/*.md`); 29 | 30 | // Step 1: go through all files, build a map of resources, genereate deterministic local names for them, and optionally replace link 31 | 32 | const sourceMappings: { [file: string]: string[] } = {}; 33 | const fileMappings: { [url: string]: string } = {}; 34 | 35 | for (const fileName of markdownFileNames) { 36 | const fileContent = await fs.promises.readFile(fileName, "utf8"); 37 | let transformedContent: string; 38 | 39 | const found: [string, string][] = []; 40 | 41 | transformedContent = await sra( 42 | fileContent, 43 | MD_IMAGE_REGEX, 44 | async (match, url) => { 45 | if (!/^http/.test(url)) { 46 | // ignore local images 47 | return match; 48 | } 49 | 50 | const newFileName = getPredictableFileName(url); 51 | const localPath = getFileLocalPath(fileName, filesFolder, newFileName); 52 | console.log(localPath); 53 | found.push([url, newFileName]); 54 | return match.replace(url, `${localPath}`); 55 | } 56 | ); 57 | 58 | transformedContent = await sra( 59 | transformedContent, 60 | ROAM_PDF_REGEX, 61 | async (match, url) => { 62 | if (!/^http/.test(url)) { 63 | // ignore local images 64 | return match; 65 | } 66 | 67 | const newFileName = getPredictableFileName(url); 68 | const localPath = getFileLocalPath(fileName, filesFolder, newFileName); 69 | console.log(localPath); 70 | found.push([url, newFileName]); 71 | 72 | return match.replace(url, `${localPath}`); 73 | } 74 | ); 75 | 76 | if (found.length > 0) { 77 | const plainFileName = path.relative(mdFolder, fileName); 78 | 79 | sourceMappings[plainFileName] = found.map(([url]) => url); 80 | found.forEach(([url, fileName]) => (fileMappings[url] = fileName)); 81 | } 82 | 83 | if (replace && transformedContent !== fileContent) { 84 | await fs.promises.writeFile(fileName, transformedContent, "utf-8"); 85 | } 86 | } 87 | 88 | // Step 2: Save the mappings 89 | for (const [url, fileName] of Object.entries(fileMappings)) { 90 | const filePath = path.join(filesFolder, fileName); 91 | if (!fs.existsSync(filePath)) { 92 | console.log(`Downloading ${url} into ${fileName}`); 93 | await downloadFile(url, filePath); 94 | } else { 95 | console.log(`Skipping ${url} as it already exists as ${fileName}`); 96 | } 97 | } 98 | 99 | // Step 3: Download all files 100 | 101 | const mappings = JSON.stringify({ sourceMappings, fileMappings }); 102 | const formattedMappings = prettier.format(mappings, { parser: "json" }); 103 | 104 | await fs.promises.writeFile(mappingFilePath, formattedMappings); 105 | }; 106 | 107 | export const copyDirectory = (from: string, to: string) => { 108 | from = getFolderOrCwd(from); 109 | to = getFolderOrCwd(to); 110 | 111 | fse.copySync(from, to); 112 | }; 113 | export const deleteDirectory = async (path: string) => { 114 | return del(path); 115 | }; 116 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import yargs from "yargs"; 3 | import { 4 | backupMarkdownFiles, 5 | copyDirectory, 6 | deleteDirectory, 7 | } from "./backup-markdown-files"; 8 | import { roamExport } from "./roam-export"; 9 | 10 | yargs 11 | .usage("usage: $0 ") 12 | .command({ 13 | command: "roam-export", 14 | describe: "Export your notes from Roam Research", 15 | builder: { 16 | email: { 17 | type: "string", 18 | demandOption: true, 19 | describe: "The email address of your Roam Research account.", 20 | }, 21 | password: { 22 | type: "string", 23 | demandOption: true, 24 | describe: 25 | "The password of your Roam Research account. Only sent to Roam.", 26 | }, 27 | graph: { 28 | type: "string", 29 | demandOption: true, 30 | describe: "The name of the graph you want to export", 31 | }, 32 | formats: { 33 | type: "array", 34 | demandOption: false, 35 | choices: ["JSON", "EDN", "Markdown"] as const, 36 | default: ["JSON"], 37 | }, 38 | extract: { 39 | type: "boolean", 40 | default: true, 41 | }, 42 | output: { 43 | type: "string", 44 | demandOption: false, 45 | }, 46 | debug: { 47 | type: "boolean", 48 | describe: "Open the browser instead of using a headless version.", 49 | default: false, 50 | }, 51 | }, 52 | handler: (parsed) => 53 | roamExport( 54 | parsed.email as string, 55 | parsed.password as string, 56 | parsed.graph as string, 57 | parsed.formats as string[], 58 | parsed.output as string, 59 | parsed.extract as boolean, 60 | parsed.debug as boolean 61 | ), 62 | }) 63 | .command({ 64 | command: "backup-markdown-files", 65 | describe: "Save linked files and images locally", 66 | 67 | builder: { 68 | source: { 69 | type: "string", 70 | demandOption: true, 71 | describe: "The folder containing markdown files to search in", 72 | }, 73 | files: { 74 | type: "string", 75 | demandOption: true, 76 | describe: 77 | "The output folder, where the downloaded files will be written", 78 | }, 79 | replace: { 80 | type: "boolean", 81 | demandOption: false, 82 | describe: 83 | "Replace the links in the files with the relative local paths", 84 | }, 85 | output: { 86 | type: "string", 87 | demandOption: false, 88 | describe: 89 | "The folder where the markdown files with replaced output will be written. Only one of replace or output can be set.", 90 | }, 91 | }, 92 | handler: async (parsed) => { 93 | if (parsed.output) { 94 | // If we have an output specified, copy the files there, and replace the content 95 | await deleteDirectory(parsed.output as string); 96 | copyDirectory(parsed.source as string, parsed.output as string); 97 | 98 | backupMarkdownFiles( 99 | parsed.output as string | undefined, 100 | parsed.files as string | undefined, 101 | true 102 | ); 103 | } else { 104 | backupMarkdownFiles( 105 | parsed.source as string | undefined, 106 | parsed.files as string | undefined, 107 | parsed.replace as boolean 108 | ); 109 | } 110 | }, 111 | }) 112 | .conflicts("replace", "output") 113 | .demandCommand() 114 | .help("help").argv; 115 | -------------------------------------------------------------------------------- /src/roam-export/const.ts: -------------------------------------------------------------------------------- 1 | export const ROAM_LOGIN_URL = "https://roamresearch.com/#/signin"; 2 | export const ROAM_GRAPH_SELECTION_URL = "https://roamresearch.com/#/app"; 3 | 4 | export const SELECTOR_ROAM_APP_WRAPPER = ".roam-app"; 5 | export const SELECTOR_EXPORT_DIALOG = ".bp3-dialog"; 6 | export const SELECTOR_EXPORT_SPINNER = ".bp3-spinner.bp3-intent-primary"; 7 | export const SELECTOR_LOADING_INDICATOR = ".loading-astrolabe"; 8 | -------------------------------------------------------------------------------- /src/roam-export/download.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getInitialPage, 3 | loginToRoam, 4 | sleep, 5 | openGraph, 6 | openExportPopup, 7 | exportAll, 8 | } from "./helpers"; 9 | import { detectStage } from "./stage"; 10 | 11 | export const downloadLoop = async ( 12 | email: string, 13 | password: string, 14 | graphName: string, 15 | formats: string[], 16 | outDir: string, 17 | debug: boolean 18 | ) => { 19 | const headless = !debug; 20 | 21 | const { page, browser } = await getInitialPage(headless, outDir); 22 | 23 | let run = true; 24 | let success = false; 25 | 26 | const toExport = [...formats]; 27 | 28 | while (run) { 29 | const stage = await detectStage(page); 30 | 31 | if (stage !== "EXPORT_IN_PROGRESS" && toExport.length === 0) { 32 | run = false; 33 | success = true; 34 | break; 35 | } 36 | 37 | switch (stage) { 38 | case "LOGGED_OUT": 39 | // Logged out state, we need to login 40 | await loginToRoam(page, email, password); 41 | break; 42 | case "UNHANDLED": 43 | case "LOADING": 44 | // either the graph, or the graph selection is loading 45 | await sleep(1); 46 | break; 47 | case "GRAPH_SELECT": // Graph selection screen 48 | await openGraph(page, graphName); 49 | break; 50 | case "GRAPH_LOADED": 51 | // The graph is loaded. 52 | // Check if exports have started, and open export popup if not 53 | // If all exports are done, close the browser 54 | await openExportPopup(page); 55 | break; 56 | case "EXPORT_POPUP": 57 | // The export popup is open, 58 | // and start the next export 59 | const successfulExport = await exportAll(page, toExport[0]); 60 | if (successfulExport) { 61 | toExport.shift(); 62 | await sleep(5); 63 | } else { 64 | console.log("export failed, reloading page"); 65 | await page.reload(); 66 | } 67 | break; 68 | case "EXPORT_IN_PROGRESS": 69 | await sleep(10); 70 | break; 71 | default: 72 | run = false; 73 | success = false; 74 | break; 75 | } 76 | } 77 | 78 | browser.close(); 79 | return success; 80 | }; 81 | -------------------------------------------------------------------------------- /src/roam-export/helpers.ts: -------------------------------------------------------------------------------- 1 | import playwright from "playwright"; 2 | import type { Page } from "playwright"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import del from "del"; 6 | import prettier from "prettier"; 7 | 8 | import { ROAM_LOGIN_URL } from "./const"; 9 | import extract from "extract-zip"; 10 | import glob from "glob"; 11 | 12 | export const getOutputFolder = (outDir?: string) => { 13 | let outputFolder = outDir ?? process.cwd(); 14 | 15 | outputFolder = path.resolve(outputFolder); 16 | 17 | if (!fs.existsSync(outputFolder)) { 18 | fs.mkdirSync(outputFolder); 19 | } 20 | 21 | return outputFolder; 22 | }; 23 | 24 | const attachDownloadListener = (page: Page, outDir?: string) => { 25 | const outputFolder = getOutputFolder(outDir); 26 | 27 | page.on("download", async (download) => { 28 | const fileName = download.suggestedFilename(); 29 | const downloadPath = path.join(outputFolder, fileName); 30 | 31 | console.log(`A download is initiated, saving to ${downloadPath}`); 32 | await download.saveAs(downloadPath); 33 | console.log(`${fileName} saved`); 34 | }); 35 | }; 36 | 37 | export const getInitialPage = async (headless: boolean, outDir?: string) => { 38 | const browser = await playwright.firefox.launch({ headless }); 39 | const page = await browser.newPage({ acceptDownloads: true }); 40 | 41 | console.log(`Visiting ${ROAM_LOGIN_URL}`); 42 | await page.goto(ROAM_LOGIN_URL); 43 | 44 | attachDownloadListener(page, outDir); 45 | 46 | return { page, browser }; 47 | }; 48 | 49 | export const loginToRoam = async ( 50 | page: Page, 51 | email: string, 52 | password: string 53 | ) => { 54 | try { 55 | console.log("// Step: loginToRoam"); 56 | 57 | console.log(`Filling in email ${email}`); 58 | await page.fill("[name='email']", email); 59 | console.log(`Filling in password ${password}`); 60 | await page.fill("[name='password']", password); 61 | 62 | const buttons = await page.$$("#app button"); 63 | const signInButton = buttons.find( 64 | async (b) => (await b.textContent()) === "Sign In" 65 | ); 66 | 67 | if (signInButton) { 68 | console.log("Waiting for login success"); 69 | await Promise.all([signInButton.click(), page.waitForNavigation()]); 70 | } else { 71 | throw new Error("Sign in button is not on the page"); 72 | } 73 | } catch { 74 | console.log("// Failed: loginToRoam"); 75 | } 76 | }; 77 | 78 | export const openGraph = async (page: Page, graphName: string) => { 79 | try { 80 | console.log("// Step: openGraph"); 81 | 82 | console.log(`Selecting graph with name ${graphName}`); 83 | await page.click(`'${graphName}'`); 84 | console.log(`Graph opening!`); 85 | } catch { 86 | console.log("// Failed: openGraph"); 87 | } 88 | }; 89 | 90 | export const openExportPopup = async (page: Page) => { 91 | try { 92 | console.log("// Step: openExportPopup"); 93 | 94 | console.log("Opening more menu."); 95 | await page.click(".bp3-icon-more", { timeout: 1 * 1000 }); 96 | 97 | console.log("Opening export popup"); 98 | await page.click("'Export All'"); 99 | } catch { 100 | console.log("// Failed: openExportPopup"); 101 | } 102 | }; 103 | 104 | export const exportAll = async (page: Page, format: string) => { 105 | try { 106 | console.log(`// Step: exportAll:${format}`); 107 | const currentSelected = await page.textContent( 108 | ".bp3-dialog .bp3-popover-target .bp3-button-text", 109 | { timeout: 10 * 1000 } 110 | ); 111 | 112 | console.log({ currentSelected }); 113 | 114 | if (!currentSelected) throw Error("Page is not as expexted"); 115 | 116 | if (currentSelected.toLowerCase() === format) { 117 | console.log(`Format ${format} is selected, exporting....`); 118 | page.click("text='Export All'"); 119 | } 120 | 121 | console.log("Listing export format options"); 122 | await page.click(".bp3-dialog .bp3-popover-target .bp3-icon-caret-down"); 123 | 124 | console.log(`Selecting ${format}`); 125 | await page.click(`'${format}'`); 126 | 127 | console.log(`Format ${format} has been selected, exporting....`); 128 | page.click("'Export All'"); 129 | } catch { 130 | return false; 131 | } 132 | 133 | return true; 134 | }; 135 | 136 | export const sleep = async (seconds: number) => { 137 | return new Promise((resolve) => { 138 | setTimeout(() => { 139 | resolve(); 140 | }, seconds * 1000); 141 | }); 142 | }; 143 | 144 | export const extractArchives = async (outDir?: string) => { 145 | const outputFolder = getOutputFolder(outDir); 146 | 147 | const zipPattern = path.join(outputFolder, "*.zip"); 148 | const filenames = glob.sync(zipPattern); 149 | 150 | await Promise.all( 151 | filenames.map(async (filename) => { 152 | const parsedPath = path.parse(filename); 153 | const zipFolder = path.join(parsedPath.dir, parsedPath.name); 154 | 155 | const extensions: string[] = []; 156 | 157 | await extract(filename, { 158 | dir: zipFolder, 159 | onEntry: (entry) => { 160 | extensions.push(path.parse(entry.fileName).ext); 161 | }, 162 | }); 163 | 164 | if (extensions.length === 1 && extensions[0] === ".json") { 165 | console.log(`${filename} contains json export`); 166 | fs.renameSync(zipFolder, path.join(outputFolder, "json")); 167 | } else if (extensions.length === 1 && extensions[0] === ".edn") { 168 | console.log(`${filename} contains edn export`); 169 | fs.renameSync(zipFolder, path.join(outputFolder, "edn")); 170 | } else { 171 | console.log(`${filename} contains markdown export`); 172 | fs.renameSync(zipFolder, path.join(outputFolder, "markdown")); 173 | } 174 | }) 175 | ); 176 | }; 177 | 178 | export const cleanBefore = async (outDir?: string) => { 179 | const outputFolder = getOutputFolder(outDir); 180 | 181 | const zipPattern = path.join(outputFolder, "*.zip"); 182 | const nonRenamedFolderPattern = path.join(outputFolder, "Roam-Export-*"); 183 | 184 | const deletedPaths = await del([ 185 | path.join(outputFolder, "json"), 186 | path.join(outputFolder, "edn"), 187 | path.join(outputFolder, "markdown"), 188 | zipPattern, 189 | nonRenamedFolderPattern, 190 | ]); 191 | 192 | console.log(`Removed files ${deletedPaths.join(", ")}`); 193 | }; 194 | 195 | export const cleanAfter = async (outDir?: string) => { 196 | const outputFolder = getOutputFolder(outDir); 197 | 198 | const zipPattern = path.join(outputFolder, "*.zip"); 199 | const nonRenamedFolderPattern = path.join(outputFolder, "Roam-Export-*"); 200 | 201 | const deletedPaths = await del([zipPattern, nonRenamedFolderPattern]); 202 | 203 | console.log(`Removed files ${deletedPaths.join(", ")}`); 204 | }; 205 | 206 | export const formatJsonExport = (graphName: string, outDir?: string) => { 207 | const outputFolder = getOutputFolder(outDir); 208 | const fileLocation = path.join(outputFolder, "json", `${graphName}.json`); 209 | 210 | const exists = fs.existsSync(fileLocation); 211 | 212 | if (!exists) return; 213 | 214 | const jsonFile = fs.readFileSync(fileLocation, "utf8"); 215 | 216 | const formatted = prettier.format(jsonFile, {parser: "json"}); 217 | 218 | fs.writeFileSync(fileLocation, formatted); 219 | }; 220 | -------------------------------------------------------------------------------- /src/roam-export/index.ts: -------------------------------------------------------------------------------- 1 | import { downloadLoop } from "./download"; 2 | import { 3 | extractArchives, 4 | cleanBefore, 5 | cleanAfter, 6 | formatJsonExport, 7 | } from "./helpers"; 8 | 9 | export const roamExport = async ( 10 | email: string, 11 | password: string, 12 | graphName: string, 13 | formats: string[], 14 | outDir: string, 15 | extractFiles: boolean, 16 | debug: boolean 17 | ) => { 18 | cleanBefore(outDir); 19 | 20 | const downloadSuccess = await downloadLoop( 21 | email, 22 | password, 23 | graphName, 24 | formats, 25 | outDir, 26 | debug 27 | ); 28 | 29 | if (!downloadSuccess) { 30 | process.exitCode = 1; 31 | return; 32 | } 33 | 34 | if (extractFiles) { 35 | await extractArchives(outDir); 36 | } 37 | 38 | if (extractFiles && formats.includes("JSON")) { 39 | formatJsonExport(graphName, outDir); 40 | } 41 | 42 | await cleanAfter(outDir); 43 | }; 44 | -------------------------------------------------------------------------------- /src/roam-export/stage.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "playwright"; 2 | import { 3 | ROAM_LOGIN_URL, 4 | SELECTOR_EXPORT_DIALOG, 5 | SELECTOR_EXPORT_SPINNER, 6 | SELECTOR_ROAM_APP_WRAPPER, 7 | SELECTOR_LOADING_INDICATOR, 8 | ROAM_GRAPH_SELECTION_URL, 9 | } from "./const"; 10 | 11 | type STAGE = 12 | | "INIT" 13 | | "LOGGED_OUT" 14 | | "LOADING" 15 | | "GRAPH_SELECT" 16 | | "GRAPH_LOADED" 17 | | "EXPORT_POPUP" 18 | | "EXPORT_IN_PROGRESS" 19 | | "UNHANDLED"; 20 | 21 | /* 22 | Since Roam can be a bit erratic, we always check what stage we're in before doing operation 23 | */ 24 | export const detectStage = async (page: Page): Promise => { 25 | try { 26 | const pageUrl = page.url(); 27 | 28 | const isLoginPage = pageUrl === ROAM_LOGIN_URL; 29 | 30 | if (isLoginPage) { 31 | console.log("stage: LOGGED_OUT"); 32 | return "LOGGED_OUT"; 33 | } 34 | 35 | const isExportDialogVisible = await page.$(SELECTOR_EXPORT_DIALOG); 36 | const isExportSpinnerVisible = await page.$(SELECTOR_EXPORT_SPINNER); 37 | 38 | if (isExportDialogVisible && isExportSpinnerVisible) { 39 | console.log("stage: EXPORT_IN_PROGRESS"); 40 | return "EXPORT_IN_PROGRESS"; 41 | } 42 | 43 | if (isExportDialogVisible && !isExportSpinnerVisible) { 44 | console.log("stage: EXPORT_POPUP"); 45 | return "EXPORT_POPUP"; 46 | } 47 | 48 | const isAppWrapperVisible = await page.$(SELECTOR_ROAM_APP_WRAPPER); 49 | const isLoadingIndicatorVisible = await page.$(SELECTOR_LOADING_INDICATOR); 50 | 51 | if (isAppWrapperVisible && !isLoadingIndicatorVisible) { 52 | console.log("stage: GRAPH_LOADED"); 53 | return "GRAPH_LOADED"; 54 | } 55 | 56 | if (isLoadingIndicatorVisible && !isAppWrapperVisible) { 57 | console.log("stage: LOADING"); 58 | return "LOADING"; 59 | } 60 | 61 | const isGraphSelectionPage = pageUrl === ROAM_GRAPH_SELECTION_URL; 62 | if (isGraphSelectionPage) { 63 | console.log("stage: GRAPH_SELECT"); 64 | return "GRAPH_SELECT"; 65 | } 66 | } catch { 67 | console.log("An error occured during detection"); 68 | } 69 | 70 | console.log("stage: UNHANDLED"); 71 | return "UNHANDLED"; 72 | }; 73 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ES2017", 5 | "module": "commonjs", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "rootDir": "src", 9 | "outDir": "dist" 10 | } 11 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@nodelib/fs.scandir@2.1.3": 6 | version "2.1.3" 7 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" 8 | integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== 9 | dependencies: 10 | "@nodelib/fs.stat" "2.0.3" 11 | run-parallel "^1.1.9" 12 | 13 | "@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": 14 | version "2.0.3" 15 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" 16 | integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== 17 | 18 | "@nodelib/fs.walk@^1.2.3": 19 | version "1.2.4" 20 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" 21 | integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== 22 | dependencies: 23 | "@nodelib/fs.scandir" "2.1.3" 24 | fastq "^1.6.0" 25 | 26 | "@types/fs-extra@^9.0.2": 27 | version "9.0.2" 28 | resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.2.tgz#e1e1b578c48e8d08ae7fc36e552b94c6f4621609" 29 | integrity sha512-jp0RI6xfZpi5JL8v7WQwpBEQTq63RqW2kxwTZt+m27LcJqQdPVU1yGnT1ZI4EtCDynQQJtIGyQahkiCGCS7e+A== 30 | dependencies: 31 | "@types/node" "*" 32 | 33 | "@types/glob@^7.1.3": 34 | version "7.1.3" 35 | resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" 36 | integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== 37 | dependencies: 38 | "@types/minimatch" "*" 39 | "@types/node" "*" 40 | 41 | "@types/minimatch@*": 42 | version "3.0.3" 43 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" 44 | integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== 45 | 46 | "@types/node-fetch@^2.5.7": 47 | version "2.5.7" 48 | resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" 49 | integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== 50 | dependencies: 51 | "@types/node" "*" 52 | form-data "^3.0.0" 53 | 54 | "@types/node@*", "@types/node@^14.14.2": 55 | version "14.14.2" 56 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.2.tgz#d25295f9e4ca5989a2c610754dc02a9721235eeb" 57 | integrity sha512-jeYJU2kl7hL9U5xuI/BhKPZ4vqGM/OmK6whiFAXVhlstzZhVamWhDSmHyGLIp+RVyuF9/d0dqr2P85aFj4BvJg== 58 | 59 | "@types/prettier@^2.1.5": 60 | version "2.1.5" 61 | resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00" 62 | integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ== 63 | 64 | "@types/yargs-parser@*": 65 | version "15.0.0" 66 | resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" 67 | integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== 68 | 69 | "@types/yargs@^15.0.9": 70 | version "15.0.9" 71 | resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.9.tgz#524cd7998fe810cdb02f26101b699cccd156ff19" 72 | integrity sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g== 73 | dependencies: 74 | "@types/yargs-parser" "*" 75 | 76 | "@types/yauzl@^2.9.1": 77 | version "2.9.1" 78 | resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" 79 | integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== 80 | dependencies: 81 | "@types/node" "*" 82 | 83 | agent-base@6: 84 | version "6.0.2" 85 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 86 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 87 | dependencies: 88 | debug "4" 89 | 90 | aggregate-error@^3.0.0: 91 | version "3.1.0" 92 | resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" 93 | integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== 94 | dependencies: 95 | clean-stack "^2.0.0" 96 | indent-string "^4.0.0" 97 | 98 | ansi-regex@^5.0.0: 99 | version "5.0.0" 100 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 101 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 102 | 103 | ansi-styles@^4.0.0: 104 | version "4.3.0" 105 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 106 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 107 | dependencies: 108 | color-convert "^2.0.1" 109 | 110 | arg@^4.1.0: 111 | version "4.1.3" 112 | resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" 113 | integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== 114 | 115 | array-union@^2.1.0: 116 | version "2.1.0" 117 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 118 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 119 | 120 | asynckit@^0.4.0: 121 | version "0.4.0" 122 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 123 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 124 | 125 | at-least-node@^1.0.0: 126 | version "1.0.0" 127 | resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" 128 | integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== 129 | 130 | balanced-match@^1.0.0: 131 | version "1.0.0" 132 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 133 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 134 | 135 | brace-expansion@^1.1.7: 136 | version "1.1.11" 137 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 138 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 139 | dependencies: 140 | balanced-match "^1.0.0" 141 | concat-map "0.0.1" 142 | 143 | braces@^3.0.1: 144 | version "3.0.2" 145 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 146 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 147 | dependencies: 148 | fill-range "^7.0.1" 149 | 150 | buffer-crc32@~0.2.3: 151 | version "0.2.13" 152 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 153 | integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= 154 | 155 | buffer-from@^1.0.0: 156 | version "1.1.1" 157 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 158 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 159 | 160 | clean-stack@^2.0.0: 161 | version "2.2.0" 162 | resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" 163 | integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== 164 | 165 | cliui@^7.0.2: 166 | version "7.0.3" 167 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.3.tgz#ef180f26c8d9bff3927ee52428bfec2090427981" 168 | integrity sha512-Gj3QHTkVMPKqwP3f7B4KPkBZRMR9r4rfi5bXFpg1a+Svvj8l7q5CnkBkVQzfxT5DFSsGk2+PascOgL0JYkL2kw== 169 | dependencies: 170 | string-width "^4.2.0" 171 | strip-ansi "^6.0.0" 172 | wrap-ansi "^7.0.0" 173 | 174 | color-convert@^2.0.1: 175 | version "2.0.1" 176 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 177 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 178 | dependencies: 179 | color-name "~1.1.4" 180 | 181 | color-name@~1.1.4: 182 | version "1.1.4" 183 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 184 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 185 | 186 | combined-stream@^1.0.8: 187 | version "1.0.8" 188 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 189 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 190 | dependencies: 191 | delayed-stream "~1.0.0" 192 | 193 | concat-map@0.0.1: 194 | version "0.0.1" 195 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 196 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 197 | 198 | debug@4, debug@^4.1.1: 199 | version "4.2.0" 200 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" 201 | integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== 202 | dependencies: 203 | ms "2.1.2" 204 | 205 | del@^6.0.0: 206 | version "6.0.0" 207 | resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" 208 | integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== 209 | dependencies: 210 | globby "^11.0.1" 211 | graceful-fs "^4.2.4" 212 | is-glob "^4.0.1" 213 | is-path-cwd "^2.2.0" 214 | is-path-inside "^3.0.2" 215 | p-map "^4.0.0" 216 | rimraf "^3.0.2" 217 | slash "^3.0.0" 218 | 219 | delayed-stream@~1.0.0: 220 | version "1.0.0" 221 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 222 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 223 | 224 | diff@^4.0.1: 225 | version "4.0.2" 226 | resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" 227 | integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== 228 | 229 | dir-glob@^3.0.1: 230 | version "3.0.1" 231 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 232 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 233 | dependencies: 234 | path-type "^4.0.0" 235 | 236 | emoji-regex@^8.0.0: 237 | version "8.0.0" 238 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 239 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 240 | 241 | end-of-stream@^1.1.0: 242 | version "1.4.4" 243 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 244 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 245 | dependencies: 246 | once "^1.4.0" 247 | 248 | escalade@^3.1.1: 249 | version "3.1.1" 250 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 251 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 252 | 253 | extract-zip@^2.0.1: 254 | version "2.0.1" 255 | resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" 256 | integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== 257 | dependencies: 258 | debug "^4.1.1" 259 | get-stream "^5.1.0" 260 | yauzl "^2.10.0" 261 | optionalDependencies: 262 | "@types/yauzl" "^2.9.1" 263 | 264 | fast-glob@^3.1.1: 265 | version "3.2.4" 266 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" 267 | integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== 268 | dependencies: 269 | "@nodelib/fs.stat" "^2.0.2" 270 | "@nodelib/fs.walk" "^1.2.3" 271 | glob-parent "^5.1.0" 272 | merge2 "^1.3.0" 273 | micromatch "^4.0.2" 274 | picomatch "^2.2.1" 275 | 276 | fastq@^1.6.0: 277 | version "1.8.0" 278 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" 279 | integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== 280 | dependencies: 281 | reusify "^1.0.4" 282 | 283 | fd-slicer@~1.1.0: 284 | version "1.1.0" 285 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 286 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 287 | dependencies: 288 | pend "~1.2.0" 289 | 290 | fill-range@^7.0.1: 291 | version "7.0.1" 292 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 293 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 294 | dependencies: 295 | to-regex-range "^5.0.1" 296 | 297 | form-data@^3.0.0: 298 | version "3.0.0" 299 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" 300 | integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== 301 | dependencies: 302 | asynckit "^0.4.0" 303 | combined-stream "^1.0.8" 304 | mime-types "^2.1.12" 305 | 306 | fs-extra@^9.0.1: 307 | version "9.0.1" 308 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" 309 | integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== 310 | dependencies: 311 | at-least-node "^1.0.0" 312 | graceful-fs "^4.2.0" 313 | jsonfile "^6.0.1" 314 | universalify "^1.0.0" 315 | 316 | fs.realpath@^1.0.0: 317 | version "1.0.0" 318 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 319 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 320 | 321 | get-caller-file@^2.0.5: 322 | version "2.0.5" 323 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 324 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 325 | 326 | get-stream@^5.1.0: 327 | version "5.2.0" 328 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 329 | integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 330 | dependencies: 331 | pump "^3.0.0" 332 | 333 | glob-parent@^5.1.0: 334 | version "5.1.1" 335 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" 336 | integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== 337 | dependencies: 338 | is-glob "^4.0.1" 339 | 340 | glob@^7.1.3, glob@^7.1.6: 341 | version "7.1.6" 342 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 343 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 344 | dependencies: 345 | fs.realpath "^1.0.0" 346 | inflight "^1.0.4" 347 | inherits "2" 348 | minimatch "^3.0.4" 349 | once "^1.3.0" 350 | path-is-absolute "^1.0.0" 351 | 352 | globby@^11.0.1: 353 | version "11.0.1" 354 | resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" 355 | integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== 356 | dependencies: 357 | array-union "^2.1.0" 358 | dir-glob "^3.0.1" 359 | fast-glob "^3.1.1" 360 | ignore "^5.1.4" 361 | merge2 "^1.3.0" 362 | slash "^3.0.0" 363 | 364 | graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: 365 | version "4.2.4" 366 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 367 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== 368 | 369 | https-proxy-agent@^5.0.0: 370 | version "5.0.0" 371 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" 372 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== 373 | dependencies: 374 | agent-base "6" 375 | debug "4" 376 | 377 | ignore@^5.1.4: 378 | version "5.1.8" 379 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" 380 | integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== 381 | 382 | indent-string@^4.0.0: 383 | version "4.0.0" 384 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" 385 | integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== 386 | 387 | inflight@^1.0.4: 388 | version "1.0.6" 389 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 390 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 391 | dependencies: 392 | once "^1.3.0" 393 | wrappy "1" 394 | 395 | inherits@2: 396 | version "2.0.4" 397 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 398 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 399 | 400 | is-extglob@^2.1.1: 401 | version "2.1.1" 402 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 403 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 404 | 405 | is-fullwidth-code-point@^3.0.0: 406 | version "3.0.0" 407 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 408 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 409 | 410 | is-glob@^4.0.1: 411 | version "4.0.1" 412 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 413 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 414 | dependencies: 415 | is-extglob "^2.1.1" 416 | 417 | is-number@^7.0.0: 418 | version "7.0.0" 419 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 420 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 421 | 422 | is-path-cwd@^2.2.0: 423 | version "2.2.0" 424 | resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" 425 | integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== 426 | 427 | is-path-inside@^3.0.2: 428 | version "3.0.2" 429 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" 430 | integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== 431 | 432 | jpeg-js@^0.4.2: 433 | version "0.4.2" 434 | resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" 435 | integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== 436 | 437 | jsonfile@^6.0.1: 438 | version "6.0.1" 439 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" 440 | integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg== 441 | dependencies: 442 | universalify "^1.0.0" 443 | optionalDependencies: 444 | graceful-fs "^4.1.6" 445 | 446 | make-error@^1.1.1: 447 | version "1.3.6" 448 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 449 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 450 | 451 | merge2@^1.3.0: 452 | version "1.4.1" 453 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 454 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 455 | 456 | micromatch@^4.0.2: 457 | version "4.0.2" 458 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" 459 | integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== 460 | dependencies: 461 | braces "^3.0.1" 462 | picomatch "^2.0.5" 463 | 464 | mime-db@1.44.0: 465 | version "1.44.0" 466 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 467 | integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== 468 | 469 | mime-types@^2.1.12: 470 | version "2.1.27" 471 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 472 | integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== 473 | dependencies: 474 | mime-db "1.44.0" 475 | 476 | mime@^2.4.6: 477 | version "2.4.6" 478 | resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" 479 | integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== 480 | 481 | minimatch@^3.0.4: 482 | version "3.0.4" 483 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 484 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 485 | dependencies: 486 | brace-expansion "^1.1.7" 487 | 488 | ms@2.1.2: 489 | version "2.1.2" 490 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 491 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 492 | 493 | node-fetch@^2.6.1: 494 | version "2.6.1" 495 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 496 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== 497 | 498 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 499 | version "1.4.0" 500 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 501 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 502 | dependencies: 503 | wrappy "1" 504 | 505 | p-map@^4.0.0: 506 | version "4.0.0" 507 | resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" 508 | integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== 509 | dependencies: 510 | aggregate-error "^3.0.0" 511 | 512 | path-is-absolute@^1.0.0: 513 | version "1.0.1" 514 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 515 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 516 | 517 | path-type@^4.0.0: 518 | version "4.0.0" 519 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 520 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 521 | 522 | pend@~1.2.0: 523 | version "1.2.0" 524 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 525 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 526 | 527 | picomatch@^2.0.5, picomatch@^2.2.1: 528 | version "2.2.2" 529 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 530 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 531 | 532 | playwright@^1.5.1: 533 | version "1.5.1" 534 | resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.5.1.tgz#f5333cbfefaf9cb7213b0c9ca8bc9f3f18efcf16" 535 | integrity sha512-FYxrQ2TAmvp5ZnBnc6EJHc1Vt3DmXx8xVDq6rxVVG4YVoNKHYMarHI92zGyDlueN2kKCavrhk4Et9w6jJ5XWaA== 536 | dependencies: 537 | debug "^4.1.1" 538 | extract-zip "^2.0.1" 539 | https-proxy-agent "^5.0.0" 540 | jpeg-js "^0.4.2" 541 | mime "^2.4.6" 542 | pngjs "^5.0.0" 543 | progress "^2.0.3" 544 | proper-lockfile "^4.1.1" 545 | proxy-from-env "^1.1.0" 546 | rimraf "^3.0.2" 547 | ws "^7.3.1" 548 | 549 | pngjs@^5.0.0: 550 | version "5.0.0" 551 | resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" 552 | integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== 553 | 554 | prettier@^2.1.2: 555 | version "2.1.2" 556 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" 557 | integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== 558 | 559 | progress@^2.0.3: 560 | version "2.0.3" 561 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 562 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 563 | 564 | proper-lockfile@^4.1.1: 565 | version "4.1.1" 566 | resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c" 567 | integrity sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg== 568 | dependencies: 569 | graceful-fs "^4.1.11" 570 | retry "^0.12.0" 571 | signal-exit "^3.0.2" 572 | 573 | proxy-from-env@^1.1.0: 574 | version "1.1.0" 575 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 576 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 577 | 578 | pump@^3.0.0: 579 | version "3.0.0" 580 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 581 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 582 | dependencies: 583 | end-of-stream "^1.1.0" 584 | once "^1.3.1" 585 | 586 | require-directory@^2.1.1: 587 | version "2.1.1" 588 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 589 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 590 | 591 | retry@^0.12.0: 592 | version "0.12.0" 593 | resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" 594 | integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= 595 | 596 | reusify@^1.0.4: 597 | version "1.0.4" 598 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 599 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 600 | 601 | rimraf@^3.0.2: 602 | version "3.0.2" 603 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 604 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 605 | dependencies: 606 | glob "^7.1.3" 607 | 608 | run-parallel@^1.1.9: 609 | version "1.1.9" 610 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" 611 | integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== 612 | 613 | signal-exit@^3.0.2: 614 | version "3.0.3" 615 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" 616 | integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== 617 | 618 | slash@^3.0.0: 619 | version "3.0.0" 620 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 621 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 622 | 623 | source-map-support@^0.5.17: 624 | version "0.5.19" 625 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" 626 | integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== 627 | dependencies: 628 | buffer-from "^1.0.0" 629 | source-map "^0.6.0" 630 | 631 | source-map@^0.6.0: 632 | version "0.6.1" 633 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 634 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 635 | 636 | string-replace-async@^2.0.0: 637 | version "2.0.0" 638 | resolved "https://registry.yarnpkg.com/string-replace-async/-/string-replace-async-2.0.0.tgz#c3224e89d1216d6116e6e229cf74223cd84a733b" 639 | integrity sha512-AHMupZscUiDh07F1QziX7PLoB1DQ/pzu19vc8Xa8LwZcgnOXaw7yCgBuSYrxVEfaM2d8scc3Gtp+i+QJZV+spw== 640 | 641 | string-width@^4.1.0, string-width@^4.2.0: 642 | version "4.2.0" 643 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" 644 | integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== 645 | dependencies: 646 | emoji-regex "^8.0.0" 647 | is-fullwidth-code-point "^3.0.0" 648 | strip-ansi "^6.0.0" 649 | 650 | strip-ansi@^6.0.0: 651 | version "6.0.0" 652 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 653 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 654 | dependencies: 655 | ansi-regex "^5.0.0" 656 | 657 | to-regex-range@^5.0.1: 658 | version "5.0.1" 659 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 660 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 661 | dependencies: 662 | is-number "^7.0.0" 663 | 664 | ts-node@^9.0.0: 665 | version "9.0.0" 666 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.0.0.tgz#e7699d2a110cc8c0d3b831715e417688683460b3" 667 | integrity sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg== 668 | dependencies: 669 | arg "^4.1.0" 670 | diff "^4.0.1" 671 | make-error "^1.1.1" 672 | source-map-support "^0.5.17" 673 | yn "3.1.1" 674 | 675 | typescript@^4.0.3: 676 | version "4.0.3" 677 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" 678 | integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== 679 | 680 | universalify@^1.0.0: 681 | version "1.0.0" 682 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" 683 | integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== 684 | 685 | wrap-ansi@^7.0.0: 686 | version "7.0.0" 687 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 688 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 689 | dependencies: 690 | ansi-styles "^4.0.0" 691 | string-width "^4.1.0" 692 | strip-ansi "^6.0.0" 693 | 694 | wrappy@1: 695 | version "1.0.2" 696 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 697 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 698 | 699 | ws@^7.3.1: 700 | version "7.3.1" 701 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" 702 | integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== 703 | 704 | y18n@^5.0.2: 705 | version "5.0.4" 706 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.4.tgz#0ab2db89dd5873b5ec4682d8e703e833373ea897" 707 | integrity sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ== 708 | 709 | yargs-parser@^20.2.2: 710 | version "20.2.3" 711 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26" 712 | integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww== 713 | 714 | yargs@^16.1.0: 715 | version "16.1.0" 716 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.1.0.tgz#fc333fe4791660eace5a894b39d42f851cd48f2a" 717 | integrity sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g== 718 | dependencies: 719 | cliui "^7.0.2" 720 | escalade "^3.1.1" 721 | get-caller-file "^2.0.5" 722 | require-directory "^2.1.1" 723 | string-width "^4.2.0" 724 | y18n "^5.0.2" 725 | yargs-parser "^20.2.2" 726 | 727 | yauzl@^2.10.0: 728 | version "2.10.0" 729 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 730 | integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= 731 | dependencies: 732 | buffer-crc32 "~0.2.3" 733 | fd-slicer "~1.1.0" 734 | 735 | yn@3.1.1: 736 | version "3.1.1" 737 | resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 738 | integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 739 | --------------------------------------------------------------------------------