From 91df7629847df3cec1fbe907d3678c738e4afe82 Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Tue, 28 Mar 2023 16:06:15 +0200 Subject: [PATCH] Initial commit --- .editorconfig | 13 + .github/workflows/ci.yml | 57 + .gitignore | 9 + LICENSE.md | 21 + README.md | 43 + build/posthtml-include.js | 41 + build/res/empty_index.html | 9 + build/rollup-posthtml-template.js | 31 + compose-db.js | 507 +++++++ package-lock.json | 1161 +++++++++++++++++ package.json | 27 + rollup.config.js | 116 ++ .../index/components/IndexDescription.js | 85 ++ src/paths/index/components/IndexHeader.js | 116 ++ .../index/components/branches/BranchItem.js | 119 ++ .../index/components/branches/BranchList.js | 73 ++ .../index/components/commits/CommitItem.js | 139 ++ .../index/components/commits/CommitList.js | 122 ++ src/paths/index/entry.js | 167 +++ src/paths/index/template.html | 16 + src/shared/components/PageContent.js | 28 + src/shared/components/SharedNavigation.js | 119 ++ src/shared/partials/body_content.html | 0 src/shared/partials/head_content.html | 9 + src/shared/scripts/global.js | 155 +++ src/shared/styles/global.css | 55 + src/shared/styles/normalize.css | 349 +++++ src/static/favicon.png | Bin 0 -> 1929 bytes src/static/icons/loader.svg | 1 + 29 files changed, 3588 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 build/posthtml-include.js create mode 100644 build/res/empty_index.html create mode 100644 build/rollup-posthtml-template.js create mode 100644 compose-db.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 src/paths/index/components/IndexDescription.js create mode 100644 src/paths/index/components/IndexHeader.js create mode 100644 src/paths/index/components/branches/BranchItem.js create mode 100644 src/paths/index/components/branches/BranchList.js create mode 100644 src/paths/index/components/commits/CommitItem.js create mode 100644 src/paths/index/components/commits/CommitList.js create mode 100644 src/paths/index/entry.js create mode 100644 src/paths/index/template.html create mode 100644 src/shared/components/PageContent.js create mode 100644 src/shared/components/SharedNavigation.js create mode 100644 src/shared/partials/body_content.html create mode 100644 src/shared/partials/head_content.html create mode 100644 src/shared/scripts/global.js create mode 100644 src/shared/styles/global.css create mode 100644 src/shared/styles/normalize.css create mode 100644 src/static/favicon.png create mode 100644 src/static/icons/loader.svg diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f220a2c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{css}] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5acd019 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: Continuous integration + +on: + push: + branches: [ master ] + schedule: + # Run every hour at 10 minutes past the hour mark. + # The slight offset is there to try and avoid the high load times. + - cron: '10 * * * *' + +# Make sure jobs cannot overlap (e.g. one from push and one from schedule). +concurrency: + group: pages-ci + cancel-in-progress: true + +jobs: + build: + name: Build and deploy to GitHub Pages + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build the static content using npm + run: npm run build + + - name: Fetch artifact data (master) + run: npm run compose-db -- branch:master + env: + GRAPHQL_TOKEN: ${{ secrets.GRAPHQL_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Archive production artifacts + uses: actions/upload-artifact@v3 + with: + name: web-static + path: out + + - name: Deploy to GitHub Pages 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: out + # Configure the commit author. + git-config-name: 'Godot Organization' + git-config-email: '<>' + # Don't keep the history. + single-commit: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a70d9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Project folders. +node_modules/ +out/ +temp/ +logs/ + +# Development environments. +.idea/ +.vscode/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cf60308 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright © 2023-present Godot Engine contributors + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..85de139 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Godot Commit Artifacts + +This project is provided for Godot Engine users and contributors to +easily and reliably get links to the CI build artifacts for the main +development branches. While these artifacts are not suitable for +production use, they can be used for testing and early feature +adoption. + +Live website: https://godotengine.github.io/godot-commit-artifacts/ + +## Contributing + +This project is written in JavaScript and is built using Node.JS. HTML and CSS are +used for the presentation. The end result of the build process is completely static +and can be server from any web server, no Node.JS required. + +Front-end is designed in a reactive manner using industry standard Web Components +(powered by `lit-element`). This provides native browser support, and results in a +small overhead from the build process. + +To build the project locally you need to have Node.JS installed (12.x and newer +should work just fine). + +This project uses GitHub's GraphQL API. To fetch live data you need to generate +a [personal OAuth token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). +You can supply your token to the scripts using the `GRAPHQL_TOKEN` environment +variable. Note, that if you don't have member access to the organization, you +may not be able to access all the information used when generating the database. + +1. Clone or download the project. +2. From the project root run `npm install` or `yarn` to install dependencies. +3. Run `npm run build` or `yarn run build` to build the pages. +4. Run `npm run compose-db` or `yarn run compose-db` to fetch the data from GitHub. +5. Serve the `out/` folder with your method of choice (e.g. using Python 3: + `python -m http.server 8080 -d ./out`). + +`rollup` is used for browser packing of scripts and copying of static assets. The +data fetching script is plain JavaScript with `node-fetch` used to polyfill +`fetch()`-like API. + +## License + +This project is provided under the [MIT License](LICENSE.md). diff --git a/build/posthtml-include.js b/build/posthtml-include.js new file mode 100644 index 0000000..7225c9e --- /dev/null +++ b/build/posthtml-include.js @@ -0,0 +1,41 @@ +import fs from "fs"; +import path from "path"; + +import parser from "posthtml-parser"; + +export default function(options) { + options = options || {}; + options.root = options.root || './'; + options.encoding = options.encoding || 'utf-8'; + + return function posthtmlInclude(tree) { + tree.match({ tag: 'include' }, function(node) { + if (!node.attrs.src) { + return { + tag: false, + content: null + }; + } + + const src = path.resolve(options.root, node.attrs.src); + const source = fs.readFileSync(src, options.encoding); + const subtree = parser(source); + subtree.match = tree.match; + const content = source.indexOf('include') !== -1? posthtmlInclude(subtree): subtree; + + if (tree.messages) { + tree.messages.push({ + type: "dependency", + file: src + }); + } + + return { + tag: false, + content: content + }; + }); + + return tree; + }; +}; \ No newline at end of file diff --git a/build/res/empty_index.html b/build/res/empty_index.html new file mode 100644 index 0000000..fd678f8 --- /dev/null +++ b/build/res/empty_index.html @@ -0,0 +1,9 @@ + + + + + Nothing to see here + + + + \ No newline at end of file diff --git a/build/rollup-posthtml-template.js b/build/rollup-posthtml-template.js new file mode 100644 index 0000000..f9cefc5 --- /dev/null +++ b/build/rollup-posthtml-template.js @@ -0,0 +1,31 @@ +import { promises as fs } from 'fs'; + +import posthtml from 'posthtml'; +import include from './posthtml-include'; +import { green } from 'colorette'; + +export default function(options = {}) { + return { + name: 'posthtml', + buildEnd: async () => { + if (!options.src || !options.dest) { + return; + } + const html = await fs.readFile(options.src, { encoding: 'utf-8' }); + + const plugins = [ + include({ + root: './src' + }) + ]; + const result = await posthtml(plugins).process(html); + + try { + await fs.unlink(options.dest); + } catch (exc) { } + + await fs.writeFile(options.dest, result.html, { encoding: 'utf-8' }); + console.log(green(`written html template ${options.dest}`)) + } + }; +} \ No newline at end of file diff --git a/compose-db.js b/compose-db.js new file mode 100644 index 0000000..1274d83 --- /dev/null +++ b/compose-db.js @@ -0,0 +1,507 @@ +const fs = require('fs').promises; +const fsConstants = require('fs').constants; +const fetch = require('node-fetch'); + +const ExitCodes = { + "RequestFailure": 1, + "ParseFailure": 2, + "ExecFailure": 3, + "IOFailure": 4, +}; + +const LogFormat = { + "Raw": 0, + "JSON": 1, +}; + +const API_DELAY_MSEC = 1500; +const API_MAX_RETRIES = 5; +const API_RATE_LIMIT = ` + rateLimit { + limit + cost + nodeCount + remaining + resetAt + } +`; + +class DataFetcher { + constructor(data_owner, data_repo) { + this.data_owner = data_owner; + this.data_repo = data_repo; + + this.repo_ssh_path = `git@github.com:${data_owner}/${data_repo}.git`; + this.api_rest_path = `https://api.github.com/repos/${data_owner}/${data_repo}`; + this.api_repository_id = `owner:"${data_owner}" name:"${data_repo}"`; + } + + async _logResponse(data, name, format = LogFormat.JSON) { + try { + await ensureDir("./logs"); + + let filename = `./logs/${name}`; + let fileContent = "" + data; + + if (format === LogFormat.JSON) { + filename = `./logs/${name}.json`; + fileContent = JSON.stringify(data, null, 4); + } + + await fs.writeFile(filename, fileContent, {encoding: "utf-8"}); + } catch (err) { + console.error(" Error saving log file: " + err); + } + } + + _handleResponseErrors(queryID, res) { + console.warn(` Failed to get data from '${queryID}'; server responded with ${res.status} ${res.statusText}`); + const retry_header = res.headers.get("Retry-After"); + if (retry_header) { + console.log(` Retry after: ${retry_header}`); + } + } + + _handleDataErrors(data) { + if (typeof data["errors"] === "undefined") { + return; + } + + console.warn(` Server handled the request, but there were errors:`); + data.errors.forEach((item) => { + console.log(` [${item.type}] ${item.message}`); + }); + } + + async delay(msec) { + return new Promise(resolve => setTimeout(resolve, msec)); + } + + async fetchGithub(query, retries = 0) { + const init = {}; + init.method = "POST"; + init.headers = {}; + init.headers["Content-Type"] = "application/json"; + if (process.env.GRAPHQL_TOKEN) { + init.headers["Authorization"] = `token ${process.env.GRAPHQL_TOKEN}`; + } else if (process.env.GITHUB_TOKEN) { + init.headers["Authorization"] = `token ${process.env.GITHUB_TOKEN}`; + } + + init.body = JSON.stringify({ + query, + }); + + let res = await fetch("https://api.github.com/graphql", init); + let attempt = 0; + while (res.status !== 200 && attempt < retries) { + attempt += 1; + console.log(` Failed with status ${res.status}, retrying (${attempt}/${retries})...`); + + // GitHub API is flaky, so we add an extra delay to let it calm down a bit. + await this.delay(API_DELAY_MSEC); + res = await fetch("https://api.github.com/graphql", init); + } + + return res; + } + + async fetchGithubRest(query) { + const init = {}; + init.method = "GET"; + init.headers = {}; + init.headers["Content-Type"] = "application/json"; + if (process.env.GRAPHQL_TOKEN) { + init.headers["Authorization"] = `token ${process.env.GRAPHQL_TOKEN}`; + } else if (process.env.GITHUB_TOKEN) { + init.headers["Authorization"] = `token ${process.env.GITHUB_TOKEN}`; + } + + return await fetch(`${this.api_rest_path}${query}`, init); + } + + async checkRates() { + try { + const query = ` + query { + ${API_RATE_LIMIT} + } + `; + + const res = await this.fetchGithub(query); + if (res.status !== 200) { + this._handleResponseErrors(this.api_repository_id, res); + process.exitCode = ExitCodes.RequestFailure; + return; + } + + const data = await res.json(); + await this._logResponse(data, "_rate_limit"); + this._handleDataErrors(data); + + const rate_limit = data.data["rateLimit"]; + console.log(` [$${rate_limit.cost}][${rate_limit.nodeCount}] Available API calls: ${rate_limit.remaining}/${rate_limit.limit}; resets at ${rate_limit.resetAt}`); + } catch (err) { + console.error(" Error checking the API rate limits: " + err); + process.exitCode = ExitCodes.RequestFailure; + return; + } + } + + async fetchRuns(branchName) { + try { + const query = ` + query { + ${API_RATE_LIMIT} + + repository (${this.api_repository_id}) { + object (expression: "${branchName}") { + ... on Commit { + history(first: 10) { + edges { + node { + ...CommitData + } + } + } + } + } + } + } + + fragment CommitData on Commit { + oid + committedDate + messageHeadline + + checkSuites(first: 20) { + edges { + node { + ...CheckSuiteData + } + } + } + } + + fragment CheckSuiteData on CheckSuite { + databaseId + url + status + conclusion + createdAt + updatedAt + workflowRun { + databaseId + workflow { + databaseId + name + } + } + } + `; + + console.log(` Requesting workflow runs data for commits in "${branchName}".`); + + const res = await this.fetchGithub(query, API_MAX_RETRIES); + if (res.status !== 200) { + this._handleResponseErrors(this.api_repository_id, res); + process.exitCode = ExitCodes.RequestFailure; + return []; + } + + const data = await res.json(); + await this._logResponse(data, `data_runs_${branchName}`); + this._handleDataErrors(data); + + const repository = data.data["repository"]; + const run_data = mapNodes(repository.object["history"]); + + const rate_limit = data.data["rateLimit"]; + console.log(` [$${rate_limit.cost}][${rate_limit.nodeCount}] Retrieved ${run_data.length} commits and their runs.`); + console.log(` --`); + return run_data; + } catch (err) { + console.error(" Error fetching workflow runs data: " + err); + process.exitCode = ExitCodes.RequestFailure; + return []; + } + } + + async fetchArtifacts(runId) { + try { + const query = `/actions/runs/${runId}/artifacts`; + + const res = await this.fetchGithubRest(query); + if (res.status !== 200) { + this._handleResponseErrors(query, res); + process.exitCode = ExitCodes.RequestFailure; + return []; + } + + const data = await res.json(); + await this._logResponse(data, `data_artifacts_${runId}`); + this._handleDataErrors(data); + + const artifacts_data = data.artifacts; + + console.log(` [$0] Retrieved ${artifacts_data.length} artifacts for '${runId}'; processing...`); + + return artifacts_data; + } catch (err) { + console.error(" Error fetching artifact data: " + err); + process.exitCode = ExitCodes.RequestFailure; + return []; + } + } +} + +class DataProcessor { + constructor() { + this.commits = []; + this.checks = {}; + this.runs = {}; + this.artifacts = {}; + } + + processRuns(runsRaw) { + try { + runsRaw.forEach((item) => { + // Compile basic information about a commit. + let commit = { + "hash": item.oid, + "title": item.messageHeadline, + "committed_date": item.committedDate, + "checks": [], + }; + + const checkSuites = mapNodes(item.checkSuites); + checkSuites.forEach((checkItem) => { + // Compile basic information about a check suite. + let check = { + "check_id": checkItem.databaseId, + "check_url": checkItem.url, + "status": checkItem.status, + "conclusion": checkItem.conclusion, + + "created_at": checkItem.createdAt, + "updated_at": checkItem.updatedAt, + + "workflow": null, + }; + + if (checkItem.workflowRun) { + const runItem = checkItem.workflowRun; + let run = { + "name": runItem.workflow.name, + "workflow_id": runItem.workflow.databaseId, + "run_id": runItem.databaseId, + + "artifacts": [], + }; + + this.runs[run.run_id] = run; + check.workflow = run.run_id; + } + + this.checks[check.check_id] = check; + commit.checks.push(check.check_id); + }); + + this.commits.push(commit); + }); + } catch (err) { + console.error(" Error parsing pull request data: " + err); + process.exitCode = ExitCodes.ParseFailure; + } + } + + processArtifacts(runId, artifactsRaw) { + try { + artifactsRaw.forEach((item) => { + let artifact = { + "id": item.id, + "name": item.name, + "size": item.size_in_bytes, + + "created_at": item.created_at, + "updated_at": item.upadted_at, + "expires_at": item.expires_at, + }; + + this.runs[runId].artifacts.push(artifact); + }); + } catch (err) { + console.error(" Error parsing artifact data: " + err); + process.exitCode = ExitCodes.ParseFailure; + } + } +} + +class DataIO { + constructor() { + // Configurable parameters. + this.data_owner = "godotengine"; + this.data_repo = "godot"; + this.data_branch = ""; + } + + parseArgs() { + process.argv.forEach((arg) => { + if (arg.indexOf("owner:") === 0) { + this.data_owner = arg.substring(6); + } + if (arg.indexOf("repo:") === 0) { + this.data_repo = arg.substring(5); + } + if (arg.indexOf("branch:") === 0) { + this.data_branch = arg.substring(7); + } + }); + + if (this.data_owner === "" || this.data_repo === "" || this.data_branch === "") { + console.error(" Error reading command-line arguments: owner, repo, and branch cannot be empty."); + process.exitCode = ExitCodes.IOFailure; + return; + } + } + + async loadData() { + try { + console.log("[*] Loading existing database from a file."); + + // const dataPath = `./out/data/${this.data_owner}.${this.data_repo}.${this.data_branch}.json`; + // await fs.access(dataPath, fsConstants.R_OK); + // const existingData = await fs.readFile(dataPath); + } catch (err) { + console.error(" Error loading existing database file: " + err); + process.exitCode = ExitCodes.IOFailure; + return; + } + } + + async saveData(output, fileName) { + try { + console.log("[*] Storing database to a file."); + + await ensureDir("./out"); + await ensureDir("./out/data"); + await fs.writeFile(`./out/data/${fileName}`, JSON.stringify(output), {encoding: "utf-8"}); + } catch (err) { + console.error(" Error saving database file: " + err); + process.exitCode = ExitCodes.IOFailure; + return; + } + } +} + +function mapNodes(object) { + return object.edges.map((item) => item["node"]) +} + +async function ensureDir(dirPath) { + try { + await fs.access(dirPath, fsConstants.R_OK | fsConstants.W_OK); + } catch (err) { + await fs.mkdir(dirPath); + } +} + +async function clearDir(rootPath) { + try { + const pathStat = await fs.stat(rootPath); + if (!pathStat.isDirectory()) { + return; + } + + const removeDir = async (dirPath) => { + const dirFiles = await fs.readdir(dirPath); + for (let entryName of dirFiles) { + if (entryName === "." || entryName === "..") { + continue; + } + + const entryPath = `${dirPath}/${entryName}`; + const entryStat = await fs.stat(entryPath); + if (entryStat.isDirectory()) { + await removeDir(entryPath); + await fs.rmdir(entryPath); + } + else if (entryStat.isFile()) { + await fs.unlink(entryPath); + } + } + }; + + await removeDir(rootPath); + } catch (err) { + console.error(` Error clearing a folder at ${rootPath}: ` + err); + process.exitCode = ExitCodes.IOFailure; + return; + } +} + +async function main() { + // Internal utility methods. + const checkForExit = () => { + if (process.exitCode > 0) { + console.log(` Terminating with an exit code ${process.exitCode}.`); + process.exit(); + } + }; + + console.log("[*] Building local workflow run database."); + + const dataIO = new DataIO(); + dataIO.parseArgs(); + checkForExit(); + + // await dataIO.loadConfig(); + // checkForExit(); + + console.log(`[*] Configured for the "${dataIO.data_owner}/${dataIO.data_repo}" repository; branch ${dataIO.data_branch}.`); + + const dataFetcher = new DataFetcher(dataIO.data_owner, dataIO.data_repo); + const dataProcessor = new DataProcessor(); + + console.log("[*] Checking the rate limits before."); + await dataFetcher.checkRates(); + checkForExit(); + + console.log("[*] Fetching workflow runs data from GitHub."); + const runsRaw = await dataFetcher.fetchRuns(dataIO.data_branch); + checkForExit(); + dataProcessor.processRuns(runsRaw); + checkForExit(); + + console.log("[*] Fetching artifact data from GitHub."); + for (let runId in dataProcessor.runs) { + const artifactsRaw = await dataFetcher.fetchArtifacts(runId); + checkForExit(); + dataProcessor.processArtifacts(runId, artifactsRaw); + checkForExit(); + + // Wait for a bit before proceeding to avoid hitting the secondary rate limit in GitHub API. + // See https://docs.github.com/en/rest/guides/best-practices-for-integrators#dealing-with-secondary-rate-limits. + await dataFetcher.delay(API_DELAY_MSEC); + } + + console.log("[*] Checking the rate limits after.") + await dataFetcher.checkRates(); + checkForExit(); + + console.log("[*] Finalizing database.") + const output = { + "generated_at": Date.now(), + "commits": dataProcessor.commits, + "checks": dataProcessor.checks, + "runs": dataProcessor.runs, + "artifacts": dataProcessor.artifacts, + }; + + await dataIO.saveData(output, `${dataIO.data_owner}.${dataIO.data_repo}.${dataIO.data_branch}.json`); + checkForExit(); + + console.log("[*] Database built."); +} + +main(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1c007c3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1161 @@ +{ + "name": "godot-commit-artifacts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "godot-commit-artifacts", + "version": "1.0.0", + "dependencies": { + "@babel/core": "^7.6.4", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-proposal-decorators": "^7.6.0", + "dompurify": "^2.0.7", + "lit-element": "^2.2.1", + "marked": "^0.7.0", + "node-fetch": "^2.6.1", + "posthtml": "^0.12.0", + "rollup": "^1.24.0", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-copy": "^3.4.0", + "rollup-plugin-includepaths": "^0.2.3", + "rollup-plugin-node-resolve": "^5.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dependencies": { + "@babel/highlight": "^7.0.0" + } + }, + "node_modules/@babel/core": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz", + "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==", + "dependencies": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.4", + "@babel/helpers": "^7.6.2", + "@babel/parser": "^7.6.4", + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.3", + "@babel/types": "^7.6.3", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz", + "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==", + "dependencies": { + "@babel/types": "^7.6.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz", + "integrity": "sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng==", + "dependencies": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.5.5", + "@babel/helper-split-export-declaration": "^7.4.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dependencies": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dependencies": { + "@babel/types": "^7.5.5" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", + "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", + "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dependencies": { + "@babel/types": "^7.4.4" + } + }, + "node_modules/@babel/helpers": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", + "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", + "dependencies": { + "@babel/template": "^7.6.0", + "@babel/traverse": "^7.6.2", + "@babel/types": "^7.6.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dependencies": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz", + "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz", + "integrity": "sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.5.5", + "@babel/helper-plugin-utils": "^7.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.6.0.tgz", + "integrity": "sha512-ZSyYw9trQI50sES6YxREXKu+4b7MAg6Qx2cvyDDYjP2Hpzd3FleOUwC9cqn1+za8d0A2ZU8SHujxFao956efUg==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.6.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-decorators": "^7.2.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz", + "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", + "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz", + "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==", + "dependencies": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.6.3", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.6.3", + "@babel/types": "^7.6.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "node_modules/@babel/types": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz", + "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==", + "dependencies": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dependencies": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "node_modules/@types/fs-extra": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz", + "integrity": "sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dependencies": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, + "node_modules/@types/node": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz", + "integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==" + }, + "node_modules/@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", + "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colorette": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", + "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", + "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/dompurify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.7.tgz", + "integrity": "sha512-S3O0lk6rFJtO01ZTzMollCOGg+WAtCwS3U5E2WSDY/x/sy7q70RjEC4Dmrih5/UqzLLB9XoKJ8KqwBxaNvBu4A==" + }, + "node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz", + "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dependencies": { + "reusify": "^1.0.0" + } + }, + "node_modules/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==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dependencies": { + "isobject": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", + "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", + "dependencies": { + "@types/estree": "0.0.39" + } + }, + "node_modules/isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lit-element": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.2.1.tgz", + "integrity": "sha512-ipDcgQ1EpW6Va2Z6dWm79jYdimVepO5GL0eYkZrFvdr0OD/1N260Q9DH+K5HXHFrRoC7dOg+ZpED2XE0TgGdXw==", + "dependencies": { + "lit-html": "^1.0.0" + } + }, + "node_modules/lit-html": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.1.2.tgz", + "integrity": "sha512-FFlUMKHKi+qG1x1iHNZ1hrtc/zHmfYTyrSvs3/wBTvaNtpZjOZGWzU7efGYVpgp6KvWeKF6ql9/KsCq6Z/mEDA==" + }, + "node_modules/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "node_modules/magic-string": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", + "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, + "node_modules/marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", + "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/posthtml": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.12.0.tgz", + "integrity": "sha512-aNUEP/SfKUXAt+ghG51LC5MmafChBZeslVe/SSdfKIgLGUVRE68mrMF4V8XbH07ZifM91tCSuxY3eHIFLlecQw==", + "dependencies": { + "posthtml-parser": "^0.4.1", + "posthtml-render": "^1.1.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/posthtml-parser": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.4.1.tgz", + "integrity": "sha512-h7vXIQ21Ikz2w5wPClPakNP6mJeJCK6BT0GpqnQrNNABdR7/TchNlFyryL1Bz6Ww53YWCKkr6tdZuHlxY1AVdQ==", + "dependencies": { + "htmlparser2": "^3.9.2", + "object-assign": "^4.1.1" + } + }, + "node_modules/posthtml-render": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-1.1.5.tgz", + "integrity": "sha512-yvt54j0zCBHQVEFAuR+yHld8CZrCa/E1Z/OcFNCV1IEWTLVxT8O7nYnM4IIw1CD4r8kaRd3lc42+0lgCKgm87w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dependencies": { + "path-parse": "^1.0.6" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.24.0.tgz", + "integrity": "sha512-PiFETY/rPwodQ8TTC52Nz2DSCYUATznGh/ChnxActCr8rV5FIk3afBUb3uxNritQW/Jpbdn3kq1Rwh1HHYMwdQ==", + "dependencies": { + "@types/estree": "*", + "@types/node": "*", + "acorn": "^7.1.0" + }, + "bin": { + "rollup": "dist/bin/rollup" + } + }, + "node_modules/rollup-plugin-babel": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.3.tgz", + "integrity": "sha512-tKzWOCmIJD/6aKNz0H1GMM+lW1q9KyFubbWzGiOG540zxPPifnEAHTZwjo0g991Y+DyOZcLqBgqOdqazYE5fkw==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-babel.", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "rollup-pluginutils": "^2.8.1" + }, + "peerDependencies": { + "@babel/core": "7 || ^7.0.0-rc.2", + "rollup": ">=0.60.0 <2" + } + }, + "node_modules/rollup-plugin-commonjs": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", + "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-commonjs.", + "dependencies": { + "estree-walker": "^0.6.1", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0", + "rollup-pluginutils": "^2.8.1" + }, + "peerDependencies": { + "rollup": ">=1.12.0" + } + }, + "node_modules/rollup-plugin-copy": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz", + "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==", + "dependencies": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "engines": { + "node": ">=8.3" + } + }, + "node_modules/rollup-plugin-includepaths": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-includepaths/-/rollup-plugin-includepaths-0.2.3.tgz", + "integrity": "sha512-4QbSIZPDT+FL4SViEVCRi4cGCA64zQJu7u5qmCkO3ecHy+l9EQBsue15KfCpddfb6Br0q47V/v2+E2YUiqts9g==" + }, + "node_modules/rollup-plugin-node-resolve": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", + "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-node-resolve.", + "dependencies": { + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.11.1", + "rollup-pluginutils": "^2.8.1" + }, + "peerDependencies": { + "rollup": ">=1.11.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, + "node_modules/safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", + "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..389525c --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "godot-commit-artifacts", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "rollup -c", + "compose-db": "node ./compose-db.js" + }, + "author": "Yuri Sizov ", + "private": true, + "dependencies": { + "@babel/core": "^7.6.4", + "@babel/plugin-proposal-class-properties": "^7.5.5", + "@babel/plugin-proposal-decorators": "^7.6.0", + "dompurify": "^2.0.7", + "lit-element": "^2.2.1", + "marked": "^0.7.0", + "node-fetch": "^2.6.1", + "posthtml": "^0.12.0", + "rollup": "^1.24.0", + "rollup-plugin-babel": "^4.3.3", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-copy": "^3.4.0", + "rollup-plugin-includepaths": "^0.2.3", + "rollup-plugin-node-resolve": "^5.2.0" + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..398ebae --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,116 @@ +import { promises as fs } from 'fs'; +import path from 'path'; + +import nodeResolve from 'rollup-plugin-node-resolve'; +import includePaths from 'rollup-plugin-includepaths' +import commonjs from 'rollup-plugin-commonjs'; +import copy from 'rollup-plugin-copy'; +import posthtmlTemplate from './build/rollup-posthtml-template'; +import babel from 'rollup-plugin-babel'; + +const INPUT_ROOT = 'src'; +const INPUT_PATHS_ROOT = path.join(INPUT_ROOT, 'paths'); +const INPUT_SHARED_ROOT = path.join(INPUT_ROOT, 'shared'); +const INPUT_STATIC_ROOT = path.join(INPUT_ROOT, 'static'); + +const ENTRY_FILE_NAME = 'entry.js'; +const TEMPLATE_FILE_NAME = 'template.html'; +const GLOBAL_FILE_NAME = 'global.js'; + +const OUTPUT_ROOT = 'out'; +const OUTPUT_STYLES = path.join(OUTPUT_ROOT, 'styles'); +const OUTPUT_STYLES_SHARED = path.join(OUTPUT_STYLES, 'shared'); +const OUTPUT_SCRIPTS = path.join(OUTPUT_ROOT, 'scripts'); +const OUTPUT_SCRIPTS_SHARED = path.join(OUTPUT_SCRIPTS, 'shared'); + +const generateConfig = async () => { + let configs = []; + + getGlobalConfig(configs); + await getPathsConfigs(configs); + + return configs; +}; +const getGlobalConfig = (configs) => { + const globalScriptPath = path.join(INPUT_SHARED_ROOT, 'scripts', GLOBAL_FILE_NAME); + const outputPath = path.join(OUTPUT_SCRIPTS_SHARED, GLOBAL_FILE_NAME); + + const sharedStylesGlob = path.join(INPUT_SHARED_ROOT, 'styles/**/*.css').replace(/\\/g, '/'); // Windows path not supported by copy plugin + const staticGlob = path.join(INPUT_STATIC_ROOT, '/**/*.*').replace(/\\/g, '/'); // Windows path not supported by copy plugin + + configs.push({ + input: globalScriptPath, + output: { + name: 'global', + file: outputPath, + format: 'iife' + }, + plugins: [ + nodeResolve(), + copy({ + targets: [ + { src: sharedStylesGlob, dest: OUTPUT_STYLES_SHARED }, + { src: staticGlob, dest: OUTPUT_ROOT }, + ], + verbose: true + }) + ] + }) + +}; +const getPathsConfigs = async (configs) => { + try { + // Collect paths to process + const paths = await fs.readdir(INPUT_PATHS_ROOT); + + for (const itemPath of paths) { + const itemRoot = path.join(INPUT_PATHS_ROOT, itemPath); + const itemFiles = await fs.readdir(itemRoot); + + if (itemFiles.indexOf(ENTRY_FILE_NAME) < 0) { + throw Error(`Missing entry script for "${itemPath}" path`); + } + if (itemFiles.indexOf(TEMPLATE_FILE_NAME) < 0) { + throw Error(`Missing HTML template for "${itemPath}" path`); + } + + const entryPath = path.join(itemRoot, ENTRY_FILE_NAME); + const templatePath = path.join(itemRoot, TEMPLATE_FILE_NAME).replace(/\\/g, '/'); // Windows path not supported by copy plugin + const bundlePath = path.join(OUTPUT_ROOT, 'scripts', `${itemPath}.js`); + const htmlPath = path.join(OUTPUT_ROOT, `${itemPath}.html`); + + configs.push({ + input: entryPath, + output: { + name: itemPath, + file: bundlePath, + format: 'iife' + }, + plugins: [ + babel({ + exclude: 'node_modules/**', + plugins: [ + [ '@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true } ], + '@babel/plugin-proposal-class-properties' + ] + }), + includePaths({ + paths: [ './' ] + }), + nodeResolve(), + commonjs({ + sourceMap: false + }), + posthtmlTemplate({ + src: templatePath, + dest: htmlPath + }) + ] + }) + } + } catch (exc) { + console.error(exc); + } +}; + +export default generateConfig(); \ No newline at end of file diff --git a/src/paths/index/components/IndexDescription.js b/src/paths/index/components/IndexDescription.js new file mode 100644 index 0000000..2d7dc06 --- /dev/null +++ b/src/paths/index/components/IndexDescription.js @@ -0,0 +1,85 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +@customElement('gr-index-description') +export default class IndexDescription extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + } + @media (prefers-color-scheme: dark) { + :host { + } + } + + /** Component styling **/ + :host { + line-height: 22px; + } + + :host .header-description { + display: flex; + align-items: flex-end; + color: var(--dimmed-font-color); + } + + :host .header-description-column { + flex: 2; + } + :host .header-description-column.header-extra-links { + flex: 1; + text-align: right; + } + + :host .header-description a { + color: var(--link-font-color); + text-decoration: none; + } + :host .header-description a:hover { + color: var(--link-font-color-hover); + } + + :host hr { + border: none; + border-top: 1px solid var(--g-background-extra-color); + width: 30%; + } + + @media only screen and (max-width: 900px) { + :host .header-description { + padding: 0 8px; + flex-direction: column; + } + + :host .header-description-column { + width: 100%; + } + :host .header-description-column.header-extra-links { + text-align: center; + padding-top: 12px; + } + } + `; + } + + @property({ type: Date }) generated_at = null; + + render() { + return html` +
+
+ This page provides links to the latest CI build artifacts for + active development branches. +
+ + These builds may not be suitable for production use. +
+ Please use them for testing purposes only. +
+
+ +
+ `; + } +} diff --git a/src/paths/index/components/IndexHeader.js b/src/paths/index/components/IndexHeader.js new file mode 100644 index 0000000..e210348 --- /dev/null +++ b/src/paths/index/components/IndexHeader.js @@ -0,0 +1,116 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +@customElement('gr-index-entry') +export default class IndexHeader extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + --header-meta-color: #98a5b8; + } + @media (prefers-color-scheme: dark) { + :host { + --header-meta-color: #515c6c; + } + } + + /** Component styling **/ + :host { + } + + :host .header { + display: flex; + justify-content: space-between; + align-items: center; + } + + :host .header-metadata { + color: var(--header-meta-color); + text-align: right; + } + :host .header-metadata a { + color: var(--link-font-color); + text-decoration: none; + } + :host .header-metadata a:hover { + color: var(--link-font-color-hover); + } + + @media only screen and (max-width: 900px) { + :host .header { + flex-wrap: wrap; + text-align: center; + } + :host .header-title, + :host .header-metadata { + width: 100%; + } + :host .header-metadata { + padding-bottom: 12px; + text-align: center; + } + } + `; + } + + @property({ type: Date }) generated_at = null; + + constructor() { + super(); + + // Auto-refresh about once a minute so that the relative time of generation is always actual. + this._refreshTimeout = setTimeout(this._refresh.bind(this), 60 * 1000); + } + + _refresh() { + this.requestUpdate(); + + // Continue updating. + this._refreshTimeout = setTimeout(this._refresh.bind(this), 60 * 1000); + } + + render() { + let generatedAt = ""; + let generatedRel = ""; + + if (this.generated_at) { + generatedAt = greports.format.formatTimestamp(this.generated_at); + + let timeValue = (Date.now() - this.generated_at) / (1000 * 60); + let timeUnit = "minute"; + + if (timeValue < 1) { + generatedRel = "just now"; + } else { + if (timeValue > 60) { + timeValue = timeValue / 60; + timeUnit = "hour"; + } + + generatedRel = greports.format.formatTimespan(-Math.round(timeValue), timeUnit); + } + } + + return html` +
+

+ Godot Commit Artifacts +

+ +
+ `; + } +} diff --git a/src/paths/index/components/branches/BranchItem.js b/src/paths/index/components/branches/BranchItem.js new file mode 100644 index 0000000..a92fd87 --- /dev/null +++ b/src/paths/index/components/branches/BranchItem.js @@ -0,0 +1,119 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +@customElement('gr-branch-item') +export default class BranchItem extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + --tab-hover-background-color: rgba(0, 0, 0, 0.14); + --tab-active-background-color: #d6e6ff; + --tab-active-border-color: #397adf; + } + @media (prefers-color-scheme: dark) { + :host { + --tab-hover-background-color: rgba(255, 255, 255, 0.14); + --tab-active-background-color: #283446; + --tab-active-border-color: #5394f9; + } + } + + /** Component styling **/ + :host { + max-width: 200px; + } + + :host .branch-item { + border-left: 5px solid transparent; + color: var(--g-font-color); + cursor: pointer; + display: flex; + flex-direction: row; + gap: 6px; + padding: 6px 16px; + align-items: center; + } + :host .branch-item:hover { + background-color: var(--tab-hover-background-color); + } + :host .branch-item--active { + background-color: var(--tab-active-background-color); + border-left: 5px solid var(--tab-active-border-color); + } + + :host .branch-title { + flex-grow: 1; + font-size: 15px; + white-space: nowrap; + overflow: hidden; + } + + @keyframes loader-rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + :host .branch-loader { + background-image: url('loader.svg'); + background-size: 20px 20px; + background-position: 50% 50%; + background-repeat: no-repeat; + border-radius: 2px; + display: inline-block; + width: 20px; + height: 20px; + min-width: 20px; + animation-name: loader-rotate; + animation-duration: 1.25s; + animation-timing-function: steps(8); + animation-iteration-count: infinite; + } + + @media (prefers-color-scheme: light) { + :host .branch-loader { + filter: invert(1); + } + } + + @media only screen and (max-width: 900px) { + :host .branch-item { + padding: 10px 20px; + } + + :host .branch-title { + font-size: 18px; + } + } + `; + } + + @property({ type: String, reflect: true }) name = ""; + @property({ type: Boolean, reflect: true }) active = false; + @property({ type: Boolean, reflect: true }) loading = false; + + render(){ + const classList = [ "branch-item" ]; + if (this.active) { + classList.push("branch-item--active"); + } + + return html` +
+ + ${this.name} + + + ${(this.loading ? html` +
+ ` : null)} +
+ `; + } +} diff --git a/src/paths/index/components/branches/BranchList.js b/src/paths/index/components/branches/BranchList.js new file mode 100644 index 0000000..f1abbcd --- /dev/null +++ b/src/paths/index/components/branches/BranchList.js @@ -0,0 +1,73 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +import BranchItem from "./BranchItem"; + +@customElement('gr-branch-list') +export default class BranchList extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + --branches-background-color: #fcfcfa; + --branches-border-color: #515c6c; + } + @media (prefers-color-scheme: dark) { + :host { + --branches-background-color: #0d1117; + --branches-border-color: #515c6c; + } + } + + /** Component styling **/ + :host { + position: relative; + } + + :host .branch-list { + background-color: var(--branches-background-color); + border-right: 2px solid var(--branches-border-color); + width: 200px; + min-height: 216px; + } + + @media only screen and (max-width: 900px) { + :host { + width: 100% + } + + :host .branch-list { + width: 100% !important; + } + } + `; + } + + @property({ type: Array }) branches = []; + @property({ type: Array }) loadingBranchess = []; + @property({ type: String }) selectedBranch = ""; + + _onItemClicked(branchName) { + this.dispatchEvent(greports.util.createEvent("branchclick", { + "branch": branchName, + })); + } + + render() { + return html` +
+ ${this.branches.map((item) => { + return html` +
+ +
+ `; + })} +
+ `; + } +} diff --git a/src/paths/index/components/commits/CommitItem.js b/src/paths/index/components/commits/CommitItem.js new file mode 100644 index 0000000..c1139af --- /dev/null +++ b/src/paths/index/components/commits/CommitItem.js @@ -0,0 +1,139 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +@customElement('gr-commit-item') +export default class CommitItem extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + --item-border-color: #fcfcfa; + } + + @media (prefers-color-scheme: dark) { + :host { + --item-border-color: #0d1117; + } + } + + /** Component styling **/ + :host { + border-bottom: 3px solid var(--item-border-color); + display: block; + padding: 14px 12px 20px 12px; + } + + :host a { + color: var(--link-font-color); + text-decoration: none; + } + :host a:hover { + color: var(--link-font-color-hover); + } + + :host .item-title { + display: inline-flex; + justify-content: space-between; + font-size: 20px; + margin-top: 6px; + margin-bottom: 12px; + width: 100%; + } + + :host .item-subtitle { + color: var(--dimmed-font-color); + font-size: 16px; + line-height: 20px; + word-break: break-word; + } + + :host .item-workflows { + margin-top: 12px; + } + + :host .workflow { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + border-bottom: 2px solid var(--g-background-extra-color); + padding: 12px 10px; + } + + :host .workflow-artifacts { + display: flex; + flex-direction: column; + gap: 6px; + color: var(--dimmed-font-color); + font-size: 14px; + } + + :host .workflow-artifacts a { + font-size: 15px; + font-weight: 600; + } + + @media only screen and (max-width: 900px) { + :host { + padding: 14px 0 20px 0; + } + + :host .workflow { + grid-template-columns: 1fr; + } + } + + @media only screen and (max-width: 640px) { + :host .item-container { + padding: 0 10px; + } + } + `; + } + + @property({ type: String, reflect: true }) hash = ''; + @property({ type: String }) title = ''; + @property({ type: Array }) workflows = []; + + @property({ type: String }) repository = ''; + + render(){ + return html` +
+
+ ${greports.format.formatTimestamp(this.committed_date)} + + #${this.hash.substring(0, 9)} + +
+
${this.title}
+
+ ${this.workflows.map((item) => { + return html` +
+
${item.name}
+
+ ${item.artifacts.map((artifact) => { + return html` + + + ${artifact.name} + + (${greports.format.humanizeBytes(artifact.size)}) + + `; + })} +
+
+ `; + })} +
+
+ `; + } +} diff --git a/src/paths/index/components/commits/CommitList.js b/src/paths/index/components/commits/CommitList.js new file mode 100644 index 0000000..6bbe140 --- /dev/null +++ b/src/paths/index/components/commits/CommitList.js @@ -0,0 +1,122 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +import CommitItem from "./CommitItem"; + +@customElement('gr-commit-list') +export default class CommitList extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + --item-border-color: #fcfcfa; + --commits-background-color: #e5edf8; + } + @media (prefers-color-scheme: dark) { + :host { + --item-border-color: #0d1117; + --commits-background-color: #191d23; + } + } + + /** Component styling **/ + :host { + flex-grow: 1; + } + + :host .branch-commits { + display: flex; + flex-direction: column; + gap: 24px; + background-color: var(--commits-background-color); + border-radius: 0 4px 4px 0; + padding: 8px 12px; + max-width: 760px; + } + @media only screen and (max-width: 900px) { + :host .branch-commits { + padding: 8px; + max-width: 95%; + margin: 0px auto; + } + } + + :host .branch-commits-empty { + color: var(--g-font-color); + display: inline-block; + font-size: 20px; + line-height: 24px; + margin-top: 6px; + margin-bottom: 12px; + padding: 14px 12px; + word-break: break-word; + } + `; + } + + @property({ type: Array }) commits = []; + @property({ type: Object }) checks = {}; + @property({ type: Object }) runs = {}; + @property({ type: Object }) artifacts = {}; + + @property({ type: String }) selectedRepository = ""; + @property({ type: String }) selectedBranch = ""; + @property({ type: Boolean, reflect: true }) loading = false; + + render(){ + if (this.selectedBranch === "") { + return html``; + } + if (this.loading) { + return html` + Loading artifacts... + ` + } + + return html` +
+ ${this.commits.map((item) => { + let workflows = []; + + for (let checkId in this.checks) { + const check = this.checks[checkId]; + if (item.checks.indexOf(check.check_id) < 0) { + continue; + } + + if (check.workflow == null || typeof this.runs[check.workflow] === "undefined") { + continue; + } + + const run = this.runs[check.workflow]; + if (run.artifacts.length === 0) { + continue; + } + + workflows.push({ + "name": run.name, + "name_sanitized": run.name.replace(/([^a-zA-Z0-9_\- ]+)/g, "").trim().toLowerCase(), + "check_id": check.check_id, + "artifacts": run.artifacts, + }); + } + + workflows.sort((a,b) => { + if (a.name_sanitized > b.name_sanitized) return 1; + if (a.name_sanitized < b.name_sanitized) return -1; + return 0; + }); + + return html` + + `; + })} +
+ `; + } +} diff --git a/src/paths/index/entry.js b/src/paths/index/entry.js new file mode 100644 index 0000000..2e60704 --- /dev/null +++ b/src/paths/index/entry.js @@ -0,0 +1,167 @@ +import { LitElement, html, css, customElement, property } from 'lit-element'; + +import PageContent from 'src/shared/components/PageContent'; +import SharedNavigation from 'src/shared/components/SharedNavigation'; +import IndexHeader from "./components/IndexHeader"; +import IndexDescription from "./components/IndexDescription"; + +import BranchList from "./components/branches/BranchList"; +import CommitList from "./components/commits/CommitList"; + +@customElement('entry-component') +export default class EntryComponent extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + } + @media (prefers-color-scheme: dark) { + :host { + } + } + + /** Component styling **/ + :host { + } + + :host .branches { + display: flex; + padding: 24px 0; + } + + @media only screen and (max-width: 900px) { + :host .branches { + flex-wrap: wrap; + } + } + `; + } + + constructor() { + super(); + + this._entryRequested = false; + this._isLoading = true; + this._loadingBranches = []; + + this._branches = [ "master" ]; + this._branchData = {}; + + this._selectedRepository = "godotengine/godot"; + this._selectedBranch = ""; + + this._restoreUserPreferences(); + this._requestData(); + } + + performUpdate() { + this._requestData(); + super.performUpdate(); + } + + _restoreUserPreferences() { + const userPreferences = greports.util.getLocalPreferences(); + + // ... + } + + _saveUserPreferences() { + const currentPreferences = { + // ... + }; + + greports.util.setLocalPreferences(currentPreferences); + } + + async _requestData() { + if (this._entryRequested) { + return; + } + this._entryRequested = true; + this._isLoading = true; + + this._isLoading = false; + this.requestUpdate(); + + this._branches.forEach((branch) => { + this._requestBranchData(branch); + }); + } + + async _requestBranchData(branch) { + // Start loading, show the indicator. + this._loadingBranches.push(branch); + + const branchData = await greports.api.getBranchData(this._selectedRepository, branch); + + if (branchData) { + this._branchData[branch] = branchData; + } + + // Finish loading, hide the indicator. + const index = this._loadingBranches.indexOf(branch); + this._loadingBranches.splice(index, 1); + this.requestUpdate(); + } + + _onBranchClicked(event) { + this._selectedBranch = event.detail.branch; + this.requestUpdate(); + + window.scrollTo(0, 0); + } + + render() { + // Dereferencing to ensure it triggers an update. + const [...branches] = this._branches; + const [...loadingBranches] = this._loadingBranches; + + let commits = []; + let checks = {}; + let runs = {}; + let artifacts = {}; + + if (this._selectedBranch !== "" && typeof this._branchData[this._selectedBranch] !== "undefined") { + const branchData = this._branchData[this._selectedBranch]; + + commits = branchData.commits; + checks = branchData.checks; + runs = branchData.runs; + artifacts = branchData.artifacts; + } + + return html` + + + + + + ${(this._isLoading ? html` +

Loading...

+ ` : html` +
+ + + ${(this._selectedBranch !== "" ? html` + + ` : null)} +
+ `)} +
+ `; + } +} diff --git a/src/paths/index/template.html b/src/paths/index/template.html new file mode 100644 index 0000000..2531f3a --- /dev/null +++ b/src/paths/index/template.html @@ -0,0 +1,16 @@ + + + + + + Godot Interactive Changelog + + + + + + + + + + \ No newline at end of file diff --git a/src/shared/components/PageContent.js b/src/shared/components/PageContent.js new file mode 100644 index 0000000..a41a63a --- /dev/null +++ b/src/shared/components/PageContent.js @@ -0,0 +1,28 @@ +import { LitElement, html, css, customElement } from 'lit-element'; + +@customElement('page-content') +export default class PageContent extends LitElement { + static get styles() { + return css` + /** Component styling **/ + :host { + display: block; + margin: 0 auto; + padding: 0 12px; + max-width: 1024px; + } + + @media only screen and (max-width: 900px) { + :host { + padding: 0; + } + } + `; + } + + render(){ + return html` + + `; + } +} \ No newline at end of file diff --git a/src/shared/components/SharedNavigation.js b/src/shared/components/SharedNavigation.js new file mode 100644 index 0000000..c125378 --- /dev/null +++ b/src/shared/components/SharedNavigation.js @@ -0,0 +1,119 @@ +import { LitElement, html, css, customElement } from 'lit-element'; + +@customElement('shared-nav') +export default class SharedNavigation extends LitElement { + static get styles() { + return css` + /** Colors and variables **/ + :host { + } + @media (prefers-color-scheme: dark) { + :host { + } + } + + /** Component styling **/ + :host { + } + + :host .nav-container a { + color: var(--link-font-color); + text-decoration: none; + } + :host .nav-container a:hover { + color: var(--link-font-color-hover); + } + + :host .nav-container { + display: flex; + gap: 8px; + margin-top: 8px; + background: var(--g-background-color); + } + + :host .nav-item { + font-size: 16px; + font-weight: 600; + padding: 10px 16px; + } + :host .nav-item:hover { + background-color: var(--g-background-extra2-color); + } + + :host .nav-toggler { + display: none; + background-image: url('hamburger.svg'); + background-repeat: no-repeat; + background-position: center; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + width: 48px; + height: 48px; + } + :host .nav-toggler:hover { + background-color: var(--g-background-extra2-color); + } + + @media only screen and (max-width: 640px) { + :host .nav-container { + display: none; + flex-direction: column; + position: absolute; + top: 0; + left: 0; + right: 0; + padding-top: 40px; + padding-bottom: 12px; + } + :host .nav-container.nav-active { + display: flex; + } + + :host .nav-toggler { + display: block; + } + } + `; + } + + constructor() { + super(); + + this._mobileActive = false; + } + + _onMobileToggled() { + this._mobileActive = !this._mobileActive; + this.requestUpdate(); + } + + render(){ + const containerClassList = [ "nav-container" ]; + if (this._mobileActive) { + containerClassList.push("nav-active"); + } + + return html` + + + `; + } +} diff --git a/src/shared/partials/body_content.html b/src/shared/partials/body_content.html new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/partials/head_content.html b/src/shared/partials/head_content.html new file mode 100644 index 0000000..5d3e248 --- /dev/null +++ b/src/shared/partials/head_content.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/shared/scripts/global.js b/src/shared/scripts/global.js new file mode 100644 index 0000000..892ff0b --- /dev/null +++ b/src/shared/scripts/global.js @@ -0,0 +1,155 @@ +const LOCAL_PREFERENCE_PREFIX = "_godot_cmtar" +const LOCAL_PREFERENCE_DEFAULTS = { + +}; + +// API Interaction +const ReportsAPI = { + async get(path = '/') { + const res = await fetch(`${path}`); + if (res.status !== 200) { + return null; + } + + return await res.json(); + }, + + async getBranchData(repositoryId, branchName) { + const idBits = repositoryId.split("/"); + + return await this.get(`data/${idBits[0]}.${idBits[1]}.${branchName}.json`); + }, +}; + +// Content helpers +const ReportsFormatter = { + formatDate(dateString) { + const options = { + year: 'numeric', month: 'long', day: 'numeric', + }; + const dateFormatter = new Intl.DateTimeFormat('en-US', options); + + const date = new Date(dateString); + return dateFormatter.format(date); + }, + + formatTimestamp(timeString) { + const options = { + year: 'numeric', month: 'long', day: 'numeric', + hour: 'numeric', hour12: false, minute: 'numeric', + timeZone: 'UTC', timeZoneName: 'short', + }; + const dateFormatter = new Intl.DateTimeFormat('en-US', options); + + const date = new Date(timeString); + return dateFormatter.format(date); + }, + + formatTimespan(timeValue, timeUnit) { + const options = { + style: 'long', + }; + const timeFormatter = new Intl.RelativeTimeFormat('en-US', options); + + return timeFormatter.format(timeValue, timeUnit); + }, + + getDaysSince(dateString) { + const date = new Date(dateString); + const msBetween = (new Date()) - date; + const days = Math.floor(msBetween / (1000 * 60 * 60 * 24)); + + return days; + }, + + formatDays(days) { + return days + " " + (days !== 1 ? "days" : "day"); + }, + + humanizeBytes(bytes) { + if (bytes < 1024) { + return `${bytes} B`; + } + + bytes = bytes / 1024; + if (bytes < 1024) { + return `${Math.round(bytes, 2)} KB`; + } + + bytes = bytes / 1024; + if (bytes < 1024) { + return `${Math.round(bytes, 2)} MB`; + } + + bytes = bytes / 1024; + if (bytes < 1024) { + return `${Math.round(bytes, 2)} GB`; + } + + bytes = bytes / 1024; + return `${Math.round(bytes, 2)} TB`; + } +}; + +const ReportsUtils = { + createEvent(name, detail = {}) { + return new CustomEvent(name, { + detail: detail + }); + }, + + getHistoryHash() { + let rawHash = window.location.hash; + if (rawHash !== "") { + return rawHash.substr(1); + } + + return ""; + }, + + setHistoryHash(hash) { + const url = new URL(window.location); + url.hash = hash; + window.history.pushState({}, "", url); + }, + + navigateHistoryHash(hash) { + this.setHistoryHash(hash); + window.location.reload(); + }, + + getLocalPreferences() { + // Always fallback on defaults. + const localPreferences = { ...LOCAL_PREFERENCE_DEFAULTS }; + + for (let key in localPreferences) { + const storedValue = localStorage.getItem(`${LOCAL_PREFERENCE_PREFIX}_${key}`); + if (storedValue != null) { + localPreferences[key] = JSON.parse(storedValue); + } + } + + return localPreferences; + }, + + setLocalPreferences(currentPreferences) { + for (let key in currentPreferences) { + // Only store known properties. + if (key in LOCAL_PREFERENCE_DEFAULTS) { + localStorage.setItem(`${LOCAL_PREFERENCE_PREFIX}_${key}`, JSON.stringify(currentPreferences[key])); + } + } + }, + + resetLocalPreferences() { + this.setLocalPreferences(LOCAL_PREFERENCE_DEFAULTS); + }, +}; + +const ReportsSingleton = { + api: ReportsAPI, + format: ReportsFormatter, + util: ReportsUtils, +}; + +window.greports = ReportsSingleton; diff --git a/src/shared/styles/global.css b/src/shared/styles/global.css new file mode 100644 index 0000000..8e7d562 --- /dev/null +++ b/src/shared/styles/global.css @@ -0,0 +1,55 @@ +/** Colors and variables **/ +:root { + --g-background-color: #fcfcfa; + --g-background-extra-color: #98a5b8; + --g-background-extra2-color: #cad3e1; + --g-font-color: #121314; + --g-font-size: 15px; + --g-font-weight: 400; + --g-line-height: 20px; + + --link-font-color: #1d6dff; + --link-font-color-hover: #1051c9; + --link-font-color-inactive: #35496f; + + --dimmed-font-color: #535c5f; + --light-font-color: #6b7893; +} + +@media (prefers-color-scheme: dark) { + :root { + --g-background-color: #0d1117; + --g-background-extra-color: #515c6c; + --g-background-extra2-color: #22252b; + --g-font-color: rgba(228, 228, 232, 0.9); + + --link-font-color: #367df7; + --link-font-color-hover: #6391ec; + --link-font-color-inactive: #abbdcc; + + --dimmed-font-color: #929da0; + --light-font-color: #8491ab; + } +} + +/** General styling **/ +html {} + +body { + background: var(--g-background-color); + color: var(--g-font-color); + font-family: 'Roboto', sans-serif; + font-size: var(--g-font-size); + font-weight: var(--g-font-weight); + line-height: var(--g-line-height); + min-width: 380px; +} + +a { + color: var(--link-font-color); + font-weight: 700; + text-decoration: none; +} +a:hover { + color: var(--link-font-color-hover); +} diff --git a/src/shared/styles/normalize.css b/src/shared/styles/normalize.css new file mode 100644 index 0000000..b0c1902 --- /dev/null +++ b/src/shared/styles/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} \ No newline at end of file diff --git a/src/static/favicon.png b/src/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..ac69f11e014b8a64542ec956ea5f36e712bd26cc GIT binary patch literal 1929 zcmV;42X^?0P)a&hkDhV#IKmkkd* zcuODpiN9?SKdjUrzG%Y@SP;F3H%Y(N2;yq3Nea@M#7;abZO)s-HE4sjXa_EEa;}co z4C0rW;Pp&!4(k9tXN?-;y)Vjf{L}a0m*D$+dvJmqF*sL*#w5*ULfawMp$TGu0-jyY zI1mG|I9CHY2Z3_pz4Z6L>TCotpEfxj-emUYTnw1E+aXTgB)uaJ_THp~LnD~Cx!wpc znO7-F0Oq$Ko#F-Q9(#_1;TeBp&rM1=c%!t^_0|BvJPG1dF{3om^+tfV$g7k9tN~ii z)quTPwGrSQ_!~|w4D}BGi2T+ij%LEjRZ9S38O@{mu3f%k^aE#wg0X7&CJL}tQk84v^jWR?PUo1w&If*YmkhFZe~e3 z|Jxr@Fz?LyqL$z71JsfTS;5=rQZ`U zY*#4771WYFR5iBKg3~em*7-$eVyU{Ro$Mhtzm|qFEvhEqP2V8OC~qVHsI9G~n>TLK z`STYjB_)M`){btPb|kXTou7U*l3F{v2}ns!K@7w~Ok4bnvPOEtCrG^n>=UzX!NbDB z=*>6YqQC#`A2u`Sjbyc0F+v=R5=2^mB@u>ahkgl#b!4$vNUt|M6XUHn-=^@eZ~}n6 zVwQRd$f;~1prWFJUVrTk2ztTrNm+?#^uBPr99LqB2mmfe7uoe0yZ;dZ=gytC7Xz`G zhDrjmE1J|v0MD$e+d{zUQ>P)su8kctjsQmsRW6eFKj7J(585&?uCWf|oH=urfKH}~ zS8D`xbXf@4vuCeEVHl#o^7HX_Ir6HS2>|eH&u8U@cmlxXD9+x!`v_?7>{f3Iq?I-h z5Ey87C~V1+rDU;Mv0L;y0V8&Yk(F^QS-iwy98nYrNGYjTF9F_cuLrQu&6_*#1yLF= zE#>6oP-tc){Ck}MSa?eyM;na8X;RtX(69Yi(dr4)`qEaE`pins+l0!FH zIea#v04p~>4cT_4J86X71ZKFMD(4SQl--DdScoZ8XsMKdzSz6|A@q)apUw>`B)|;} zUy^V=#ni)*Mt4cTlp_%auBWJP=~z`z(?Y1l15?ZCQgji{0Nq=zC%4Q$71O|0=f=1F zg4|$!LOK$X zYh?$gHYvTr3cqs^1+rq#M-~u(HfReDaDfxth=Ev$31Fp{e*%_MzY$i*rsXgX<-*5YCa^+I)jLhwlz;ntC{*f&KTke#6zICg1xR z?MFj%Q9BS0Opmgw@rlygzQG-Gd{{7pGb#yf&=wrv0w=f;1F;Yjv1RyRGq_#?mnGiM z2%-mY6&Ux+ebFD_`U1=!HWDfaVCTzqhQM!feFu^t{*29~k^$8DBHsA$CtQC*rx(A@ zo20-2(D?_rk^b_231So{YfNIXD}L(u23VC}mb|&)#z(Qby25QTTGCdYf4qv$j5TwhbKM0w=f;W5E4_ z42u$P692@`7jL63XCr1B>(qRlIAM6k-|!ATUIz0Y(dK2(XAeB^z=MAS4*8~-AMHOz P00000NkvXXu0mjfyF8U* literal 0 HcmV?d00001 diff --git a/src/static/icons/loader.svg b/src/static/icons/loader.svg new file mode 100644 index 0000000..ae53992 --- /dev/null +++ b/src/static/icons/loader.svg @@ -0,0 +1 @@ +