├── .editorconfig
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE.md
├── README.md
├── build
├── posthtml-include.js
├── res
│ ├── empty_index.html
│ └── redirect_index.html
└── rollup-posthtml-template.js
├── compose-db.js
├── package-lock.json
├── package.json
├── rollup.config.js
└── src
├── paths
└── index
│ ├── components
│ ├── IndexDescription.js
│ ├── IndexHeader.js
│ ├── branches
│ │ ├── BranchItem.js
│ │ └── BranchList.js
│ └── commits
│ │ ├── CommitItem.js
│ │ ├── CommitList.js
│ │ └── LatestItem.js
│ ├── entry.js
│ └── template.html
├── shared
├── components
│ ├── PageContent.js
│ └── SharedNavigation.js
├── partials
│ ├── body_content.html
│ └── head_content.html
├── scripts
│ └── global.js
└── styles
│ ├── global.css
│ └── normalize.css
└── static
├── favicon.png
└── icons
└── loader.svg
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{css}]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Continuous integration
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | schedule:
7 | # Run every hour at 10 minutes past the hour mark.
8 | # The slight offset is there to try and avoid the high load times.
9 | - cron: '10 * * * *'
10 |
11 | # Make sure jobs cannot overlap (e.g. one from push and one from schedule).
12 | concurrency:
13 | group: pages-ci
14 | cancel-in-progress: true
15 |
16 | jobs:
17 | build:
18 | name: Build and deploy to GitHub Pages
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 |
24 | - name: Checkout published data
25 | uses: actions/checkout@v4
26 | with:
27 | ref: gh-pages
28 | path: out
29 |
30 | - name: Install Node.js 16.x
31 | uses: actions/setup-node@v4
32 | with:
33 | node-version: 16.x
34 | cache: 'npm'
35 |
36 | - name: Install dependencies
37 | run: npm ci
38 |
39 | - name: Build the static content using npm
40 | run: npm run build
41 |
42 | - name: Fetch artifact data (master)
43 | run: npm run compose-db -- branch:master
44 | env:
45 | GRAPHQL_TOKEN: ${{ secrets.GRAPHQL_TOKEN }}
46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47 |
48 | - name: Fetch artifact data (4.0)
49 | run: npm run compose-db -- branch:4.0
50 | env:
51 | GRAPHQL_TOKEN: ${{ secrets.GRAPHQL_TOKEN }}
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 |
54 | - name: Fetch artifact data (3.x)
55 | run: npm run compose-db -- branch:3.x
56 | env:
57 | GRAPHQL_TOKEN: ${{ secrets.GRAPHQL_TOKEN }}
58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 |
60 | - name: Fetch artifact data (3.5)
61 | run: npm run compose-db -- branch:3.5
62 | env:
63 | GRAPHQL_TOKEN: ${{ secrets.GRAPHQL_TOKEN }}
64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65 |
66 | - name: Archive production artifacts
67 | uses: actions/upload-artifact@v4
68 | with:
69 | name: web-static
70 | path: out
71 |
72 | - name: Deploy to GitHub Pages 🚀
73 | uses: JamesIves/github-pages-deploy-action@v4
74 | with:
75 | branch: gh-pages
76 | folder: out
77 | # Configure the commit author.
78 | git-config-name: 'Godot Organization'
79 | git-config-email: '<>'
80 | # Don't keep the history.
81 | single-commit: true
82 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project folders.
2 | node_modules/
3 | out/
4 | logs/
5 |
6 | # Development environments.
7 | .idea/
8 | .vscode/
9 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # MIT License
2 |
3 | Copyright © 2023-present Godot Engine contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Godot Commit Artifacts
2 |
3 | This project is provided for Godot Engine users and contributors to
4 | easily and reliably get links to the CI build artifacts for the main
5 | development branches. While these artifacts are not suitable for
6 | production use, they can be used for testing and early feature
7 | adoption.
8 |
9 | Live website: https://godotengine.github.io/godot-commit-artifacts/
10 |
11 | ## Contributing
12 |
13 | This project is written in JavaScript and is built using Node.JS. HTML and CSS are
14 | used for the presentation. The end result of the build process is completely static
15 | and can be server from any web server, no Node.JS required.
16 |
17 | Front-end is designed in a reactive manner using industry standard Web Components
18 | (powered by `lit-element`). This provides native browser support, and results in a
19 | small overhead from the build process.
20 |
21 | To build the project locally you need to have Node.JS installed (12.x and newer
22 | should work just fine).
23 |
24 | This project uses GitHub's GraphQL API. To fetch live data you need to generate
25 | a [personal OAuth token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).
26 | You can supply your token to the scripts using the `GRAPHQL_TOKEN` environment
27 | variable. Note, that if you don't have member access to the organization, you
28 | may not be able to access all the information used when generating the database.
29 |
30 | 1. Clone or download the project.
31 | 2. From the project root run `npm install` or `yarn` to install dependencies.
32 | 3. Run `npm run build` or `yarn run build` to build the pages.
33 | 4. Run `npm run compose-db` or `yarn run compose-db` to fetch the data from GitHub.
34 | 5. Serve the `out/` folder with your method of choice (e.g. using Python 3:
35 | `python -m http.server 8080 -d ./out`).
36 |
37 | `rollup` is used for browser packing of scripts and copying of static assets. The
38 | data fetching script is plain JavaScript with `node-fetch` used to polyfill
39 | `fetch()`-like API.
40 |
41 | ## License
42 |
43 | This project is provided under the [MIT License](LICENSE.md).
44 |
--------------------------------------------------------------------------------
/build/posthtml-include.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import path from "path";
3 |
4 | import parser from "posthtml-parser";
5 |
6 | export default function(options) {
7 | options = options || {};
8 | options.root = options.root || './';
9 | options.encoding = options.encoding || 'utf-8';
10 |
11 | return function posthtmlInclude(tree) {
12 | tree.match({ tag: 'include' }, function(node) {
13 | if (!node.attrs.src) {
14 | return {
15 | tag: false,
16 | content: null
17 | };
18 | }
19 |
20 | const src = path.resolve(options.root, node.attrs.src);
21 | const source = fs.readFileSync(src, options.encoding);
22 | const subtree = parser(source);
23 | subtree.match = tree.match;
24 | const content = source.indexOf('include') !== -1? posthtmlInclude(subtree): subtree;
25 |
26 | if (tree.messages) {
27 | tree.messages.push({
28 | type: "dependency",
29 | file: src
30 | });
31 | }
32 |
33 | return {
34 | tag: false,
35 | content: content
36 | };
37 | });
38 |
39 | return tree;
40 | };
41 | };
--------------------------------------------------------------------------------
/build/res/empty_index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Nothing to see here
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/res/redirect_index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Redirecting…
5 |
6 |
7 |
8 |
9 | Redirecting…
10 | Click here if you are not redirected.
11 |
12 |
--------------------------------------------------------------------------------
/build/rollup-posthtml-template.js:
--------------------------------------------------------------------------------
1 | import { promises as fs } from 'fs';
2 |
3 | import posthtml from 'posthtml';
4 | import include from './posthtml-include';
5 | import { green } from 'colorette';
6 |
7 | export default function(options = {}) {
8 | return {
9 | name: 'posthtml',
10 | buildEnd: async () => {
11 | if (!options.src || !options.dest) {
12 | return;
13 | }
14 | const html = await fs.readFile(options.src, { encoding: 'utf-8' });
15 |
16 | const plugins = [
17 | include({
18 | root: './src'
19 | })
20 | ];
21 | const result = await posthtml(plugins).process(html);
22 |
23 | try {
24 | await fs.unlink(options.dest);
25 | } catch (exc) { }
26 |
27 | await fs.writeFile(options.dest, result.html, { encoding: 'utf-8' });
28 | console.log(green(`written html template ${options.dest}`))
29 | }
30 | };
31 | }
--------------------------------------------------------------------------------
/compose-db.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs').promises;
2 | const fsConstants = require('fs').constants;
3 | const fetch = require('node-fetch');
4 |
5 | const ExitCodes = {
6 | "RequestFailure": 1,
7 | "ParseFailure": 2,
8 | "ExecFailure": 3,
9 | "IOFailure": 4,
10 | };
11 |
12 | const LogFormat = {
13 | "Raw": 0,
14 | "JSON": 1,
15 | };
16 |
17 | const API_DELAY_MSEC = 1500;
18 | const API_MAX_RETRIES = 5;
19 | const API_RATE_LIMIT = `
20 | rateLimit {
21 | limit
22 | cost
23 | nodeCount
24 | remaining
25 | resetAt
26 | }
27 | `;
28 |
29 | class DataFetcher {
30 | constructor(data_owner, data_repo) {
31 | this.data_owner = data_owner;
32 | this.data_repo = data_repo;
33 |
34 | this.repo_ssh_path = `git@github.com:${data_owner}/${data_repo}.git`;
35 | this.api_rest_path = `https://api.github.com/repos/${data_owner}/${data_repo}`;
36 | this.api_repository_id = `owner:"${data_owner}" name:"${data_repo}"`;
37 | }
38 |
39 | async _logResponse(data, name, format = LogFormat.JSON) {
40 | try {
41 | await ensureDir("./logs");
42 |
43 | let filename = `./logs/${name}`;
44 | let fileContent = "" + data;
45 |
46 | if (format === LogFormat.JSON) {
47 | filename = `./logs/${name}.json`;
48 | fileContent = JSON.stringify(data, null, 4);
49 | }
50 |
51 | await fs.writeFile(filename, fileContent, {encoding: "utf-8"});
52 | } catch (err) {
53 | console.error(" Error saving log file: " + err);
54 | }
55 | }
56 |
57 | _handleResponseErrors(queryID, res) {
58 | console.warn(` Failed to get data from '${queryID}'; server responded with ${res.status} ${res.statusText}`);
59 | const retry_header = res.headers.get("Retry-After");
60 | if (retry_header) {
61 | console.log(` Retry after: ${retry_header}`);
62 | }
63 | }
64 |
65 | _handleDataErrors(data) {
66 | if (typeof data["errors"] === "undefined") {
67 | return;
68 | }
69 |
70 | console.warn(` Server handled the request, but there were errors:`);
71 | data.errors.forEach((item) => {
72 | console.log(` [${item.type}] ${item.message}`);
73 | });
74 | }
75 |
76 | async delay(msec) {
77 | return new Promise(resolve => setTimeout(resolve, msec));
78 | }
79 |
80 | async fetchGithub(query, retries = 0) {
81 | const init = {};
82 | init.method = "POST";
83 | init.headers = {};
84 | init.headers["Content-Type"] = "application/json";
85 | if (process.env.GRAPHQL_TOKEN) {
86 | init.headers["Authorization"] = `token ${process.env.GRAPHQL_TOKEN}`;
87 | } else if (process.env.GITHUB_TOKEN) {
88 | init.headers["Authorization"] = `token ${process.env.GITHUB_TOKEN}`;
89 | }
90 |
91 | init.body = JSON.stringify({
92 | query,
93 | });
94 |
95 | let res = await fetch("https://api.github.com/graphql", init);
96 | let attempt = 0;
97 | while (res.status !== 200 && attempt < retries) {
98 | attempt += 1;
99 | console.log(` Failed with status ${res.status}, retrying (${attempt}/${retries})...`);
100 |
101 | // GitHub API is flaky, so we add an extra delay to let it calm down a bit.
102 | await this.delay(API_DELAY_MSEC);
103 | res = await fetch("https://api.github.com/graphql", init);
104 | }
105 |
106 | return res;
107 | }
108 |
109 | async fetchGithubRest(query) {
110 | const init = {};
111 | init.method = "GET";
112 | init.headers = {};
113 | init.headers["Content-Type"] = "application/json";
114 | if (process.env.GRAPHQL_TOKEN) {
115 | init.headers["Authorization"] = `token ${process.env.GRAPHQL_TOKEN}`;
116 | } else if (process.env.GITHUB_TOKEN) {
117 | init.headers["Authorization"] = `token ${process.env.GITHUB_TOKEN}`;
118 | }
119 |
120 | return await fetch(`${this.api_rest_path}${query}`, init);
121 | }
122 |
123 | async checkRates() {
124 | try {
125 | const query = `
126 | query {
127 | ${API_RATE_LIMIT}
128 | }
129 | `;
130 |
131 | const res = await this.fetchGithub(query);
132 | if (res.status !== 200) {
133 | this._handleResponseErrors(this.api_repository_id, res);
134 | process.exitCode = ExitCodes.RequestFailure;
135 | return;
136 | }
137 |
138 | const data = await res.json();
139 | await this._logResponse(data, "_rate_limit");
140 | this._handleDataErrors(data);
141 |
142 | const rate_limit = data.data["rateLimit"];
143 | console.log(` [$${rate_limit.cost}][${rate_limit.nodeCount}] Available API calls: ${rate_limit.remaining}/${rate_limit.limit}; resets at ${rate_limit.resetAt}`);
144 | } catch (err) {
145 | console.error(" Error checking the API rate limits: " + err);
146 | process.exitCode = ExitCodes.RequestFailure;
147 | return;
148 | }
149 | }
150 |
151 | async fetchRuns(branchName) {
152 | try {
153 | const query = `
154 | query {
155 | ${API_RATE_LIMIT}
156 |
157 | repository (${this.api_repository_id}) {
158 | object (expression: "${branchName}") {
159 | ... on Commit {
160 | history(first: 10) {
161 | edges {
162 | node {
163 | ...CommitData
164 | }
165 | }
166 | }
167 | }
168 | }
169 | }
170 | }
171 |
172 | fragment CommitData on Commit {
173 | oid
174 | committedDate
175 | messageHeadline
176 |
177 | checkSuites(first: 20) {
178 | edges {
179 | node {
180 | ...CheckSuiteData
181 | }
182 | }
183 | }
184 | }
185 |
186 | fragment CheckSuiteData on CheckSuite {
187 | databaseId
188 | url
189 | status
190 | conclusion
191 | createdAt
192 | updatedAt
193 | workflowRun {
194 | databaseId
195 | workflow {
196 | databaseId
197 | name
198 | }
199 | }
200 | }
201 | `;
202 |
203 | console.log(` Requesting workflow runs data for commits in "${branchName}".`);
204 |
205 | const res = await this.fetchGithub(query, API_MAX_RETRIES);
206 | if (res.status !== 200) {
207 | this._handleResponseErrors(this.api_repository_id, res);
208 | process.exitCode = ExitCodes.RequestFailure;
209 | return [];
210 | }
211 |
212 | const data = await res.json();
213 | await this._logResponse(data, `data_runs_${branchName}`);
214 | this._handleDataErrors(data);
215 |
216 | const repository = data.data["repository"];
217 | const run_data = mapNodes(repository.object["history"]);
218 |
219 | const rate_limit = data.data["rateLimit"];
220 | console.log(` [$${rate_limit.cost}][${rate_limit.nodeCount}] Retrieved ${run_data.length} commits and their runs.`);
221 | console.log(` --`);
222 | return run_data;
223 | } catch (err) {
224 | console.error(" Error fetching workflow runs data: " + err);
225 | process.exitCode = ExitCodes.RequestFailure;
226 | return [];
227 | }
228 | }
229 |
230 | async fetchArtifacts(runId) {
231 | try {
232 | const query = `/actions/runs/${runId}/artifacts`;
233 |
234 | const res = await this.fetchGithubRest(query);
235 | if (res.status !== 200) {
236 | this._handleResponseErrors(query, res);
237 | process.exitCode = ExitCodes.RequestFailure;
238 | return [];
239 | }
240 |
241 | const data = await res.json();
242 | await this._logResponse(data, `data_artifacts_${runId}`);
243 | this._handleDataErrors(data);
244 |
245 | const artifacts_data = data.artifacts;
246 |
247 | console.log(` [$0] Retrieved ${artifacts_data.length} artifacts for '${runId}'; processing...`);
248 |
249 | return artifacts_data;
250 | } catch (err) {
251 | console.error(" Error fetching artifact data: " + err);
252 | process.exitCode = ExitCodes.RequestFailure;
253 | return [];
254 | }
255 | }
256 | }
257 |
258 | class DataProcessor {
259 | constructor() {
260 | this.commits = [];
261 | this.checks = {};
262 | this.runs = {};
263 | }
264 |
265 | readExistingData(existingData) {
266 | if (typeof existingData.commits !== "undefined") {
267 | this.commits = existingData.commits;
268 | }
269 | if (typeof existingData.checks !== "undefined") {
270 | this.checks = existingData.checks;
271 | }
272 | if (typeof existingData.runs !== "undefined") {
273 | this.runs = existingData.runs;
274 | }
275 | }
276 |
277 | reduceData() {
278 | // The goal is to display only the most recent commits and their artifacts.
279 | // However, we can't just always fetch the last N commits and be done with
280 | // it. Fetched commits can still be in progress, and we want to have at least
281 | // some version available.
282 |
283 | // Note that artifacts expire, so it is still possible to have none. But we
284 | // should at least try.
285 |
286 | const MAX_COMMITS = 20;
287 |
288 | // Determine which commits are the latest available with ready builds.
289 | const latestArtifacts = this.getLatestArtifacts();
290 | const latestCommits = [];
291 | for (let artifactName in latestArtifacts) {
292 | const artifactCommit = latestArtifacts[artifactName].commit_hash;
293 | if (latestCommits.indexOf(artifactCommit) < 0) {
294 | latestCommits.push(artifactCommit);
295 | }
296 | }
297 |
298 | for (let i = 0; i < this.commits.length; i++) {
299 | const commit = this.commits[i];
300 | const commitIndex = latestCommits.indexOf(commit.hash);
301 | if (commitIndex >= 0) {
302 | latestCommits.splice(commitIndex, 1);
303 | }
304 |
305 | // We want to have at least MAX_COMMITS commits; and we also want to
306 | // hit every commit contributing to the latest artifacts.
307 | if (i < MAX_COMMITS || latestCommits.length > 0) {
308 | continue;
309 | }
310 |
311 | // But beyond that, cut it all out.
312 | console.log(` Removed extra commit ${commit.hash}.`);
313 |
314 | this.commits.splice(i, 1);
315 | for (let checkId of commit.checks) {
316 | const check = this.checks[checkId];
317 | delete this.checks[checkId];
318 |
319 | if (check.workflow !== "") {
320 | delete this.runs[check.workflow];
321 | }
322 | }
323 | }
324 | }
325 |
326 | processRuns(runsRaw) {
327 | try {
328 | // We will be adding items to the front, so reversing is
329 | // necessary.
330 | runsRaw.reverse();
331 |
332 | runsRaw.forEach((item) => {
333 | // Check if this commit is already tracked.
334 | let commit = this.commits.find((it) => {
335 | return it.hash === item.oid;
336 | });
337 |
338 | if (!commit) {
339 | // Compile basic information about a commit.
340 | commit = {
341 | "hash": item.oid,
342 | "title": item.messageHeadline,
343 | "committed_date": item.committedDate,
344 | "checks": [],
345 | };
346 | this.commits.unshift(commit);
347 | }
348 |
349 | const checkSuites = mapNodes(item.checkSuites);
350 | checkSuites.forEach((checkItem) => {
351 | let check = this.checks[checkItem.databaseId];
352 |
353 | if (typeof check === "undefined") {
354 | // Compile basic information about a check suite.
355 | check = {
356 | "check_id": checkItem.databaseId,
357 | "check_url": checkItem.url,
358 | "status": checkItem.status,
359 | "conclusion": checkItem.conclusion,
360 |
361 | "created_at": checkItem.createdAt,
362 | "updated_at": checkItem.updatedAt,
363 |
364 | "workflow": "",
365 | };
366 | this.checks[check.check_id] = check;
367 | } else {
368 | check.status = checkItem.status;
369 | check.conclusion = checkItem.conclusion;
370 | check.updated_at = checkItem.updatedAt;
371 | }
372 |
373 | if (check.workflow === "" && checkItem.workflowRun) {
374 | const runItem = checkItem.workflowRun;
375 | let run = {
376 | "name": runItem.workflow.name,
377 | "workflow_id": runItem.workflow.databaseId,
378 | "run_id": runItem.databaseId,
379 |
380 | "artifacts": [],
381 | };
382 |
383 | this.runs[run.run_id] = run;
384 | check.workflow = run.run_id;
385 | }
386 |
387 |
388 | // Existing data may contain this commit, but not all of
389 | // its checks.
390 | if (commit.checks.indexOf(check.check_id) < 0) {
391 | commit.checks.push(check.check_id);
392 | }
393 | });
394 | });
395 | } catch (err) {
396 | console.error(" Error parsing pull request data: " + err);
397 | process.exitCode = ExitCodes.ParseFailure;
398 | }
399 | }
400 |
401 | getIncompleteRuns() {
402 | let runs = [];
403 |
404 | for (let runId in this.runs) {
405 | const runData = this.runs[runId];
406 | if (runData.artifacts.length > 0) {
407 | continue;
408 | }
409 |
410 | runs.push(runId);
411 | }
412 |
413 | return runs;
414 | }
415 |
416 | processArtifacts(runId, artifactsRaw) {
417 | try {
418 | artifactsRaw.forEach((item) => {
419 | let artifact = {
420 | "id": item.id,
421 | "name": item.name,
422 | "size": item.size_in_bytes,
423 |
424 | "created_at": item.created_at,
425 | "updated_at": item.upadted_at,
426 | "expires_at": item.expires_at,
427 | };
428 |
429 | this.runs[runId].artifacts.push(artifact);
430 | });
431 | } catch (err) {
432 | console.error(" Error parsing artifact data: " + err);
433 | process.exitCode = ExitCodes.ParseFailure;
434 | }
435 | }
436 |
437 | getLatestArtifacts() {
438 | let latest = {};
439 |
440 | this.commits.forEach((commit) => {
441 | for (let checkId of commit.checks) {
442 | const check = this.checks[checkId];
443 | if (check.workflow === "") {
444 | continue;
445 | }
446 |
447 | const run = this.runs[check.workflow];
448 | run.artifacts.forEach((artifact) => {
449 | if (typeof latest[artifact.name] !== "undefined") {
450 | return; // Continue;
451 | }
452 |
453 | latest[artifact.name] = {
454 | "commit_hash": commit.hash,
455 | "check_id": check.check_id,
456 | "workflow_name": run.name,
457 | "artifact_id": artifact.id,
458 | "artifact_name": artifact.name,
459 | "artifact_size": artifact.size,
460 | };
461 | });
462 | }
463 | });
464 |
465 | return latest;
466 | }
467 | }
468 |
469 | class DataIO {
470 | constructor() {
471 | // Configurable parameters.
472 | this.data_owner = "godotengine";
473 | this.data_repo = "godot";
474 | this.data_branch = "";
475 | }
476 |
477 | parseArgs() {
478 | process.argv.forEach((arg) => {
479 | if (arg.indexOf("owner:") === 0) {
480 | this.data_owner = arg.substring(6);
481 | }
482 | if (arg.indexOf("repo:") === 0) {
483 | this.data_repo = arg.substring(5);
484 | }
485 | if (arg.indexOf("branch:") === 0) {
486 | this.data_branch = arg.substring(7);
487 | }
488 | });
489 |
490 | if (this.data_owner === "" || this.data_repo === "" || this.data_branch === "") {
491 | console.error(" Error reading command-line arguments: owner, repo, and branch cannot be empty.");
492 | process.exitCode = ExitCodes.IOFailure;
493 | return;
494 | }
495 | }
496 |
497 | async loadData() {
498 | try {
499 | console.log("[*] Loading existing database from a file.");
500 |
501 | const dataPath = `./out/data/${this.data_owner}.${this.data_repo}.${this.data_branch}.json`;
502 | await fs.access(dataPath, fsConstants.R_OK);
503 | const fileRaw = await fs.readFile(dataPath, {encoding: "utf-8"});
504 |
505 | return JSON.parse(fileRaw);
506 | } catch (err) {
507 | return {};
508 | }
509 | }
510 |
511 | async saveData(output, fileName) {
512 | try {
513 | console.log("[*] Storing database to a file.");
514 |
515 | await ensureDir("./out");
516 | await ensureDir("./out/data");
517 | await fs.writeFile(`./out/data/${fileName}`, JSON.stringify(output), {encoding: "utf-8"});
518 | } catch (err) {
519 | console.error(" Error saving database file: " + err);
520 | process.exitCode = ExitCodes.IOFailure;
521 | return;
522 | }
523 | }
524 |
525 | async createRedirects(artifacts) {
526 | let redirectTemplate = "";
527 |
528 | try {
529 | const dataPath = `./build/res/redirect_index.html`;
530 | await fs.access(dataPath, fsConstants.R_OK);
531 | redirectTemplate = await fs.readFile(dataPath, {encoding: "utf-8"});
532 |
533 | if (redirectTemplate === "") {
534 | throw new Error("File is missing.");
535 | }
536 | } catch (err) {
537 | console.error(" Error loading a redirect template: " + err);
538 | process.exitCode = ExitCodes.IOFailure;
539 | return;
540 | }
541 |
542 | await ensureDir("./out");
543 | await ensureDir("./out/download");
544 | await ensureDir(`./out/download/${this.data_owner}`);
545 | await ensureDir(`./out/download/${this.data_owner}/${this.data_repo}`);
546 | await ensureDir(`./out/download/${this.data_owner}/${this.data_repo}/${this.data_branch}`);
547 |
548 | const outputDir = `./out/download/${this.data_owner}/${this.data_repo}/${this.data_branch}`;
549 | for (let artifactName in artifacts) {
550 | await ensureDir(`${outputDir}/${artifactName}`);
551 |
552 | try {
553 | const artifact = artifacts[artifactName];
554 | const artifactPath = `https://github.com/godotengine/godot/suites/${artifact.check_id}/artifacts/${artifact.artifact_id}`;
555 |
556 | const redirectPage = redirectTemplate.replace(/\{\{REDIRECT_PATH\}\}/g, artifactPath);
557 | await fs.writeFile(`${outputDir}/${artifactName}/index.html`, redirectPage, {encoding: "utf-8"});
558 | console.log(` Created a redirect at ${outputDir}/${artifactName}.`)
559 | } catch (err) {
560 | console.error(` Error saving a redirect page for "${artifactName}": ` + err);
561 | process.exitCode = ExitCodes.IOFailure;
562 | return;
563 | }
564 | }
565 | }
566 | }
567 |
568 | function mapNodes(object) {
569 | return object.edges.map((item) => item["node"])
570 | }
571 |
572 | async function ensureDir(dirPath) {
573 | try {
574 | await fs.access(dirPath, fsConstants.R_OK | fsConstants.W_OK);
575 | } catch (err) {
576 | await fs.mkdir(dirPath);
577 | }
578 | }
579 |
580 | async function clearDir(rootPath) {
581 | try {
582 | const pathStat = await fs.stat(rootPath);
583 | if (!pathStat.isDirectory()) {
584 | return;
585 | }
586 |
587 | const removeDir = async (dirPath) => {
588 | const dirFiles = await fs.readdir(dirPath);
589 | for (let entryName of dirFiles) {
590 | if (entryName === "." || entryName === "..") {
591 | continue;
592 | }
593 |
594 | const entryPath = `${dirPath}/${entryName}`;
595 | const entryStat = await fs.stat(entryPath);
596 | if (entryStat.isDirectory()) {
597 | await removeDir(entryPath);
598 | await fs.rmdir(entryPath);
599 | }
600 | else if (entryStat.isFile()) {
601 | await fs.unlink(entryPath);
602 | }
603 | }
604 | };
605 |
606 | await removeDir(rootPath);
607 | } catch (err) {
608 | console.error(` Error clearing a folder at ${rootPath}: ` + err);
609 | process.exitCode = ExitCodes.IOFailure;
610 | return;
611 | }
612 | }
613 |
614 | async function main() {
615 | // Internal utility methods.
616 | const checkForExit = () => {
617 | if (process.exitCode > 0) {
618 | console.log(` Terminating with an exit code ${process.exitCode}.`);
619 | process.exit();
620 | }
621 | };
622 |
623 | console.log("[*] Building local workflow run database.");
624 |
625 | const dataIO = new DataIO();
626 | dataIO.parseArgs();
627 | checkForExit();
628 |
629 | console.log(`[*] Configured for the "${dataIO.data_owner}/${dataIO.data_repo}" repository; branch ${dataIO.data_branch}.`);
630 |
631 | const dataFetcher = new DataFetcher(dataIO.data_owner, dataIO.data_repo);
632 | const dataProcessor = new DataProcessor();
633 |
634 | const existingData = await dataIO.loadData();
635 | dataProcessor.readExistingData(existingData);
636 |
637 | console.log("[*] Checking the rate limits before.");
638 | await dataFetcher.checkRates();
639 | checkForExit();
640 |
641 | console.log("[*] Fetching workflow runs data from GitHub.");
642 | const runsRaw = await dataFetcher.fetchRuns(dataIO.data_branch);
643 | checkForExit();
644 | dataProcessor.processRuns(runsRaw);
645 | checkForExit();
646 |
647 | console.log("[*] Fetching artifact data from GitHub.");
648 | for (let runId of dataProcessor.getIncompleteRuns()) {
649 | const artifactsRaw = await dataFetcher.fetchArtifacts(runId);
650 | checkForExit();
651 | dataProcessor.processArtifacts(runId, artifactsRaw);
652 | checkForExit();
653 |
654 | // Wait for a bit before proceeding to avoid hitting the secondary rate limit in GitHub API.
655 | // See https://docs.github.com/en/rest/guides/best-practices-for-integrators#dealing-with-secondary-rate-limits.
656 | await dataFetcher.delay(API_DELAY_MSEC);
657 | }
658 |
659 | console.log("[*] Checking the rate limits after.");
660 | await dataFetcher.checkRates();
661 | checkForExit();
662 |
663 | console.log("[*] Reducing database.");
664 | dataProcessor.reduceData();
665 | const latestArtifacts = dataProcessor.getLatestArtifacts();
666 |
667 | console.log("[*] Finalizing database.")
668 | const output = {
669 | "generated_at": Date.now(),
670 | "commits": dataProcessor.commits,
671 | "checks": dataProcessor.checks,
672 | "runs": dataProcessor.runs,
673 | "latest": latestArtifacts,
674 | };
675 |
676 | await dataIO.saveData(output, `${dataIO.data_owner}.${dataIO.data_repo}.${dataIO.data_branch}.json`);
677 | checkForExit();
678 |
679 | console.log("[*] Creating stable download paths.");
680 | await dataIO.createRedirects(latestArtifacts);
681 | checkForExit();
682 |
683 | console.log("[*] Database built.");
684 | }
685 |
686 | main();
687 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "godot-commit-artifacts",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "godot-commit-artifacts",
9 | "version": "1.0.0",
10 | "dependencies": {
11 | "@babel/core": "^7.6.4",
12 | "@babel/plugin-proposal-class-properties": "^7.5.5",
13 | "@babel/plugin-proposal-decorators": "^7.6.0",
14 | "dompurify": "^2.0.7",
15 | "lit-element": "^2.2.1",
16 | "marked": "^0.7.0",
17 | "node-fetch": "^2.6.1",
18 | "posthtml": "^0.12.0",
19 | "rollup": "^1.24.0",
20 | "rollup-plugin-babel": "^4.3.3",
21 | "rollup-plugin-commonjs": "^10.1.0",
22 | "rollup-plugin-copy": "^3.4.0",
23 | "rollup-plugin-includepaths": "^0.2.3",
24 | "rollup-plugin-node-resolve": "^5.2.0"
25 | }
26 | },
27 | "node_modules/@babel/code-frame": {
28 | "version": "7.5.5",
29 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
30 | "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
31 | "dependencies": {
32 | "@babel/highlight": "^7.0.0"
33 | }
34 | },
35 | "node_modules/@babel/core": {
36 | "version": "7.6.4",
37 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz",
38 | "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==",
39 | "dependencies": {
40 | "@babel/code-frame": "^7.5.5",
41 | "@babel/generator": "^7.6.4",
42 | "@babel/helpers": "^7.6.2",
43 | "@babel/parser": "^7.6.4",
44 | "@babel/template": "^7.6.0",
45 | "@babel/traverse": "^7.6.3",
46 | "@babel/types": "^7.6.3",
47 | "convert-source-map": "^1.1.0",
48 | "debug": "^4.1.0",
49 | "json5": "^2.1.0",
50 | "lodash": "^4.17.13",
51 | "resolve": "^1.3.2",
52 | "semver": "^5.4.1",
53 | "source-map": "^0.5.0"
54 | },
55 | "engines": {
56 | "node": ">=6.9.0"
57 | }
58 | },
59 | "node_modules/@babel/generator": {
60 | "version": "7.6.4",
61 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz",
62 | "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==",
63 | "dependencies": {
64 | "@babel/types": "^7.6.3",
65 | "jsesc": "^2.5.1",
66 | "lodash": "^4.17.13",
67 | "source-map": "^0.5.0"
68 | }
69 | },
70 | "node_modules/@babel/helper-create-class-features-plugin": {
71 | "version": "7.6.0",
72 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.6.0.tgz",
73 | "integrity": "sha512-O1QWBko4fzGju6VoVvrZg0RROCVifcLxiApnGP3OWfWzvxRZFCoBD81K5ur5e3bVY2Vf/5rIJm8cqPKn8HUJng==",
74 | "dependencies": {
75 | "@babel/helper-function-name": "^7.1.0",
76 | "@babel/helper-member-expression-to-functions": "^7.5.5",
77 | "@babel/helper-optimise-call-expression": "^7.0.0",
78 | "@babel/helper-plugin-utils": "^7.0.0",
79 | "@babel/helper-replace-supers": "^7.5.5",
80 | "@babel/helper-split-export-declaration": "^7.4.4"
81 | },
82 | "peerDependencies": {
83 | "@babel/core": "^7.0.0"
84 | }
85 | },
86 | "node_modules/@babel/helper-function-name": {
87 | "version": "7.1.0",
88 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
89 | "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
90 | "dependencies": {
91 | "@babel/helper-get-function-arity": "^7.0.0",
92 | "@babel/template": "^7.1.0",
93 | "@babel/types": "^7.0.0"
94 | }
95 | },
96 | "node_modules/@babel/helper-get-function-arity": {
97 | "version": "7.0.0",
98 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
99 | "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
100 | "dependencies": {
101 | "@babel/types": "^7.0.0"
102 | }
103 | },
104 | "node_modules/@babel/helper-member-expression-to-functions": {
105 | "version": "7.5.5",
106 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz",
107 | "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==",
108 | "dependencies": {
109 | "@babel/types": "^7.5.5"
110 | }
111 | },
112 | "node_modules/@babel/helper-module-imports": {
113 | "version": "7.0.0",
114 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
115 | "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==",
116 | "dependencies": {
117 | "@babel/types": "^7.0.0"
118 | }
119 | },
120 | "node_modules/@babel/helper-optimise-call-expression": {
121 | "version": "7.0.0",
122 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz",
123 | "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==",
124 | "dependencies": {
125 | "@babel/types": "^7.0.0"
126 | }
127 | },
128 | "node_modules/@babel/helper-plugin-utils": {
129 | "version": "7.0.0",
130 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz",
131 | "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA=="
132 | },
133 | "node_modules/@babel/helper-replace-supers": {
134 | "version": "7.5.5",
135 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz",
136 | "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==",
137 | "dependencies": {
138 | "@babel/helper-member-expression-to-functions": "^7.5.5",
139 | "@babel/helper-optimise-call-expression": "^7.0.0",
140 | "@babel/traverse": "^7.5.5",
141 | "@babel/types": "^7.5.5"
142 | }
143 | },
144 | "node_modules/@babel/helper-split-export-declaration": {
145 | "version": "7.4.4",
146 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz",
147 | "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==",
148 | "dependencies": {
149 | "@babel/types": "^7.4.4"
150 | }
151 | },
152 | "node_modules/@babel/helpers": {
153 | "version": "7.6.2",
154 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz",
155 | "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==",
156 | "dependencies": {
157 | "@babel/template": "^7.6.0",
158 | "@babel/traverse": "^7.6.2",
159 | "@babel/types": "^7.6.0"
160 | }
161 | },
162 | "node_modules/@babel/highlight": {
163 | "version": "7.5.0",
164 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
165 | "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
166 | "dependencies": {
167 | "chalk": "^2.0.0",
168 | "esutils": "^2.0.2",
169 | "js-tokens": "^4.0.0"
170 | }
171 | },
172 | "node_modules/@babel/parser": {
173 | "version": "7.6.4",
174 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz",
175 | "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==",
176 | "bin": {
177 | "parser": "bin/babel-parser.js"
178 | },
179 | "engines": {
180 | "node": ">=6.0.0"
181 | }
182 | },
183 | "node_modules/@babel/plugin-proposal-class-properties": {
184 | "version": "7.5.5",
185 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz",
186 | "integrity": "sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==",
187 | "dependencies": {
188 | "@babel/helper-create-class-features-plugin": "^7.5.5",
189 | "@babel/helper-plugin-utils": "^7.0.0"
190 | },
191 | "peerDependencies": {
192 | "@babel/core": "^7.0.0-0"
193 | }
194 | },
195 | "node_modules/@babel/plugin-proposal-decorators": {
196 | "version": "7.6.0",
197 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.6.0.tgz",
198 | "integrity": "sha512-ZSyYw9trQI50sES6YxREXKu+4b7MAg6Qx2cvyDDYjP2Hpzd3FleOUwC9cqn1+za8d0A2ZU8SHujxFao956efUg==",
199 | "dependencies": {
200 | "@babel/helper-create-class-features-plugin": "^7.6.0",
201 | "@babel/helper-plugin-utils": "^7.0.0",
202 | "@babel/plugin-syntax-decorators": "^7.2.0"
203 | },
204 | "peerDependencies": {
205 | "@babel/core": "^7.0.0-0"
206 | }
207 | },
208 | "node_modules/@babel/plugin-syntax-decorators": {
209 | "version": "7.2.0",
210 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz",
211 | "integrity": "sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==",
212 | "dependencies": {
213 | "@babel/helper-plugin-utils": "^7.0.0"
214 | },
215 | "peerDependencies": {
216 | "@babel/core": "^7.0.0-0"
217 | }
218 | },
219 | "node_modules/@babel/template": {
220 | "version": "7.6.0",
221 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz",
222 | "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==",
223 | "dependencies": {
224 | "@babel/code-frame": "^7.0.0",
225 | "@babel/parser": "^7.6.0",
226 | "@babel/types": "^7.6.0"
227 | }
228 | },
229 | "node_modules/@babel/traverse": {
230 | "version": "7.6.3",
231 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.3.tgz",
232 | "integrity": "sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==",
233 | "dependencies": {
234 | "@babel/code-frame": "^7.5.5",
235 | "@babel/generator": "^7.6.3",
236 | "@babel/helper-function-name": "^7.1.0",
237 | "@babel/helper-split-export-declaration": "^7.4.4",
238 | "@babel/parser": "^7.6.3",
239 | "@babel/types": "^7.6.3",
240 | "debug": "^4.1.0",
241 | "globals": "^11.1.0",
242 | "lodash": "^4.17.13"
243 | }
244 | },
245 | "node_modules/@babel/types": {
246 | "version": "7.6.3",
247 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.3.tgz",
248 | "integrity": "sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==",
249 | "dependencies": {
250 | "esutils": "^2.0.2",
251 | "lodash": "^4.17.13",
252 | "to-fast-properties": "^2.0.0"
253 | }
254 | },
255 | "node_modules/@nodelib/fs.scandir": {
256 | "version": "2.1.3",
257 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
258 | "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
259 | "dependencies": {
260 | "@nodelib/fs.stat": "2.0.3",
261 | "run-parallel": "^1.1.9"
262 | },
263 | "engines": {
264 | "node": ">= 8"
265 | }
266 | },
267 | "node_modules/@nodelib/fs.stat": {
268 | "version": "2.0.3",
269 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
270 | "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
271 | "engines": {
272 | "node": ">= 8"
273 | }
274 | },
275 | "node_modules/@nodelib/fs.walk": {
276 | "version": "1.2.4",
277 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
278 | "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
279 | "dependencies": {
280 | "@nodelib/fs.scandir": "2.1.3",
281 | "fastq": "^1.6.0"
282 | },
283 | "engines": {
284 | "node": ">= 8"
285 | }
286 | },
287 | "node_modules/@types/estree": {
288 | "version": "0.0.39",
289 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
290 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="
291 | },
292 | "node_modules/@types/events": {
293 | "version": "3.0.0",
294 | "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
295 | "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
296 | },
297 | "node_modules/@types/fs-extra": {
298 | "version": "8.1.2",
299 | "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz",
300 | "integrity": "sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==",
301 | "dependencies": {
302 | "@types/node": "*"
303 | }
304 | },
305 | "node_modules/@types/glob": {
306 | "version": "7.1.1",
307 | "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
308 | "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
309 | "dependencies": {
310 | "@types/events": "*",
311 | "@types/minimatch": "*",
312 | "@types/node": "*"
313 | }
314 | },
315 | "node_modules/@types/minimatch": {
316 | "version": "3.0.3",
317 | "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
318 | "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
319 | },
320 | "node_modules/@types/node": {
321 | "version": "12.11.1",
322 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
323 | "integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A=="
324 | },
325 | "node_modules/@types/resolve": {
326 | "version": "0.0.8",
327 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
328 | "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
329 | "dependencies": {
330 | "@types/node": "*"
331 | }
332 | },
333 | "node_modules/acorn": {
334 | "version": "7.1.0",
335 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
336 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
337 | "bin": {
338 | "acorn": "bin/acorn"
339 | },
340 | "engines": {
341 | "node": ">=0.4.0"
342 | }
343 | },
344 | "node_modules/ansi-styles": {
345 | "version": "3.2.1",
346 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
347 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
348 | "dependencies": {
349 | "color-convert": "^1.9.0"
350 | },
351 | "engines": {
352 | "node": ">=4"
353 | }
354 | },
355 | "node_modules/array-union": {
356 | "version": "2.1.0",
357 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
358 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
359 | "engines": {
360 | "node": ">=8"
361 | }
362 | },
363 | "node_modules/balanced-match": {
364 | "version": "1.0.0",
365 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
366 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
367 | },
368 | "node_modules/brace-expansion": {
369 | "version": "1.1.11",
370 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
371 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
372 | "dependencies": {
373 | "balanced-match": "^1.0.0",
374 | "concat-map": "0.0.1"
375 | }
376 | },
377 | "node_modules/braces": {
378 | "version": "3.0.2",
379 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
380 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
381 | "dependencies": {
382 | "fill-range": "^7.0.1"
383 | },
384 | "engines": {
385 | "node": ">=8"
386 | }
387 | },
388 | "node_modules/builtin-modules": {
389 | "version": "3.1.0",
390 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz",
391 | "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
392 | "engines": {
393 | "node": ">=6"
394 | }
395 | },
396 | "node_modules/chalk": {
397 | "version": "2.4.2",
398 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
399 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
400 | "dependencies": {
401 | "ansi-styles": "^3.2.1",
402 | "escape-string-regexp": "^1.0.5",
403 | "supports-color": "^5.3.0"
404 | },
405 | "engines": {
406 | "node": ">=4"
407 | }
408 | },
409 | "node_modules/color-convert": {
410 | "version": "1.9.3",
411 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
412 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
413 | "dependencies": {
414 | "color-name": "1.1.3"
415 | }
416 | },
417 | "node_modules/color-name": {
418 | "version": "1.1.3",
419 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
420 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
421 | },
422 | "node_modules/colorette": {
423 | "version": "1.1.0",
424 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz",
425 | "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg=="
426 | },
427 | "node_modules/concat-map": {
428 | "version": "0.0.1",
429 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
430 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
431 | },
432 | "node_modules/convert-source-map": {
433 | "version": "1.6.0",
434 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
435 | "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
436 | "dependencies": {
437 | "safe-buffer": "~5.1.1"
438 | }
439 | },
440 | "node_modules/convert-source-map/node_modules/safe-buffer": {
441 | "version": "5.1.2",
442 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
443 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
444 | },
445 | "node_modules/debug": {
446 | "version": "4.1.1",
447 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
448 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
449 | "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)",
450 | "dependencies": {
451 | "ms": "^2.1.1"
452 | }
453 | },
454 | "node_modules/dir-glob": {
455 | "version": "3.0.1",
456 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
457 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
458 | "dependencies": {
459 | "path-type": "^4.0.0"
460 | },
461 | "engines": {
462 | "node": ">=8"
463 | }
464 | },
465 | "node_modules/dom-serializer": {
466 | "version": "0.2.1",
467 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
468 | "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
469 | "dependencies": {
470 | "domelementtype": "^2.0.1",
471 | "entities": "^2.0.0"
472 | }
473 | },
474 | "node_modules/dom-serializer/node_modules/domelementtype": {
475 | "version": "2.0.1",
476 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
477 | "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
478 | },
479 | "node_modules/dom-serializer/node_modules/entities": {
480 | "version": "2.0.0",
481 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
482 | "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
483 | },
484 | "node_modules/domelementtype": {
485 | "version": "1.3.1",
486 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
487 | "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
488 | },
489 | "node_modules/domhandler": {
490 | "version": "2.4.2",
491 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
492 | "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
493 | "dependencies": {
494 | "domelementtype": "1"
495 | }
496 | },
497 | "node_modules/dompurify": {
498 | "version": "2.0.7",
499 | "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.7.tgz",
500 | "integrity": "sha512-S3O0lk6rFJtO01ZTzMollCOGg+WAtCwS3U5E2WSDY/x/sy7q70RjEC4Dmrih5/UqzLLB9XoKJ8KqwBxaNvBu4A=="
501 | },
502 | "node_modules/domutils": {
503 | "version": "1.7.0",
504 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
505 | "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
506 | "dependencies": {
507 | "dom-serializer": "0",
508 | "domelementtype": "1"
509 | }
510 | },
511 | "node_modules/entities": {
512 | "version": "1.1.2",
513 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
514 | "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
515 | },
516 | "node_modules/escape-string-regexp": {
517 | "version": "1.0.5",
518 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
519 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
520 | "engines": {
521 | "node": ">=0.8.0"
522 | }
523 | },
524 | "node_modules/estree-walker": {
525 | "version": "0.6.1",
526 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
527 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="
528 | },
529 | "node_modules/esutils": {
530 | "version": "2.0.3",
531 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
532 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
533 | "engines": {
534 | "node": ">=0.10.0"
535 | }
536 | },
537 | "node_modules/fast-glob": {
538 | "version": "3.1.0",
539 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.0.tgz",
540 | "integrity": "sha512-TrUz3THiq2Vy3bjfQUB2wNyPdGBeGmdjbzzBLhfHN4YFurYptCKwGq/TfiRavbGywFRzY6U2CdmQ1zmsY5yYaw==",
541 | "dependencies": {
542 | "@nodelib/fs.stat": "^2.0.2",
543 | "@nodelib/fs.walk": "^1.2.3",
544 | "glob-parent": "^5.1.0",
545 | "merge2": "^1.3.0",
546 | "micromatch": "^4.0.2"
547 | },
548 | "engines": {
549 | "node": ">=8"
550 | }
551 | },
552 | "node_modules/fastq": {
553 | "version": "1.6.0",
554 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz",
555 | "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==",
556 | "dependencies": {
557 | "reusify": "^1.0.0"
558 | }
559 | },
560 | "node_modules/fill-range": {
561 | "version": "7.0.1",
562 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
563 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
564 | "dependencies": {
565 | "to-regex-range": "^5.0.1"
566 | },
567 | "engines": {
568 | "node": ">=8"
569 | }
570 | },
571 | "node_modules/fs-extra": {
572 | "version": "8.1.0",
573 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
574 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
575 | "dependencies": {
576 | "graceful-fs": "^4.2.0",
577 | "jsonfile": "^4.0.0",
578 | "universalify": "^0.1.0"
579 | },
580 | "engines": {
581 | "node": ">=6 <7 || >=8"
582 | }
583 | },
584 | "node_modules/fs.realpath": {
585 | "version": "1.0.0",
586 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
587 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
588 | },
589 | "node_modules/glob": {
590 | "version": "7.1.4",
591 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
592 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
593 | "dependencies": {
594 | "fs.realpath": "^1.0.0",
595 | "inflight": "^1.0.4",
596 | "inherits": "2",
597 | "minimatch": "^3.0.4",
598 | "once": "^1.3.0",
599 | "path-is-absolute": "^1.0.0"
600 | },
601 | "engines": {
602 | "node": "*"
603 | }
604 | },
605 | "node_modules/glob-parent": {
606 | "version": "5.1.0",
607 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
608 | "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
609 | "dependencies": {
610 | "is-glob": "^4.0.1"
611 | },
612 | "engines": {
613 | "node": ">= 6"
614 | }
615 | },
616 | "node_modules/globals": {
617 | "version": "11.12.0",
618 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
619 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
620 | "engines": {
621 | "node": ">=4"
622 | }
623 | },
624 | "node_modules/globby": {
625 | "version": "10.0.1",
626 | "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz",
627 | "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==",
628 | "dependencies": {
629 | "@types/glob": "^7.1.1",
630 | "array-union": "^2.1.0",
631 | "dir-glob": "^3.0.1",
632 | "fast-glob": "^3.0.3",
633 | "glob": "^7.1.3",
634 | "ignore": "^5.1.1",
635 | "merge2": "^1.2.3",
636 | "slash": "^3.0.0"
637 | },
638 | "engines": {
639 | "node": ">=8"
640 | }
641 | },
642 | "node_modules/graceful-fs": {
643 | "version": "4.2.2",
644 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
645 | "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q=="
646 | },
647 | "node_modules/has-flag": {
648 | "version": "3.0.0",
649 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
650 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
651 | "engines": {
652 | "node": ">=4"
653 | }
654 | },
655 | "node_modules/htmlparser2": {
656 | "version": "3.10.1",
657 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
658 | "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
659 | "dependencies": {
660 | "domelementtype": "^1.3.1",
661 | "domhandler": "^2.3.0",
662 | "domutils": "^1.5.1",
663 | "entities": "^1.1.1",
664 | "inherits": "^2.0.1",
665 | "readable-stream": "^3.1.1"
666 | }
667 | },
668 | "node_modules/ignore": {
669 | "version": "5.1.4",
670 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
671 | "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
672 | "engines": {
673 | "node": ">= 4"
674 | }
675 | },
676 | "node_modules/inflight": {
677 | "version": "1.0.6",
678 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
679 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
680 | "dependencies": {
681 | "once": "^1.3.0",
682 | "wrappy": "1"
683 | }
684 | },
685 | "node_modules/inherits": {
686 | "version": "2.0.4",
687 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
688 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
689 | },
690 | "node_modules/is-extglob": {
691 | "version": "2.1.1",
692 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
693 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
694 | "engines": {
695 | "node": ">=0.10.0"
696 | }
697 | },
698 | "node_modules/is-glob": {
699 | "version": "4.0.1",
700 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
701 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
702 | "dependencies": {
703 | "is-extglob": "^2.1.1"
704 | },
705 | "engines": {
706 | "node": ">=0.10.0"
707 | }
708 | },
709 | "node_modules/is-module": {
710 | "version": "1.0.0",
711 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
712 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE="
713 | },
714 | "node_modules/is-number": {
715 | "version": "7.0.0",
716 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
717 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
718 | "engines": {
719 | "node": ">=0.12.0"
720 | }
721 | },
722 | "node_modules/is-plain-object": {
723 | "version": "3.0.0",
724 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
725 | "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
726 | "dependencies": {
727 | "isobject": "^4.0.0"
728 | },
729 | "engines": {
730 | "node": ">=0.10.0"
731 | }
732 | },
733 | "node_modules/is-reference": {
734 | "version": "1.1.4",
735 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
736 | "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
737 | "dependencies": {
738 | "@types/estree": "0.0.39"
739 | }
740 | },
741 | "node_modules/isobject": {
742 | "version": "4.0.0",
743 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
744 | "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==",
745 | "engines": {
746 | "node": ">=0.10.0"
747 | }
748 | },
749 | "node_modules/js-tokens": {
750 | "version": "4.0.0",
751 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
752 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
753 | },
754 | "node_modules/jsesc": {
755 | "version": "2.5.2",
756 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
757 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
758 | "bin": {
759 | "jsesc": "bin/jsesc"
760 | },
761 | "engines": {
762 | "node": ">=4"
763 | }
764 | },
765 | "node_modules/json5": {
766 | "version": "2.1.1",
767 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz",
768 | "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==",
769 | "dependencies": {
770 | "minimist": "^1.2.0"
771 | },
772 | "bin": {
773 | "json5": "lib/cli.js"
774 | },
775 | "engines": {
776 | "node": ">=6"
777 | }
778 | },
779 | "node_modules/jsonfile": {
780 | "version": "4.0.0",
781 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
782 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
783 | "optionalDependencies": {
784 | "graceful-fs": "^4.1.6"
785 | }
786 | },
787 | "node_modules/lit-element": {
788 | "version": "2.2.1",
789 | "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.2.1.tgz",
790 | "integrity": "sha512-ipDcgQ1EpW6Va2Z6dWm79jYdimVepO5GL0eYkZrFvdr0OD/1N260Q9DH+K5HXHFrRoC7dOg+ZpED2XE0TgGdXw==",
791 | "dependencies": {
792 | "lit-html": "^1.0.0"
793 | }
794 | },
795 | "node_modules/lit-html": {
796 | "version": "1.1.2",
797 | "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.1.2.tgz",
798 | "integrity": "sha512-FFlUMKHKi+qG1x1iHNZ1hrtc/zHmfYTyrSvs3/wBTvaNtpZjOZGWzU7efGYVpgp6KvWeKF6ql9/KsCq6Z/mEDA=="
799 | },
800 | "node_modules/lodash": {
801 | "version": "4.17.15",
802 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
803 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
804 | },
805 | "node_modules/magic-string": {
806 | "version": "0.25.4",
807 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz",
808 | "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==",
809 | "dependencies": {
810 | "sourcemap-codec": "^1.4.4"
811 | }
812 | },
813 | "node_modules/marked": {
814 | "version": "0.7.0",
815 | "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz",
816 | "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==",
817 | "bin": {
818 | "marked": "bin/marked"
819 | },
820 | "engines": {
821 | "node": ">=0.10.0"
822 | }
823 | },
824 | "node_modules/merge2": {
825 | "version": "1.3.0",
826 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
827 | "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==",
828 | "engines": {
829 | "node": ">= 6"
830 | }
831 | },
832 | "node_modules/micromatch": {
833 | "version": "4.0.2",
834 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
835 | "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
836 | "dependencies": {
837 | "braces": "^3.0.1",
838 | "picomatch": "^2.0.5"
839 | },
840 | "engines": {
841 | "node": ">=8"
842 | }
843 | },
844 | "node_modules/minimatch": {
845 | "version": "3.0.4",
846 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
847 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
848 | "dependencies": {
849 | "brace-expansion": "^1.1.7"
850 | },
851 | "engines": {
852 | "node": "*"
853 | }
854 | },
855 | "node_modules/minimist": {
856 | "version": "1.2.0",
857 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
858 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
859 | },
860 | "node_modules/ms": {
861 | "version": "2.1.2",
862 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
863 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
864 | },
865 | "node_modules/node-fetch": {
866 | "version": "2.6.1",
867 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
868 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
869 | "engines": {
870 | "node": "4.x || >=6.0.0"
871 | }
872 | },
873 | "node_modules/object-assign": {
874 | "version": "4.1.1",
875 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
876 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
877 | "engines": {
878 | "node": ">=0.10.0"
879 | }
880 | },
881 | "node_modules/once": {
882 | "version": "1.4.0",
883 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
884 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
885 | "dependencies": {
886 | "wrappy": "1"
887 | }
888 | },
889 | "node_modules/path-is-absolute": {
890 | "version": "1.0.1",
891 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
892 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
893 | "engines": {
894 | "node": ">=0.10.0"
895 | }
896 | },
897 | "node_modules/path-parse": {
898 | "version": "1.0.6",
899 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
900 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
901 | },
902 | "node_modules/path-type": {
903 | "version": "4.0.0",
904 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
905 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
906 | "engines": {
907 | "node": ">=8"
908 | }
909 | },
910 | "node_modules/picomatch": {
911 | "version": "2.0.7",
912 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
913 | "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
914 | "engines": {
915 | "node": ">=8"
916 | }
917 | },
918 | "node_modules/posthtml": {
919 | "version": "0.12.0",
920 | "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.12.0.tgz",
921 | "integrity": "sha512-aNUEP/SfKUXAt+ghG51LC5MmafChBZeslVe/SSdfKIgLGUVRE68mrMF4V8XbH07ZifM91tCSuxY3eHIFLlecQw==",
922 | "dependencies": {
923 | "posthtml-parser": "^0.4.1",
924 | "posthtml-render": "^1.1.5"
925 | },
926 | "engines": {
927 | "node": ">=6.0.0"
928 | }
929 | },
930 | "node_modules/posthtml-parser": {
931 | "version": "0.4.1",
932 | "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.4.1.tgz",
933 | "integrity": "sha512-h7vXIQ21Ikz2w5wPClPakNP6mJeJCK6BT0GpqnQrNNABdR7/TchNlFyryL1Bz6Ww53YWCKkr6tdZuHlxY1AVdQ==",
934 | "dependencies": {
935 | "htmlparser2": "^3.9.2",
936 | "object-assign": "^4.1.1"
937 | }
938 | },
939 | "node_modules/posthtml-render": {
940 | "version": "1.1.5",
941 | "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-1.1.5.tgz",
942 | "integrity": "sha512-yvt54j0zCBHQVEFAuR+yHld8CZrCa/E1Z/OcFNCV1IEWTLVxT8O7nYnM4IIw1CD4r8kaRd3lc42+0lgCKgm87w==",
943 | "engines": {
944 | "node": ">=6"
945 | }
946 | },
947 | "node_modules/readable-stream": {
948 | "version": "3.4.0",
949 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
950 | "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
951 | "dependencies": {
952 | "inherits": "^2.0.3",
953 | "string_decoder": "^1.1.1",
954 | "util-deprecate": "^1.0.1"
955 | },
956 | "engines": {
957 | "node": ">= 6"
958 | }
959 | },
960 | "node_modules/resolve": {
961 | "version": "1.12.0",
962 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
963 | "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
964 | "dependencies": {
965 | "path-parse": "^1.0.6"
966 | }
967 | },
968 | "node_modules/reusify": {
969 | "version": "1.0.4",
970 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
971 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
972 | "engines": {
973 | "iojs": ">=1.0.0",
974 | "node": ">=0.10.0"
975 | }
976 | },
977 | "node_modules/rollup": {
978 | "version": "1.24.0",
979 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.24.0.tgz",
980 | "integrity": "sha512-PiFETY/rPwodQ8TTC52Nz2DSCYUATznGh/ChnxActCr8rV5FIk3afBUb3uxNritQW/Jpbdn3kq1Rwh1HHYMwdQ==",
981 | "dependencies": {
982 | "@types/estree": "*",
983 | "@types/node": "*",
984 | "acorn": "^7.1.0"
985 | },
986 | "bin": {
987 | "rollup": "dist/bin/rollup"
988 | }
989 | },
990 | "node_modules/rollup-plugin-babel": {
991 | "version": "4.3.3",
992 | "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-4.3.3.tgz",
993 | "integrity": "sha512-tKzWOCmIJD/6aKNz0H1GMM+lW1q9KyFubbWzGiOG540zxPPifnEAHTZwjo0g991Y+DyOZcLqBgqOdqazYE5fkw==",
994 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-babel.",
995 | "dependencies": {
996 | "@babel/helper-module-imports": "^7.0.0",
997 | "rollup-pluginutils": "^2.8.1"
998 | },
999 | "peerDependencies": {
1000 | "@babel/core": "7 || ^7.0.0-rc.2",
1001 | "rollup": ">=0.60.0 <2"
1002 | }
1003 | },
1004 | "node_modules/rollup-plugin-commonjs": {
1005 | "version": "10.1.0",
1006 | "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz",
1007 | "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==",
1008 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-commonjs.",
1009 | "dependencies": {
1010 | "estree-walker": "^0.6.1",
1011 | "is-reference": "^1.1.2",
1012 | "magic-string": "^0.25.2",
1013 | "resolve": "^1.11.0",
1014 | "rollup-pluginutils": "^2.8.1"
1015 | },
1016 | "peerDependencies": {
1017 | "rollup": ">=1.12.0"
1018 | }
1019 | },
1020 | "node_modules/rollup-plugin-copy": {
1021 | "version": "3.4.0",
1022 | "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz",
1023 | "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==",
1024 | "dependencies": {
1025 | "@types/fs-extra": "^8.0.1",
1026 | "colorette": "^1.1.0",
1027 | "fs-extra": "^8.1.0",
1028 | "globby": "10.0.1",
1029 | "is-plain-object": "^3.0.0"
1030 | },
1031 | "engines": {
1032 | "node": ">=8.3"
1033 | }
1034 | },
1035 | "node_modules/rollup-plugin-includepaths": {
1036 | "version": "0.2.3",
1037 | "resolved": "https://registry.npmjs.org/rollup-plugin-includepaths/-/rollup-plugin-includepaths-0.2.3.tgz",
1038 | "integrity": "sha512-4QbSIZPDT+FL4SViEVCRi4cGCA64zQJu7u5qmCkO3ecHy+l9EQBsue15KfCpddfb6Br0q47V/v2+E2YUiqts9g=="
1039 | },
1040 | "node_modules/rollup-plugin-node-resolve": {
1041 | "version": "5.2.0",
1042 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz",
1043 | "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==",
1044 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-node-resolve.",
1045 | "dependencies": {
1046 | "@types/resolve": "0.0.8",
1047 | "builtin-modules": "^3.1.0",
1048 | "is-module": "^1.0.0",
1049 | "resolve": "^1.11.1",
1050 | "rollup-pluginutils": "^2.8.1"
1051 | },
1052 | "peerDependencies": {
1053 | "rollup": ">=1.11.0"
1054 | }
1055 | },
1056 | "node_modules/rollup-pluginutils": {
1057 | "version": "2.8.2",
1058 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
1059 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
1060 | "dependencies": {
1061 | "estree-walker": "^0.6.1"
1062 | }
1063 | },
1064 | "node_modules/run-parallel": {
1065 | "version": "1.1.9",
1066 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
1067 | "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q=="
1068 | },
1069 | "node_modules/safe-buffer": {
1070 | "version": "5.2.0",
1071 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
1072 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
1073 | },
1074 | "node_modules/semver": {
1075 | "version": "5.7.1",
1076 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1077 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
1078 | "bin": {
1079 | "semver": "bin/semver"
1080 | }
1081 | },
1082 | "node_modules/slash": {
1083 | "version": "3.0.0",
1084 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
1085 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
1086 | "engines": {
1087 | "node": ">=8"
1088 | }
1089 | },
1090 | "node_modules/source-map": {
1091 | "version": "0.5.7",
1092 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
1093 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
1094 | "engines": {
1095 | "node": ">=0.10.0"
1096 | }
1097 | },
1098 | "node_modules/sourcemap-codec": {
1099 | "version": "1.4.6",
1100 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz",
1101 | "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==",
1102 | "deprecated": "Please use @jridgewell/sourcemap-codec instead"
1103 | },
1104 | "node_modules/string_decoder": {
1105 | "version": "1.3.0",
1106 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1107 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1108 | "dependencies": {
1109 | "safe-buffer": "~5.2.0"
1110 | }
1111 | },
1112 | "node_modules/supports-color": {
1113 | "version": "5.5.0",
1114 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1115 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1116 | "dependencies": {
1117 | "has-flag": "^3.0.0"
1118 | },
1119 | "engines": {
1120 | "node": ">=4"
1121 | }
1122 | },
1123 | "node_modules/to-fast-properties": {
1124 | "version": "2.0.0",
1125 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
1126 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
1127 | "engines": {
1128 | "node": ">=4"
1129 | }
1130 | },
1131 | "node_modules/to-regex-range": {
1132 | "version": "5.0.1",
1133 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1134 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1135 | "dependencies": {
1136 | "is-number": "^7.0.0"
1137 | },
1138 | "engines": {
1139 | "node": ">=8.0"
1140 | }
1141 | },
1142 | "node_modules/universalify": {
1143 | "version": "0.1.2",
1144 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
1145 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
1146 | "engines": {
1147 | "node": ">= 4.0.0"
1148 | }
1149 | },
1150 | "node_modules/util-deprecate": {
1151 | "version": "1.0.2",
1152 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1153 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
1154 | },
1155 | "node_modules/wrappy": {
1156 | "version": "1.0.2",
1157 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1158 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
1159 | }
1160 | }
1161 | }
1162 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "godot-commit-artifacts",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build": "rollup -c",
7 | "compose-db": "node ./compose-db.js"
8 | },
9 | "author": "Yuri Sizov ",
10 | "private": true,
11 | "dependencies": {
12 | "@babel/core": "^7.6.4",
13 | "@babel/plugin-proposal-class-properties": "^7.5.5",
14 | "@babel/plugin-proposal-decorators": "^7.6.0",
15 | "dompurify": "^2.0.7",
16 | "lit-element": "^2.2.1",
17 | "marked": "^0.7.0",
18 | "node-fetch": "^2.6.1",
19 | "posthtml": "^0.12.0",
20 | "rollup": "^1.24.0",
21 | "rollup-plugin-babel": "^4.3.3",
22 | "rollup-plugin-commonjs": "^10.1.0",
23 | "rollup-plugin-copy": "^3.4.0",
24 | "rollup-plugin-includepaths": "^0.2.3",
25 | "rollup-plugin-node-resolve": "^5.2.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { promises as fs } from 'fs';
2 | import path from 'path';
3 |
4 | import nodeResolve from 'rollup-plugin-node-resolve';
5 | import includePaths from 'rollup-plugin-includepaths'
6 | import commonjs from 'rollup-plugin-commonjs';
7 | import copy from 'rollup-plugin-copy';
8 | import posthtmlTemplate from './build/rollup-posthtml-template';
9 | import babel from 'rollup-plugin-babel';
10 |
11 | const INPUT_ROOT = 'src';
12 | const INPUT_PATHS_ROOT = path.join(INPUT_ROOT, 'paths');
13 | const INPUT_SHARED_ROOT = path.join(INPUT_ROOT, 'shared');
14 | const INPUT_STATIC_ROOT = path.join(INPUT_ROOT, 'static');
15 |
16 | const ENTRY_FILE_NAME = 'entry.js';
17 | const TEMPLATE_FILE_NAME = 'template.html';
18 | const GLOBAL_FILE_NAME = 'global.js';
19 |
20 | const OUTPUT_ROOT = 'out';
21 | const OUTPUT_STYLES = path.join(OUTPUT_ROOT, 'styles');
22 | const OUTPUT_STYLES_SHARED = path.join(OUTPUT_STYLES, 'shared');
23 | const OUTPUT_SCRIPTS = path.join(OUTPUT_ROOT, 'scripts');
24 | const OUTPUT_SCRIPTS_SHARED = path.join(OUTPUT_SCRIPTS, 'shared');
25 |
26 | const generateConfig = async () => {
27 | let configs = [];
28 |
29 | getGlobalConfig(configs);
30 | await getPathsConfigs(configs);
31 |
32 | return configs;
33 | };
34 | const getGlobalConfig = (configs) => {
35 | const globalScriptPath = path.join(INPUT_SHARED_ROOT, 'scripts', GLOBAL_FILE_NAME);
36 | const outputPath = path.join(OUTPUT_SCRIPTS_SHARED, GLOBAL_FILE_NAME);
37 |
38 | const sharedStylesGlob = path.join(INPUT_SHARED_ROOT, 'styles/**/*.css').replace(/\\/g, '/'); // Windows path not supported by copy plugin
39 | const staticGlob = path.join(INPUT_STATIC_ROOT, '/**/*.*').replace(/\\/g, '/'); // Windows path not supported by copy plugin
40 |
41 | configs.push({
42 | input: globalScriptPath,
43 | output: {
44 | name: 'global',
45 | file: outputPath,
46 | format: 'iife'
47 | },
48 | plugins: [
49 | nodeResolve(),
50 | copy({
51 | targets: [
52 | { src: sharedStylesGlob, dest: OUTPUT_STYLES_SHARED },
53 | { src: staticGlob, dest: OUTPUT_ROOT },
54 | ],
55 | verbose: true
56 | })
57 | ]
58 | })
59 |
60 | };
61 | const getPathsConfigs = async (configs) => {
62 | try {
63 | // Collect paths to process
64 | const paths = await fs.readdir(INPUT_PATHS_ROOT);
65 |
66 | for (const itemPath of paths) {
67 | const itemRoot = path.join(INPUT_PATHS_ROOT, itemPath);
68 | const itemFiles = await fs.readdir(itemRoot);
69 |
70 | if (itemFiles.indexOf(ENTRY_FILE_NAME) < 0) {
71 | throw Error(`Missing entry script for "${itemPath}" path`);
72 | }
73 | if (itemFiles.indexOf(TEMPLATE_FILE_NAME) < 0) {
74 | throw Error(`Missing HTML template for "${itemPath}" path`);
75 | }
76 |
77 | const entryPath = path.join(itemRoot, ENTRY_FILE_NAME);
78 | const templatePath = path.join(itemRoot, TEMPLATE_FILE_NAME).replace(/\\/g, '/'); // Windows path not supported by copy plugin
79 | const bundlePath = path.join(OUTPUT_ROOT, 'scripts', `${itemPath}.js`);
80 | const htmlPath = path.join(OUTPUT_ROOT, `${itemPath}.html`);
81 |
82 | configs.push({
83 | input: entryPath,
84 | output: {
85 | name: itemPath,
86 | file: bundlePath,
87 | format: 'iife'
88 | },
89 | plugins: [
90 | babel({
91 | exclude: 'node_modules/**',
92 | plugins: [
93 | [ '@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true } ],
94 | '@babel/plugin-proposal-class-properties'
95 | ]
96 | }),
97 | includePaths({
98 | paths: [ './' ]
99 | }),
100 | nodeResolve(),
101 | commonjs({
102 | sourceMap: false
103 | }),
104 | posthtmlTemplate({
105 | src: templatePath,
106 | dest: htmlPath
107 | })
108 | ]
109 | })
110 | }
111 | } catch (exc) {
112 | console.error(exc);
113 | }
114 | };
115 |
116 | export default generateConfig();
--------------------------------------------------------------------------------
/src/paths/index/components/IndexDescription.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | @customElement('gr-index-description')
4 | export default class IndexDescription extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Colors and variables **/
8 | :host {
9 | }
10 | @media (prefers-color-scheme: dark) {
11 | :host {
12 | }
13 | }
14 |
15 | /** Component styling **/
16 | :host {
17 | line-height: 22px;
18 | }
19 |
20 | :host .header-description {
21 | display: flex;
22 | align-items: flex-end;
23 | color: var(--dimmed-font-color);
24 | }
25 |
26 | :host .header-description-column {
27 | flex: 2;
28 | }
29 | :host .header-description-column.header-extra-links {
30 | flex: 1;
31 | text-align: right;
32 | }
33 |
34 | :host .header-description a {
35 | color: var(--link-font-color);
36 | text-decoration: none;
37 | }
38 | :host .header-description a:hover {
39 | color: var(--link-font-color-hover);
40 | }
41 |
42 | :host hr {
43 | border: none;
44 | border-top: 1px solid var(--g-background-extra-color);
45 | width: 30%;
46 | }
47 |
48 | @media only screen and (max-width: 900px) {
49 | :host .header-description {
50 | padding: 0 8px;
51 | flex-direction: column;
52 | }
53 |
54 | :host .header-description-column {
55 | width: 100%;
56 | }
57 | :host .header-description-column.header-extra-links {
58 | text-align: center;
59 | padding-top: 12px;
60 | }
61 | }
62 | `;
63 | }
64 |
65 | @property({ type: Date }) generated_at = null;
66 |
67 | render() {
68 | return html`
69 |
83 | `;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/paths/index/components/IndexHeader.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | @customElement('gr-index-entry')
4 | export default class IndexHeader extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Colors and variables **/
8 | :host {
9 | --header-meta-color: #98a5b8;
10 | }
11 | @media (prefers-color-scheme: dark) {
12 | :host {
13 | --header-meta-color: #515c6c;
14 | }
15 | }
16 |
17 | /** Component styling **/
18 | :host {
19 | }
20 |
21 | :host .header {
22 | display: flex;
23 | justify-content: space-between;
24 | align-items: center;
25 | }
26 |
27 | :host .header-metadata {
28 | color: var(--header-meta-color);
29 | text-align: right;
30 | }
31 | :host .header-metadata a {
32 | color: var(--link-font-color);
33 | text-decoration: none;
34 | }
35 | :host .header-metadata a:hover {
36 | color: var(--link-font-color-hover);
37 | }
38 |
39 | @media only screen and (max-width: 900px) {
40 | :host .header {
41 | flex-wrap: wrap;
42 | text-align: center;
43 | }
44 | :host .header-title,
45 | :host .header-metadata {
46 | width: 100%;
47 | }
48 | :host .header-metadata {
49 | padding-bottom: 12px;
50 | text-align: center;
51 | }
52 | }
53 | `;
54 | }
55 |
56 | @property({ type: Date }) generated_at = null;
57 |
58 | constructor() {
59 | super();
60 |
61 | // Auto-refresh about once a minute so that the relative time of generation is always actual.
62 | this._refreshTimeout = setTimeout(this._refresh.bind(this), 60 * 1000);
63 | }
64 |
65 | _refresh() {
66 | this.requestUpdate();
67 |
68 | // Continue updating.
69 | this._refreshTimeout = setTimeout(this._refresh.bind(this), 60 * 1000);
70 | }
71 |
72 | render() {
73 | let generatedAt = "";
74 | let generatedRel = "";
75 |
76 | if (this.generated_at) {
77 | generatedAt = greports.format.formatTimestamp(this.generated_at);
78 |
79 | let timeValue = (Date.now() - this.generated_at) / (1000 * 60);
80 | let timeUnit = "minute";
81 |
82 | if (timeValue < 1) {
83 | generatedRel = "just now";
84 | } else {
85 | if (timeValue > 60) {
86 | timeValue = timeValue / 60;
87 | timeUnit = "hour";
88 | }
89 |
90 | generatedRel = greports.format.formatTimespan(-Math.round(timeValue), timeUnit);
91 | }
92 | }
93 |
94 | return html`
95 |
114 | `;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/paths/index/components/branches/BranchItem.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | @customElement('gr-branch-item')
4 | export default class BranchItem extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Colors and variables **/
8 | :host {
9 | --tab-hover-background-color: rgba(0, 0, 0, 0.14);
10 | --tab-active-background-color: #d6e6ff;
11 | --tab-active-border-color: #397adf;
12 | }
13 | @media (prefers-color-scheme: dark) {
14 | :host {
15 | --tab-hover-background-color: rgba(255, 255, 255, 0.14);
16 | --tab-active-background-color: #283446;
17 | --tab-active-border-color: #5394f9;
18 | }
19 | }
20 |
21 | /** Component styling **/
22 | :host {
23 | max-width: 200px;
24 | }
25 |
26 | :host .branch-item {
27 | border-left: 5px solid transparent;
28 | color: var(--g-font-color);
29 | cursor: pointer;
30 | display: flex;
31 | flex-direction: row;
32 | gap: 6px;
33 | padding: 6px 16px;
34 | align-items: center;
35 | }
36 | :host .branch-item:hover {
37 | background-color: var(--tab-hover-background-color);
38 | }
39 | :host .branch-item--active {
40 | background-color: var(--tab-active-background-color);
41 | border-left: 5px solid var(--tab-active-border-color);
42 | }
43 |
44 | :host .branch-title {
45 | flex-grow: 1;
46 | font-size: 15px;
47 | white-space: nowrap;
48 | overflow: hidden;
49 | }
50 |
51 | @keyframes loader-rotate {
52 | from {
53 | transform: rotate(0deg);
54 | }
55 | to {
56 | transform: rotate(360deg);
57 | }
58 | }
59 |
60 | :host .branch-loader {
61 | background-image: url('loader.svg');
62 | background-size: 20px 20px;
63 | background-position: 50% 50%;
64 | background-repeat: no-repeat;
65 | border-radius: 2px;
66 | display: inline-block;
67 | width: 20px;
68 | height: 20px;
69 | min-width: 20px;
70 | animation-name: loader-rotate;
71 | animation-duration: 1.25s;
72 | animation-timing-function: steps(8);
73 | animation-iteration-count: infinite;
74 | }
75 |
76 | @media (prefers-color-scheme: light) {
77 | :host .branch-loader {
78 | filter: invert(1);
79 | }
80 | }
81 |
82 | @media only screen and (max-width: 900px) {
83 | :host .branch-item {
84 | padding: 10px 20px;
85 | }
86 |
87 | :host .branch-title {
88 | font-size: 18px;
89 | }
90 | }
91 | `;
92 | }
93 |
94 | @property({ type: String, reflect: true }) name = "";
95 | @property({ type: Boolean, reflect: true }) active = false;
96 | @property({ type: Boolean, reflect: true }) loading = false;
97 |
98 | render(){
99 | const classList = [ "branch-item" ];
100 | if (this.active) {
101 | classList.push("branch-item--active");
102 | }
103 |
104 | return html`
105 |
109 |
110 | ${this.name}
111 |
112 |
113 | ${(this.loading ? html`
114 |
115 | ` : null)}
116 |
117 | `;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/paths/index/components/branches/BranchList.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | import BranchItem from "./BranchItem";
4 |
5 | @customElement('gr-branch-list')
6 | export default class BranchList extends LitElement {
7 | static get styles() {
8 | return css`
9 | /** Colors and variables **/
10 | :host {
11 | --branches-background-color: #fcfcfa;
12 | --branches-border-color: #515c6c;
13 | }
14 | @media (prefers-color-scheme: dark) {
15 | :host {
16 | --branches-background-color: #0d1117;
17 | --branches-border-color: #515c6c;
18 | }
19 | }
20 |
21 | /** Component styling **/
22 | :host {
23 | position: relative;
24 | }
25 |
26 | :host .branch-list {
27 | background-color: var(--branches-background-color);
28 | border-right: 2px solid var(--branches-border-color);
29 | width: 200px;
30 | min-height: 216px;
31 | }
32 |
33 | @media only screen and (max-width: 900px) {
34 | :host {
35 | width: 100%
36 | }
37 |
38 | :host .branch-list {
39 | width: 100% !important;
40 | }
41 | }
42 | `;
43 | }
44 |
45 | @property({ type: Array }) branches = [];
46 | @property({ type: Array }) loadingBranchess = [];
47 | @property({ type: String }) selectedBranch = "";
48 |
49 | _onItemClicked(branchName) {
50 | this.dispatchEvent(greports.util.createEvent("branchclick", {
51 | "branch": branchName,
52 | }));
53 | }
54 |
55 | render() {
56 | return html`
57 |
58 | ${this.branches.map((item) => {
59 | return html`
60 |
61 |
67 |
68 | `;
69 | })}
70 |
71 | `;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/paths/index/components/commits/CommitItem.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | @customElement('gr-commit-item')
4 | export default class CommitItem extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Colors and variables **/
8 | :host {
9 | --item-border-color: #fcfcfa;
10 | }
11 |
12 | @media (prefers-color-scheme: dark) {
13 | :host {
14 | --item-border-color: #0d1117;
15 | }
16 | }
17 |
18 | /** Component styling **/
19 | :host {
20 | border-bottom: 3px solid var(--item-border-color);
21 | display: block;
22 | padding: 14px 12px 20px 12px;
23 | }
24 |
25 | :host a {
26 | color: var(--link-font-color);
27 | text-decoration: none;
28 | }
29 | :host a:hover {
30 | color: var(--link-font-color-hover);
31 | }
32 |
33 | :host .item-title {
34 | display: inline-flex;
35 | justify-content: space-between;
36 | font-size: 20px;
37 | margin-top: 6px;
38 | margin-bottom: 12px;
39 | width: 100%;
40 | }
41 |
42 | :host .item-subtitle {
43 | color: var(--dimmed-font-color);
44 | font-size: 16px;
45 | line-height: 20px;
46 | word-break: break-word;
47 | }
48 |
49 | :host .item-workflows {
50 | margin-top: 12px;
51 | }
52 |
53 | :host .workflow {
54 | display: grid;
55 | grid-template-columns: 1fr 1fr;
56 | gap: 8px;
57 | padding: 12px 10px;
58 | }
59 | :host .workflow + .workflow {
60 | border-top: 2px solid var(--g-background-extra-color);
61 | }
62 |
63 | :host .workflow-artifacts {
64 | display: flex;
65 | flex-direction: column;
66 | gap: 6px;
67 | color: var(--dimmed-font-color);
68 | font-size: 14px;
69 | }
70 |
71 | :host .workflow-artifacts a {
72 | font-size: 15px;
73 | font-weight: 600;
74 | }
75 |
76 | @media only screen and (max-width: 900px) {
77 | :host {
78 | padding: 14px 0 20px 0;
79 | }
80 |
81 | :host .workflow {
82 | grid-template-columns: 1fr;
83 | }
84 | }
85 |
86 | @media only screen and (max-width: 640px) {
87 | :host .item-container {
88 | padding: 0 10px;
89 | }
90 | }
91 | `;
92 | }
93 |
94 | @property({ type: String, reflect: true }) hash = '';
95 | @property({ type: String }) title = '';
96 | @property({ type: Array }) workflows = [];
97 |
98 | @property({ type: String }) repository = '';
99 |
100 | render(){
101 | const [...workflows] = this.workflows;
102 | workflows.sort((a,b) => {
103 | if (a.name_sanitized > b.name_sanitized) return 1;
104 | if (a.name_sanitized < b.name_sanitized) return -1;
105 | return 0;
106 | });
107 |
108 | return html`
109 |
110 |
120 |
${this.title}
121 |
122 | ${workflows.map((item) => {
123 | return html`
124 |
125 |
${item.name}
126 |
127 | ${item.artifacts.map((artifact) => {
128 | return html`
129 |
130 |
134 | ${artifact.name}
135 |
136 | (${greports.format.humanizeBytes(artifact.size)})
137 |
138 | `;
139 | })}
140 |
141 |
142 | `;
143 | })}
144 |
145 |
146 | `;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/paths/index/components/commits/CommitList.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | import CommitItem from "./CommitItem";
4 | import LatestItem from "./LatestItem";
5 |
6 | @customElement('gr-commit-list')
7 | export default class CommitList extends LitElement {
8 | static get styles() {
9 | return css`
10 | /** Colors and variables **/
11 | :host {
12 | --item-border-color: #fcfcfa;
13 | --commits-background-color: #e5edf8;
14 | }
15 | @media (prefers-color-scheme: dark) {
16 | :host {
17 | --item-border-color: #0d1117;
18 | --commits-background-color: #191d23;
19 | }
20 | }
21 |
22 | /** Component styling **/
23 | :host {
24 | flex-grow: 1;
25 | }
26 |
27 | :host .branch-commits {
28 | display: flex;
29 | flex-direction: column;
30 | gap: 24px;
31 | background-color: var(--commits-background-color);
32 | border-radius: 0 4px 4px 0;
33 | padding: 8px 12px;
34 | max-width: 760px;
35 | }
36 | @media only screen and (max-width: 900px) {
37 | :host .branch-commits {
38 | padding: 8px;
39 | max-width: 95%;
40 | margin: 0px auto;
41 | }
42 | }
43 |
44 | :host .branch-commits-empty {
45 | color: var(--g-font-color);
46 | display: inline-block;
47 | font-size: 20px;
48 | line-height: 24px;
49 | margin-top: 6px;
50 | margin-bottom: 12px;
51 | padding: 14px 12px;
52 | word-break: break-word;
53 | }
54 | `;
55 | }
56 |
57 | @property({ type: Array }) commits = [];
58 | @property({ type: Object }) checks = {};
59 | @property({ type: Object }) runs = {};
60 | @property({ type: Object }) artifacts = {};
61 | @property({ type: Object }) latest = {};
62 |
63 | @property({ type: String }) selectedRepository = "";
64 | @property({ type: String }) selectedBranch = "";
65 | @property({ type: Boolean, reflect: true }) loading = false;
66 |
67 | constructor() {
68 | super();
69 |
70 | this._workflowsPerCommit = {};
71 | }
72 |
73 | _updateWorkflows() {
74 | this._workflowsPerCommit = {};
75 |
76 | this.commits.forEach((item) => {
77 | let workflows = [];
78 |
79 | for (let checkId in this.checks) {
80 | const check = this.checks[checkId];
81 | if (item.checks.indexOf(check.check_id) < 0) {
82 | continue;
83 | }
84 |
85 | if (check.workflow === "" || typeof this.runs[check.workflow] === "undefined") {
86 | continue;
87 | }
88 |
89 | const run = this.runs[check.workflow];
90 | if (run.artifacts.length === 0) {
91 | continue;
92 | }
93 |
94 | workflows.push({
95 | "name": run.name,
96 | "name_sanitized": run.name.replace(/([^a-zA-Z0-9_\- ]+)/g, "").trim().toLowerCase(),
97 | "check_id": check.check_id,
98 | "artifacts": run.artifacts,
99 | });
100 | }
101 |
102 | this._workflowsPerCommit[item.hash] = workflows;
103 | });
104 | }
105 |
106 | update(changedProperties) {
107 | // Only recalculate when class properties change; skip for manual updates.
108 | if (changedProperties.size > 0) {
109 | this._updateWorkflows();
110 | }
111 |
112 | super.update(changedProperties);
113 | }
114 |
115 | render(){
116 | if (this.selectedBranch === "") {
117 | return html``;
118 | }
119 | if (this.loading) {
120 | return html`
121 | Loading artifacts...
122 | `;
123 | }
124 |
125 | return html`
126 |
127 |
132 |
133 | ${this.commits.map((item) => {
134 | const workflows = this._workflowsPerCommit[item.hash];
135 |
136 | return html`
137 |
144 | `;
145 | })}
146 |
147 | `;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/paths/index/components/commits/LatestItem.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | @customElement('gr-latest-item')
4 | export default class LatestItem extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Colors and variables **/
8 | :host {
9 | --item-border-color: #fcfcfa;
10 | }
11 |
12 | @media (prefers-color-scheme: dark) {
13 | :host {
14 | --item-border-color: #0d1117;
15 | }
16 | }
17 |
18 | /** Component styling **/
19 | :host {
20 | border-bottom: 3px solid var(--item-border-color);
21 | display: block;
22 | padding: 14px 12px 20px 12px;
23 | }
24 |
25 | :host a {
26 | color: var(--link-font-color);
27 | text-decoration: none;
28 | }
29 | :host a:hover {
30 | color: var(--link-font-color-hover);
31 | }
32 |
33 | :host .item-title {
34 | display: inline-flex;
35 | justify-content: space-between;
36 | font-size: 20px;
37 | margin-top: 6px;
38 | margin-bottom: 12px;
39 | width: 100%;
40 | }
41 |
42 | :host .item-subtitle {
43 | color: var(--dimmed-font-color);
44 | font-size: 16px;
45 | line-height: 20px;
46 | word-break: break-word;
47 | }
48 |
49 | :host .item-workflows {
50 | margin-top: 12px;
51 | }
52 |
53 | :host .workflow {
54 | display: grid;
55 | grid-template-columns: 1fr 1fr;
56 | gap: 8px;
57 | padding: 12px 10px;
58 | }
59 | :host .workflow + .workflow {
60 | border-top: 2px solid var(--g-background-extra-color);
61 | }
62 |
63 | :host .workflow-artifacts {
64 | display: flex;
65 | flex-direction: column;
66 | gap: 6px;
67 | color: var(--dimmed-font-color);
68 | font-size: 14px;
69 | }
70 |
71 | :host .workflow-artifacts a.workflow-artifact-link {
72 | font-size: 15px;
73 | font-weight: 600;
74 | }
75 |
76 | @media only screen and (max-width: 900px) {
77 | :host {
78 | padding: 14px 0 20px 0;
79 | }
80 |
81 | :host .workflow {
82 | grid-template-columns: 1fr;
83 | }
84 | }
85 |
86 | @media only screen and (max-width: 640px) {
87 | :host .item-container {
88 | padding: 0 10px;
89 | }
90 | }
91 | `;
92 | }
93 |
94 | @property({ type: Object }) artifacts = {};
95 |
96 | @property({ type: String }) repository = '';
97 | @property({ type: String }) branch = '';
98 |
99 | constructor() {
100 | super();
101 |
102 | this._latestByWorkflow = [];
103 | }
104 |
105 | _updateWorkflows() {
106 | this._latestByWorkflow = [];
107 | const existingWorkflow = {};
108 |
109 | for (let artifactName in this.artifacts) {
110 | const artifact = this.artifacts[artifactName];
111 |
112 | if (typeof existingWorkflow[artifact.workflow_name] === "undefined") {
113 | existingWorkflow[artifact.workflow_name] = {
114 | "name": artifact.workflow_name,
115 | "name_sanitized": artifact.workflow_name.replace(/([^a-zA-Z0-9_\- ]+)/g, "").trim().toLowerCase(),
116 | "artifacts": [],
117 | };
118 | this._latestByWorkflow.push(existingWorkflow[artifact.workflow_name]);
119 | }
120 |
121 | existingWorkflow[artifact.workflow_name].artifacts.push(artifact);
122 | }
123 |
124 | this._latestByWorkflow.sort((a,b) => {
125 | if (a.name_sanitized > b.name_sanitized) return 1;
126 | if (a.name_sanitized < b.name_sanitized) return -1;
127 | return 0;
128 | });
129 | }
130 |
131 | update(changedProperties) {
132 | // Only recalculate when class properties change; skip for manual updates.
133 | if (changedProperties.size > 0) {
134 | this._updateWorkflows();
135 | }
136 |
137 | super.update(changedProperties);
138 | }
139 |
140 | render(){
141 | return html`
142 |
143 |
144 | Latest
145 |
146 |
Builds may be from different runs, depending on their availability.
147 |
148 | ${this._latestByWorkflow.map((item) => {
149 | return html`
150 |
151 |
${item.name}
152 |
175 |
176 | `;
177 | })}
178 |
179 |
180 | `;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/paths/index/entry.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement, property } from 'lit-element';
2 |
3 | import PageContent from 'src/shared/components/PageContent';
4 | import SharedNavigation from 'src/shared/components/SharedNavigation';
5 | import IndexHeader from "./components/IndexHeader";
6 | import IndexDescription from "./components/IndexDescription";
7 |
8 | import BranchList from "./components/branches/BranchList";
9 | import CommitList from "./components/commits/CommitList";
10 |
11 | @customElement('entry-component')
12 | export default class EntryComponent extends LitElement {
13 | static get styles() {
14 | return css`
15 | /** Colors and variables **/
16 | :host {
17 | }
18 | @media (prefers-color-scheme: dark) {
19 | :host {
20 | }
21 | }
22 |
23 | /** Component styling **/
24 | :host {
25 | }
26 |
27 | :host .branches {
28 | display: flex;
29 | padding: 24px 0;
30 | }
31 |
32 | @media only screen and (max-width: 900px) {
33 | :host .branches {
34 | flex-wrap: wrap;
35 | }
36 | }
37 | `;
38 | }
39 |
40 | constructor() {
41 | super();
42 |
43 | this._entryRequested = false;
44 | this._isLoading = true;
45 | this._loadingBranches = [];
46 |
47 | this._branches = [ "master", "4.0", "3.x", "3.5" ];
48 | this._branchData = {};
49 |
50 | this._selectedRepository = "godotengine/godot";
51 | this._selectedBranch = "";
52 |
53 | this._restoreUserPreferences();
54 | this._requestData();
55 | }
56 |
57 | performUpdate() {
58 | this._requestData();
59 | super.performUpdate();
60 | }
61 |
62 | _restoreUserPreferences() {
63 | const userPreferences = greports.util.getLocalPreferences();
64 |
65 | // ...
66 | }
67 |
68 | _saveUserPreferences() {
69 | const currentPreferences = {
70 | // ...
71 | };
72 |
73 | greports.util.setLocalPreferences(currentPreferences);
74 | }
75 |
76 | async _requestData() {
77 | if (this._entryRequested) {
78 | return;
79 | }
80 | this._entryRequested = true;
81 | this._isLoading = true;
82 |
83 | this._isLoading = false;
84 | this.requestUpdate();
85 |
86 | this._branches.forEach((branch) => {
87 | this._requestBranchData(branch);
88 | });
89 | }
90 |
91 | async _requestBranchData(branch) {
92 | // Start loading, show the indicator.
93 | this._loadingBranches.push(branch);
94 |
95 | const branchData = await greports.api.getBranchData(this._selectedRepository, branch);
96 |
97 | if (branchData) {
98 | this._branchData[branch] = branchData;
99 | }
100 |
101 | // Finish loading, hide the indicator.
102 | const index = this._loadingBranches.indexOf(branch);
103 | this._loadingBranches.splice(index, 1);
104 | this.requestUpdate();
105 | }
106 |
107 | _onBranchClicked(event) {
108 | this._selectedBranch = event.detail.branch;
109 | this.requestUpdate();
110 |
111 | window.scrollTo(0, 0);
112 | }
113 |
114 | render() {
115 | // Dereferencing to ensure it triggers an update.
116 | const [...branches] = this._branches;
117 | const [...loadingBranches] = this._loadingBranches;
118 |
119 | let commits = [];
120 | let checks = {};
121 | let runs = {};
122 | let artifacts = {};
123 | let latest = {};
124 |
125 | if (this._selectedBranch !== "" && typeof this._branchData[this._selectedBranch] !== "undefined") {
126 | const branchData = this._branchData[this._selectedBranch];
127 |
128 | commits = branchData.commits;
129 | checks = branchData.checks;
130 | runs = branchData.runs;
131 | artifacts = branchData.artifacts;
132 | latest = branchData.latest;
133 | }
134 |
135 | return html`
136 |
137 |
138 |
139 |
140 |
141 | ${(this._isLoading ? html`
142 | Loading...
143 | ` : html`
144 |
145 |
151 |
152 | ${(this._selectedBranch !== "" ? html`
153 | = 0}"
163 | >
164 | ` : null)}
165 |
166 | `)}
167 |
168 | `;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/src/paths/index/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Godot Commit Artifacts
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/shared/components/PageContent.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement } from 'lit-element';
2 |
3 | @customElement('page-content')
4 | export default class PageContent extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Component styling **/
8 | :host {
9 | display: block;
10 | margin: 0 auto;
11 | padding: 0 12px;
12 | max-width: 1024px;
13 | }
14 |
15 | @media only screen and (max-width: 900px) {
16 | :host {
17 | padding: 0;
18 | }
19 | }
20 | `;
21 | }
22 |
23 | render(){
24 | return html`
25 |
26 | `;
27 | }
28 | }
--------------------------------------------------------------------------------
/src/shared/components/SharedNavigation.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css, customElement } from 'lit-element';
2 |
3 | @customElement('shared-nav')
4 | export default class SharedNavigation extends LitElement {
5 | static get styles() {
6 | return css`
7 | /** Colors and variables **/
8 | :host {
9 | }
10 | @media (prefers-color-scheme: dark) {
11 | :host {
12 | }
13 | }
14 |
15 | /** Component styling **/
16 | :host {
17 | }
18 |
19 | :host .nav-container a {
20 | color: var(--link-font-color);
21 | text-decoration: none;
22 | }
23 | :host .nav-container a:hover {
24 | color: var(--link-font-color-hover);
25 | }
26 |
27 | :host .nav-container {
28 | display: flex;
29 | gap: 8px;
30 | margin-top: 8px;
31 | background: var(--g-background-color);
32 | }
33 |
34 | :host .nav-item {
35 | font-size: 16px;
36 | font-weight: 600;
37 | padding: 10px 16px;
38 | }
39 | :host .nav-item:hover {
40 | background-color: var(--g-background-extra2-color);
41 | }
42 |
43 | :host .nav-toggler {
44 | display: none;
45 | background-image: url('hamburger.svg');
46 | background-repeat: no-repeat;
47 | background-position: center;
48 | cursor: pointer;
49 | position: absolute;
50 | top: 0;
51 | left: 0;
52 | width: 48px;
53 | height: 48px;
54 | }
55 | :host .nav-toggler:hover {
56 | background-color: var(--g-background-extra2-color);
57 | }
58 |
59 | @media only screen and (max-width: 640px) {
60 | :host .nav-container {
61 | display: none;
62 | flex-direction: column;
63 | position: absolute;
64 | top: 0;
65 | left: 0;
66 | right: 0;
67 | padding-top: 40px;
68 | padding-bottom: 12px;
69 | }
70 | :host .nav-container.nav-active {
71 | display: flex;
72 | }
73 |
74 | :host .nav-toggler {
75 | display: block;
76 | }
77 | }
78 | `;
79 | }
80 |
81 | constructor() {
82 | super();
83 |
84 | this._mobileActive = false;
85 | }
86 |
87 | _onMobileToggled() {
88 | this._mobileActive = !this._mobileActive;
89 | this.requestUpdate();
90 | }
91 |
92 | render(){
93 | const containerClassList = [ "nav-container" ];
94 | if (this._mobileActive) {
95 | containerClassList.push("nav-active");
96 | }
97 |
98 | return html`
99 |
116 |
120 | `;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/shared/partials/body_content.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/godotengine/godot-commit-artifacts/30894b7156f1362079e29030307c325567073962/src/shared/partials/body_content.html
--------------------------------------------------------------------------------
/src/shared/partials/head_content.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/shared/scripts/global.js:
--------------------------------------------------------------------------------
1 | const LOCAL_PREFERENCE_PREFIX = "_godot_cmtar"
2 | const LOCAL_PREFERENCE_DEFAULTS = {
3 |
4 | };
5 |
6 | // API Interaction
7 | const ReportsAPI = {
8 | async get(path = '/') {
9 | const res = await fetch(`${path}`);
10 | if (res.status !== 200) {
11 | return null;
12 | }
13 |
14 | return await res.json();
15 | },
16 |
17 | async getBranchData(repositoryId, branchName) {
18 | const idBits = repositoryId.split("/");
19 |
20 | return await this.get(`data/${idBits[0]}.${idBits[1]}.${branchName}.json`);
21 | },
22 | };
23 |
24 | // Content helpers
25 | const ReportsFormatter = {
26 | formatDate(dateString) {
27 | const options = {
28 | year: 'numeric', month: 'long', day: 'numeric',
29 | };
30 | const dateFormatter = new Intl.DateTimeFormat('en-US', options);
31 |
32 | const date = new Date(dateString);
33 | return dateFormatter.format(date);
34 | },
35 |
36 | formatTimestamp(timeString) {
37 | const options = {
38 | year: 'numeric', month: 'long', day: 'numeric',
39 | hour: 'numeric', hour12: false, minute: 'numeric',
40 | timeZone: 'UTC', timeZoneName: 'short',
41 | };
42 | const dateFormatter = new Intl.DateTimeFormat('en-US', options);
43 |
44 | const date = new Date(timeString);
45 | return dateFormatter.format(date);
46 | },
47 |
48 | formatTimespan(timeValue, timeUnit) {
49 | const options = {
50 | style: 'long',
51 | };
52 | const timeFormatter = new Intl.RelativeTimeFormat('en-US', options);
53 |
54 | return timeFormatter.format(timeValue, timeUnit);
55 | },
56 |
57 | getDaysSince(dateString) {
58 | const date = new Date(dateString);
59 | const msBetween = (new Date()) - date;
60 | const days = Math.floor(msBetween / (1000 * 60 * 60 * 24));
61 |
62 | return days;
63 | },
64 |
65 | formatDays(days) {
66 | return days + " " + (days !== 1 ? "days" : "day");
67 | },
68 |
69 | humanizeBytes(bytes) {
70 | if (bytes < 1024) {
71 | return `${bytes} B`;
72 | }
73 |
74 | bytes = bytes / 1024;
75 | if (bytes < 1024) {
76 | return `${Math.round(bytes, 2)} KB`;
77 | }
78 |
79 | bytes = bytes / 1024;
80 | if (bytes < 1024) {
81 | return `${Math.round(bytes, 2)} MB`;
82 | }
83 |
84 | bytes = bytes / 1024;
85 | if (bytes < 1024) {
86 | return `${Math.round(bytes, 2)} GB`;
87 | }
88 |
89 | bytes = bytes / 1024;
90 | return `${Math.round(bytes, 2)} TB`;
91 | }
92 | };
93 |
94 | const ReportsUtils = {
95 | createEvent(name, detail = {}) {
96 | return new CustomEvent(name, {
97 | detail: detail
98 | });
99 | },
100 |
101 | getHistoryHash() {
102 | let rawHash = window.location.hash;
103 | if (rawHash !== "") {
104 | return rawHash.substr(1);
105 | }
106 |
107 | return "";
108 | },
109 |
110 | setHistoryHash(hash) {
111 | const url = new URL(window.location);
112 | url.hash = hash;
113 | window.history.pushState({}, "", url);
114 | },
115 |
116 | navigateHistoryHash(hash) {
117 | this.setHistoryHash(hash);
118 | window.location.reload();
119 | },
120 |
121 | getLocalPreferences() {
122 | // Always fallback on defaults.
123 | const localPreferences = { ...LOCAL_PREFERENCE_DEFAULTS };
124 |
125 | for (let key in localPreferences) {
126 | const storedValue = localStorage.getItem(`${LOCAL_PREFERENCE_PREFIX}_${key}`);
127 | if (storedValue != null) {
128 | localPreferences[key] = JSON.parse(storedValue);
129 | }
130 | }
131 |
132 | return localPreferences;
133 | },
134 |
135 | setLocalPreferences(currentPreferences) {
136 | for (let key in currentPreferences) {
137 | // Only store known properties.
138 | if (key in LOCAL_PREFERENCE_DEFAULTS) {
139 | localStorage.setItem(`${LOCAL_PREFERENCE_PREFIX}_${key}`, JSON.stringify(currentPreferences[key]));
140 | }
141 | }
142 | },
143 |
144 | resetLocalPreferences() {
145 | this.setLocalPreferences(LOCAL_PREFERENCE_DEFAULTS);
146 | },
147 | };
148 |
149 | const ReportsSingleton = {
150 | api: ReportsAPI,
151 | format: ReportsFormatter,
152 | util: ReportsUtils,
153 | };
154 |
155 | window.greports = ReportsSingleton;
156 |
--------------------------------------------------------------------------------
/src/shared/styles/global.css:
--------------------------------------------------------------------------------
1 | /** Colors and variables **/
2 | :root {
3 | --g-background-color: #fcfcfa;
4 | --g-background-extra-color: #98a5b8;
5 | --g-background-extra2-color: #cad3e1;
6 | --g-font-color: #121314;
7 | --g-font-size: 15px;
8 | --g-font-weight: 400;
9 | --g-line-height: 20px;
10 |
11 | --link-font-color: #1d6dff;
12 | --link-font-color-hover: #1051c9;
13 | --link-font-color-inactive: #35496f;
14 |
15 | --dimmed-font-color: #535c5f;
16 | --light-font-color: #6b7893;
17 | }
18 |
19 | @media (prefers-color-scheme: dark) {
20 | :root {
21 | --g-background-color: #0d1117;
22 | --g-background-extra-color: #515c6c;
23 | --g-background-extra2-color: #22252b;
24 | --g-font-color: rgba(228, 228, 232, 0.9);
25 |
26 | --link-font-color: #367df7;
27 | --link-font-color-hover: #6391ec;
28 | --link-font-color-inactive: #abbdcc;
29 |
30 | --dimmed-font-color: #929da0;
31 | --light-font-color: #8491ab;
32 | }
33 | }
34 |
35 | /** General styling **/
36 | html {}
37 |
38 | body {
39 | background: var(--g-background-color);
40 | color: var(--g-font-color);
41 | font-family: 'Roboto', sans-serif;
42 | font-size: var(--g-font-size);
43 | font-weight: var(--g-font-weight);
44 | line-height: var(--g-line-height);
45 | min-width: 380px;
46 | }
47 |
48 | a {
49 | color: var(--link-font-color);
50 | font-weight: 700;
51 | text-decoration: none;
52 | }
53 | a:hover {
54 | color: var(--link-font-color-hover);
55 | }
56 |
--------------------------------------------------------------------------------
/src/shared/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
--------------------------------------------------------------------------------
/src/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/godotengine/godot-commit-artifacts/30894b7156f1362079e29030307c325567073962/src/static/favicon.png
--------------------------------------------------------------------------------
/src/static/icons/loader.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------