├── .browserslistrc ├── .eslintrc.js ├── .eslintrc.tests.js ├── .eslintrc.ui.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── other.md ├── semantic.yml └── workflows │ └── ci.yml ├── .gitignore ├── .lighthouserc.js ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── architecture.md ├── complex-setup.md ├── configuration.md ├── getting-started.md ├── introduction-to-ci.md ├── migration-guide.md ├── recipes │ ├── docker-client │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── seccomp-chrome.json │ │ ├── test-in-container.sh │ │ ├── test.sh │ │ └── update-dockerhub.sh │ ├── docker-server │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── kubernetes │ │ │ ├── lhci-data-claim.yml │ │ │ ├── lhci-deployment.yml │ │ │ ├── lhci-ingress.yml │ │ │ └── lhci-service.yml │ │ ├── lighthouserc.json │ │ ├── package.json │ │ ├── test-in-container.sh │ │ ├── test.sh │ │ └── update-dockerhub.sh │ ├── heroku-server │ │ ├── README.md │ │ ├── package.json │ │ └── server.js │ ├── lhci-server-vpn-proxy │ │ ├── README.md │ │ └── nginx.conf │ └── puppeteer-example.js ├── server.md ├── services-disclaimer.md ├── troubleshooting.md └── version-policy.md ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── cli │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── assert │ │ │ └── assert.js │ │ ├── autorun │ │ │ └── autorun.js │ │ ├── cli.js │ │ ├── collect │ │ │ ├── collect.js │ │ │ ├── fallback-server.js │ │ │ ├── node-runner.js │ │ │ ├── psi-runner.js │ │ │ └── puppeteer-manager.js │ │ ├── fetch.js │ │ ├── healthcheck │ │ │ └── healthcheck.js │ │ ├── open │ │ │ └── open.js │ │ ├── server │ │ │ └── server.js │ │ ├── upload │ │ │ └── upload.js │ │ ├── utils.js │ │ └── wizard │ │ │ └── wizard.js │ └── test │ │ ├── .eslintrc.js │ │ ├── assert.test.js │ │ ├── autorun-github.test.js │ │ ├── autorun-start-server.test.js │ │ ├── autorun-static-dir.test.js │ │ ├── autorun.test.js │ │ ├── cli.test.js │ │ ├── collect-psi.test.js │ │ ├── collect-puppeteer.test.js │ │ ├── collect-static-dir.test.js │ │ ├── collect.test.js │ │ ├── fallback-server.test.js │ │ ├── fixtures │ │ ├── autorun-github │ │ │ ├── build │ │ │ │ ├── bad.html │ │ │ │ └── good.html │ │ │ ├── lighthouserc.json │ │ │ └── mock-github-server.js │ │ ├── autorun-start-server │ │ │ ├── autorun-server.js │ │ │ └── package.json │ │ ├── autorun-static-dir │ │ │ ├── build │ │ │ │ ├── folder.html │ │ │ │ │ └── invisible.html │ │ │ │ ├── good.html │ │ │ │ └── subdir │ │ │ │ │ └── index.html │ │ │ ├── lighthouserc.json │ │ │ └── public │ │ │ │ └── bad.html │ │ ├── budgets.json │ │ ├── collect-static-dir-autodiscover-limit │ │ │ ├── board.html │ │ │ ├── checkout.html │ │ │ ├── index.html │ │ │ ├── jobs.html │ │ │ ├── shop.html │ │ │ └── team.html │ │ ├── collect-static-dir-with-urls │ │ │ └── child │ │ │ │ └── grandchild.html │ │ ├── collect-static-dir-without-urls │ │ │ ├── checkout.html │ │ │ └── index.html │ │ ├── lighthouserc-extended.json │ │ ├── lighthouserc-invalid-port.json │ │ ├── lighthouserc-matrix.json │ │ ├── lighthouserc.js │ │ ├── lighthouserc.json │ │ ├── psi │ │ │ └── mock-psi-server.js │ │ └── puppeteer │ │ │ ├── auth-server-script.js │ │ │ └── auth-server.js │ │ ├── healthcheck.test.js │ │ ├── server-psi-collect-cron.test.js │ │ ├── server.test.js │ │ ├── test-utils.js │ │ ├── upload-url-hash.test.js │ │ ├── upload.test.js │ │ └── wizard.test.js ├── server │ ├── .npmignore │ ├── .storybook │ │ ├── main.js │ │ ├── preview.css │ │ └── preview.jsx │ ├── README.md │ ├── package.json │ ├── src │ │ ├── api │ │ │ ├── express-utils.js │ │ │ ├── routes │ │ │ │ ├── projects.js │ │ │ │ └── viewer.js │ │ │ ├── statistic-definitions.js │ │ │ └── storage │ │ │ │ ├── auth.js │ │ │ │ ├── sql │ │ │ │ ├── build-model.js │ │ │ │ ├── migrations │ │ │ │ │ ├── 20191008-initial.js │ │ │ │ │ ├── 20191009-project-token.js │ │ │ │ │ ├── 20191029-committed-at.js │ │ │ │ │ ├── 2019110801-project-slug.js │ │ │ │ │ ├── 2019111001-statistic-version.js │ │ │ │ │ ├── 2020030501-admin-token.js │ │ │ │ │ ├── 2020032401-base-branch.js │ │ │ │ │ ├── 2020081801-table-indexes.js │ │ │ │ │ └── 20240122-project-name.js │ │ │ │ ├── project-model.js │ │ │ │ ├── run-model.js │ │ │ │ ├── sql.js │ │ │ │ └── statistic-model.js │ │ │ │ └── storage-method.js │ │ ├── cron │ │ │ ├── delete-old-builds.js │ │ │ ├── psi-collect.js │ │ │ └── utils.js │ │ ├── server.js │ │ └── ui │ │ │ ├── .eslintrc.js │ │ │ ├── app.css │ │ │ ├── app.jsx │ │ │ ├── components │ │ │ ├── async-loader.jsx │ │ │ ├── d3-graph.jsx │ │ │ ├── document-title.jsx │ │ │ ├── dropdown.css │ │ │ ├── dropdown.jsx │ │ │ ├── gauge.css │ │ │ ├── gauge.jsx │ │ │ ├── gauge.stories.jsx │ │ │ ├── lhr-viewer-button.css │ │ │ ├── lhr-viewer-button.jsx │ │ │ ├── lhr-viewer-link.css │ │ │ ├── lhr-viewer-link.jsx │ │ │ ├── loading-spinner.css │ │ │ ├── loading-spinner.jsx │ │ │ ├── loading-spinner.svg │ │ │ ├── markdown.jsx │ │ │ ├── modal.css │ │ │ ├── modal.jsx │ │ │ ├── nbsp.jsx │ │ │ ├── paper.css │ │ │ ├── paper.jsx │ │ │ ├── pill.css │ │ │ ├── pill.jsx │ │ │ ├── plot.jsx │ │ │ ├── plotly.js │ │ │ ├── pwa-gauge.css │ │ │ ├── pwa-gauge.jsx │ │ │ ├── pwa-gauge.stories.jsx │ │ │ ├── redirect.jsx │ │ │ ├── score-delta-badge.css │ │ │ ├── score-delta-badge.jsx │ │ │ ├── score-delta-badge.stories.jsx │ │ │ └── score-icon.jsx │ │ │ ├── entry.jsx │ │ │ ├── favicon.png │ │ │ ├── favicon.svg │ │ │ ├── hooks │ │ │ ├── use-api-data.jsx │ │ │ ├── use-previous-value.jsx │ │ │ └── use-route-params.jsx │ │ │ ├── icons.css │ │ │ ├── index.html │ │ │ ├── layout │ │ │ ├── __mocks__ │ │ │ │ └── page.jsx │ │ │ ├── page-body.jsx │ │ │ ├── page-header.css │ │ │ ├── page-header.jsx │ │ │ ├── page-sidebar.css │ │ │ ├── page-sidebar.jsx │ │ │ └── page.jsx │ │ │ ├── logo.svg │ │ │ ├── routes │ │ │ ├── build-view │ │ │ │ ├── audit-detail │ │ │ │ │ ├── audit-detail-pane.css │ │ │ │ │ ├── audit-detail-pane.jsx │ │ │ │ │ ├── audit-detail-pane.stories.jsx │ │ │ │ │ ├── audit-detail.jsx │ │ │ │ │ ├── simple-details.css │ │ │ │ │ ├── simple-details.jsx │ │ │ │ │ ├── table-details.css │ │ │ │ │ └── table-details.jsx │ │ │ │ ├── audit-list │ │ │ │ │ ├── audit-diff.jsx │ │ │ │ │ ├── audit-group.css │ │ │ │ │ ├── audit-group.jsx │ │ │ │ │ ├── numeric-diff.css │ │ │ │ │ └── numeric-diff.jsx │ │ │ │ ├── build-hash-selector.css │ │ │ │ ├── build-hash-selector.jsx │ │ │ │ ├── build-hash-selector.stories.jsx │ │ │ │ ├── build-selector-header-section.css │ │ │ │ ├── build-selector-header-section.jsx │ │ │ │ ├── build-view-warnings.jsx │ │ │ │ ├── build-view.css │ │ │ │ ├── build-view.jsx │ │ │ │ ├── lhr-comparison-legend.css │ │ │ │ ├── lhr-comparison-legend.jsx │ │ │ │ ├── lhr-comparison-runtime-diff.css │ │ │ │ ├── lhr-comparison-runtime-diff.jsx │ │ │ │ ├── lhr-comparison-scores.css │ │ │ │ ├── lhr-comparison-scores.jsx │ │ │ │ ├── lhr-comparison.css │ │ │ │ ├── lhr-comparison.jsx │ │ │ │ └── lhr-comparison.stories.jsx │ │ │ ├── project-dashboard │ │ │ │ ├── build-list.css │ │ │ │ ├── build-list.jsx │ │ │ │ ├── category-card.css │ │ │ │ ├── category-card.jsx │ │ │ │ ├── getting-started.css │ │ │ │ ├── getting-started.jsx │ │ │ │ ├── graphs │ │ │ │ │ ├── category-score │ │ │ │ │ │ ├── category-score-graph.css │ │ │ │ │ │ ├── category-score-graph.jsx │ │ │ │ │ │ ├── category-score-graph.stories.jsx │ │ │ │ │ │ ├── score-delta-bar-graph.jsx │ │ │ │ │ │ ├── score-distribution-graph.jsx │ │ │ │ │ │ └── score-line-graph.jsx │ │ │ │ │ ├── donut-graph.css │ │ │ │ │ ├── donut-graph.jsx │ │ │ │ │ ├── graph-utils.jsx │ │ │ │ │ ├── hover-card.css │ │ │ │ │ ├── hover-card.jsx │ │ │ │ │ ├── metric-distribution-graph.css │ │ │ │ │ ├── metric-distribution-graph.jsx │ │ │ │ │ ├── metric-line-graph.css │ │ │ │ │ ├── metric-line-graph.jsx │ │ │ │ │ └── metric-line-graph.stories.jsx │ │ │ │ ├── project-category-summaries.css │ │ │ │ ├── project-category-summaries.jsx │ │ │ │ ├── project-dashboard.css │ │ │ │ └── project-dashboard.jsx │ │ │ ├── project-list │ │ │ │ ├── confetti.svg │ │ │ │ ├── project-list.css │ │ │ │ └── project-list.jsx │ │ │ └── project-settings │ │ │ │ ├── project-settings.css │ │ │ │ └── project-settings.jsx │ │ │ └── tooltips.css │ └── test │ │ ├── .eslintrc.js │ │ ├── __mocks__ │ │ └── file-mock.js │ │ ├── api │ │ ├── statistic-definitions.test.js │ │ └── storage │ │ │ └── storage-method.test.js │ │ ├── basic-auth-server.test.js │ │ ├── cron │ │ ├── delete-old-builds.test.js │ │ ├── psi-collect.test.js │ │ └── utils.test.js │ │ ├── e2e │ │ ├── __image_snapshots__ │ │ │ ├── project-dashboard-empty-test-js-project-dashboard-render-the-welcome-screen-should-look-correct-1-snap.png │ │ │ ├── project-dashboard-mixed-v-5-v-6-test-js-project-dashboard-render-the-dashboard-should-look-correct-1-snap.png │ │ │ ├── project-dashboard-mixed-v-5-v-6-test-js-project-dashboard-render-the-dashboard-should-look-correct-on-hover-1-snap.png │ │ │ └── project-dashboard-test-js-project-dashboard-render-the-dashboard-should-look-correct-1-snap.png │ │ ├── project-dashboard-empty.test.js │ │ ├── project-dashboard-mixed-v5-v6.test.js │ │ ├── project-dashboard.test.js │ │ └── steps │ │ │ ├── navigate-to-project.js │ │ │ ├── setup.js │ │ │ └── teardown.js │ │ ├── fixtures │ │ ├── lh-10-1-0-coursehero-a.json │ │ ├── lh-10-1-0-coursehero-b.json │ │ ├── lh-11-4-0-coursehero-a.json │ │ ├── lh-11-4-0-coursehero-b.json │ │ ├── lh-12-0-0-coursehero-a.json │ │ ├── lh-12-0-0-coursehero-b.json │ │ ├── lh-5-6-0-verge-a.json │ │ ├── lh-5-6-0-verge-b.json │ │ ├── lh-6-0-0-coursehero-a.json │ │ ├── lh-6-0-0-coursehero-b.json │ │ ├── lh-6-2-0-coursehero-a.json │ │ ├── lh-6-2-0-coursehero-b.json │ │ ├── lh-6-4-1-coursehero-a.json │ │ ├── lh-6-4-1-coursehero-b.json │ │ ├── lh-7-0-0-coursehero-a.json │ │ ├── lh-7-0-0-coursehero-b.json │ │ ├── lh-7-0-0-coursehero-perf-a.json │ │ ├── lh-8-0-0-coursehero-a.json │ │ ├── lh-8-0-0-coursehero-b.json │ │ ├── lh-9-3-0-coursehero-a.json │ │ ├── lh-9-3-0-coursehero-b.json │ │ ├── lh-subitems-a.json │ │ ├── lh-subitems-b.json │ │ ├── psi-8-0-0-dkdev-a.json │ │ ├── psi-8-0-0-dkdev-b.json │ │ └── seed-data.json │ │ ├── mysql-server.test.js │ │ ├── mysql-socket-path-server.test.js │ │ ├── postgres-server.test.js │ │ ├── postgres-socket-path-server.test.js │ │ ├── server-test-suite.js │ │ ├── sqlite-server.test.js │ │ ├── storybook-setup.js │ │ ├── storybook-teardown.js │ │ ├── test-utils.js │ │ └── ui │ │ ├── .eslintrc.js │ │ ├── __image_snapshots__ │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-default-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1010-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1140-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1200-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-6-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-62-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-641-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-700-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-800-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-930-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-psi-800-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-subitems-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-build-hash-selector-custom-base-branch-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-build-hash-selector-no-branch-builds-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-build-hash-selector-no-master-builds-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-build-hash-selector-select-base-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-build-hash-selector-select-compare-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-default-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1010-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1140-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1200-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-6-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-62-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-641-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-700-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-800-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-930-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-psi-800-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-gauge-default-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-gauge-with-improvement-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-gauge-with-regression-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-default-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-fast-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-installable-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-installable-and-fast-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-and-fast-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-and-installable-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-pwa-gauge-pwa-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-score-delta-badge-improvement-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-score-delta-badge-netural-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-components-score-delta-badge-regression-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-with-hover-card-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-with-version-changes-1-snap.png │ │ ├── storybook-test-jsx-image-storyshots-project-dashboard-metric-line-graph-default-1-snap.png │ │ └── storybook-test-jsx-image-storyshots-project-dashboard-metric-line-graph-with-selected-metric-1-snap.png │ │ ├── components │ │ └── async-loader.test.jsx │ │ ├── routes │ │ ├── build-view │ │ │ ├── build-view.test.jsx │ │ │ └── lhr-comparison.test.jsx │ │ ├── project-dashboard │ │ │ ├── graphs │ │ │ │ └── category-score-graph │ │ │ │ │ └── score-line-graph.test.jsx │ │ │ └── project-dashboard.test.jsx │ │ └── project-list │ │ │ └── project-list.test.jsx │ │ └── storybook.test.jsx ├── utils │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── api-client.js │ │ ├── assertions.js │ │ ├── audit-diff-finder.js │ │ ├── budget.js │ │ ├── budgets-converter.js │ │ ├── build-context.js │ │ ├── child-process-helper.js │ │ ├── lighthouserc.js │ │ ├── lodash.js │ │ ├── markdown.js │ │ ├── presets │ │ │ ├── all.js │ │ │ ├── no-pwa.js │ │ │ └── recommended.js │ │ ├── psi-client.js │ │ ├── psi-runner.js │ │ ├── representative-runs.js │ │ ├── saved-reports.js │ │ └── seed-data │ │ │ ├── dataset-generator.js │ │ │ ├── lhr-generator.js │ │ │ ├── prandom.js │ │ │ ├── sample-report.json │ │ │ └── seed-data.js │ └── test │ │ ├── .eslintrc.js │ │ ├── api-client.test.js │ │ ├── assertions.test.js │ │ ├── audit-diff-finder.test.js │ │ ├── budgets-converter.test.js │ │ ├── build-context.test.js │ │ ├── child-process-helper.test.js │ │ ├── lighthouserc.test.js │ │ ├── lodash.test.js │ │ ├── presets.test.js │ │ ├── psi-client.test.js │ │ ├── representative-runs.test.js │ │ ├── saved-reports.test.js │ │ └── seed-data │ │ ├── lhr-generator.test.js │ │ └── prandom.test.js └── viewer │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── src │ ├── ui │ │ ├── .eslintrc.js │ │ ├── app.css │ │ ├── app.jsx │ │ ├── components │ │ │ ├── lhci-components.jsx │ │ │ ├── report-upload-box.css │ │ │ ├── report-upload-box.jsx │ │ │ ├── toast.css │ │ │ └── toast.jsx │ │ ├── entry.jsx │ │ ├── index.html │ │ └── routes │ │ │ ├── comparison │ │ │ ├── comparison.css │ │ │ └── comparison.jsx │ │ │ └── landing │ │ │ ├── landing.css │ │ │ └── landing.jsx │ └── viewer.js │ └── test │ ├── .eslintrc.js │ ├── e2e │ ├── __image_snapshots__ │ │ ├── different-category-comparison-test-js-viewer-simple-comparison-render-the-comparison-route-should-look-correct-1-snap.png │ │ ├── simple-comparison-test-js-viewer-simple-comparison-render-the-comparison-route-should-look-correct-1-snap.png │ │ └── simple-comparison-test-js-viewer-simple-comparison-render-the-landing-route-should-look-correct-1-snap.png │ ├── different-category-comparison.test.js │ ├── simple-comparison.test.js │ └── steps │ │ ├── setup.js │ │ └── teardown.js │ └── test-utils.js ├── scripts ├── build-app.js ├── ci-dogfood-get-urls.js ├── ci-dogfood.sh ├── deploy-gh-pages.sh ├── diff-seed-fixture.js ├── open-seed-report-in-viewer.js ├── print-changelog.js ├── recreate-build.js ├── release.sh ├── seed-database.js ├── source-map-explorer.sh ├── test-docker.sh ├── test-lh-report.sh ├── update-seed-fixtures.js └── update-version-tags.sh ├── tsconfig.json ├── types ├── assert.d.ts ├── autorun.d.ts ├── collect.d.ts ├── healthcheck.d.ts ├── jest.d.ts ├── lighthouse-logger.d.ts ├── lighthouse.d.ts ├── lighthouserc.d.ts ├── open.d.ts ├── preact.d.ts ├── server.d.ts ├── upload.d.ts ├── utils.d.ts └── wizard.d.ts └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers we support 2 | last 2 Chrome versions 3 | last 2 Firefox versions 4 | last 2 Safari versions 5 | -------------------------------------------------------------------------------- /.eslintrc.tests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['./.eslintrc.js'], 10 | plugins: ['jest'], 11 | env: { 12 | 'jest/globals': true, 13 | }, 14 | rules: { 15 | 'import/no-extraneous-dependencies': 'off', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /.eslintrc.ui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['./.eslintrc.js', 'plugin:react/recommended', 'prettier'], 10 | plugins: ['react'], 11 | env: { 12 | browser: true, 13 | }, 14 | rules: { 15 | 'import/no-extraneous-dependencies': 'off', // false positives with parcel and less problematic in bundle jsx anyway 16 | 'import/no-unresolved': 'off', // doesn't support parcel's resolution algorithm 17 | 'react/prop-types': 'off', 18 | 'no-unused-vars': [ 19 | 'error', 20 | { 21 | vars: 'all', 22 | args: 'after-used', 23 | argsIgnorePattern: '(^reject$|^_$)', 24 | varsIgnorePattern: '(^_$|^h$)', 25 | }, 26 | ], 27 | strict: 'off', 28 | }, 29 | parserOptions: { 30 | ecmaVersion: 2020, 31 | ecmaFeatures: { 32 | jsx: true, 33 | }, 34 | sourceType: 'module', 35 | allowImportExportEverywhere: true, 36 | }, 37 | settings: { 38 | react: { 39 | version: '16.0', // we're not really using react, but this version is pretty close 40 | pragma: 'h', // Preact's pragma is `h` 41 | }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Logs/Screenshots** 24 | If applicable, add LHCI logs or screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Anything else we need to discuss. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 31 | -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | # Always validate the PR title, and ignore the commits 2 | titleOnly: true 3 | # You can define a list of valid scopes 4 | scopes: 5 | - cli 6 | - server 7 | - utils 8 | - viewer 9 | - recipes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tmp.sql 2 | *.tmp.sql-journal 3 | *.tmp.json 4 | .lighthouseci/ 5 | .cache/ 6 | dist/ 7 | node_modules/ 8 | .idea 9 | storybook-static 10 | __diff_output__/ 11 | -------------------------------------------------------------------------------- /.lighthouserc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | ci: { 10 | assert: { 11 | preset: 'lighthouse:recommended', 12 | assertions: { 13 | 'dom-size': ['error', {maxNumericValue: 3000}], 14 | 15 | 'csp-xss': 'off', 16 | 'unsized-images': 'off', 17 | 'uses-rel-preload': 'off', 18 | 'uses-responsive-images': 'off', 19 | 'uses-rel-preconnect': 'off', 20 | 'preload-lcp-image': 'off', 21 | 'offscreen-images': 'off', 22 | 'unused-javascript': 'off', 23 | 24 | label: 'off', 25 | 'color-contrast': 'off', 26 | bypass: 'off', 27 | 'tap-targets': 'off', 28 | 29 | 'apple-touch-icon': 'off', 30 | 'maskable-icon': 'off', 31 | 'installable-manifest': 'off', 32 | 'service-worker': 'off', 33 | 'splash-screen': 'off', 34 | 'themed-omnibox': 'off', 35 | }, 36 | }, 37 | upload: { 38 | urlReplacementPatterns: [ 39 | 's/[0-9a-f]{12}$/HASH/', 40 | 's#:[0-9]{3,5}/#:PORT/#', 41 | 's/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/UUID/ig', 42 | ], 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 100, 4 | "tabWidth": 2, 5 | "semi": true, 6 | "arrowParens": "avoid", 7 | "bracketSpacing": false, 8 | "singleQuote": true 9 | } 10 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | 4 | 5 | ![image](https://user-images.githubusercontent.com/2301202/81859509-f502fc00-952a-11ea-97fc-0592621cc405.png) 6 | 7 | ## Overview 8 | 9 | Lighthouse CI is split into two core packages `@lhci/cli` and `@lhci/server`. The CLI is the primary way users interact with Lighthouse CI. The server module is used to setup the [Lighthouse CI server](./server.md). 10 | 11 | The CLI is broken down into commands that are run in a variety of environments. See the below lists for where we expect different commands to be run. 12 | 13 | **Locally, on a user's device** 14 | 15 | - `wizard` (create new projects on the LHCI server) 16 | - `collect` (run Lighthouse many times) 17 | - `open` (open the median run) 18 | 19 | **Remotely, on CI servers** 20 | 21 | - `autorun` 22 | - `healthcheck` 23 | - `collect` 24 | - `assert` 25 | - `upload` 26 | 27 | **Remotely, on a LHCI server** 28 | 29 | - `server` 30 | - `wizard` (reset tokens) 31 | 32 | ## CI Flow 33 | 34 | The typical CI flow for command execution is `healthcheck` -> `collect` -> `assert` -> `upload`. Each command reads/writes data from the `.lighthouseci/` folder on the local filesystem. This is where `collect` will store reports, `assert` will _read_ reports and _write_ assertion results, and `upload` will _read_ both reports and assertion results to upload to another location such as the temporary public storage service, GitHub (for status checks), or a LHCI server. 35 | 36 | ## Related Documents 37 | 38 | - [Lighthouse Architecture](https://github.com/GoogleChrome/lighthouse/blob/master/docs/architecture.md) 39 | -------------------------------------------------------------------------------- /docs/recipes/docker-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-bullseye-slim 2 | 3 | # Set variable so puppeteer will not try to download chromium 4 | ENV PUPPETEER_SKIP_DOWNLOAD=true 5 | ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable 6 | 7 | # Install utilities 8 | RUN apt-get update --fix-missing && apt-get -y upgrade && apt-get install -y git wget gnupg && apt-get clean 9 | 10 | # Install latest chrome stable package. 11 | RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - 12 | RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 13 | RUN apt-get update \ 14 | && apt-get install -y google-chrome-stable --no-install-recommends \ 15 | && apt-get clean 16 | 17 | # Install Lighthouse CI 18 | RUN npm install -g @lhci/cli@0.14.0 19 | RUN npm install -g lighthouse 20 | 21 | # Install puppeteer 22 | RUN npm install -g puppeteer 23 | 24 | # Setup a user to avoid doing everything as root 25 | RUN groupadd --system lhci && \ 26 | useradd --system --create-home --gid lhci lhci && \ 27 | mkdir --parents /home/lhci/reports && \ 28 | chown --recursive lhci:lhci /home/lhci 29 | 30 | RUN cd /home/lhci/reports && npm link puppeteer 31 | 32 | USER lhci 33 | WORKDIR /home/lhci/reports 34 | 35 | CMD [ "lhci", "--help" ] 36 | -------------------------------------------------------------------------------- /docs/recipes/docker-client/test-in-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | # Create a git repo for us to test. 6 | mkdir /tmp/lhci_client_test 7 | cd /tmp/lhci_client_test 8 | git init 9 | touch README.md 10 | git add README.md 11 | git config --global user.email "lhci@example.com" 12 | git config --global user.name "Robot Lighthouse" 13 | git commit -m 'initial commit' 14 | 15 | # Run our LHCI commands 16 | which lhci 17 | lhci healthcheck 18 | lhci collect --url=https://example.com -n=1 --settings.chromeFlags="--no-sandbox --disable-dev-shm-usage" 19 | lhci upload --target=filesystem 20 | -------------------------------------------------------------------------------- /docs/recipes/docker-client/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | docker image build -t lhci-client . 6 | docker run --name lhci-client-container --detach lhci-client /bin/sleep 1000 7 | 8 | set +e 9 | docker exec -i lhci-client-container bash < test-in-container.sh 10 | EXIT_CODE=$? 11 | set -e 12 | 13 | docker stop lhci-client-container 14 | docker rm lhci-client-container 15 | exit $EXIT_CODE 16 | -------------------------------------------------------------------------------- /docs/recipes/docker-client/update-dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CURRENT_VERSION=$(grep 'lhci/cli' Dockerfile | sed s/.*@//g) 4 | LATEST_VERSION=$(git describe --abbrev=0 --tags | sed s/v//g) 5 | 6 | if [[ "$CURRENT_VERSION" == "$LATEST_VERSION" ]]; then 7 | echo "Version is already at $LATEST_VERSION, exiting..." 8 | exit 0 9 | fi 10 | 11 | printf "Previous version is $CURRENT_VERSION\n" 12 | printf "Publish docker version $LATEST_VERSION?\n" 13 | read -n 1 -p "Press any key to continue, Ctrl+C to exit..." 14 | 15 | printf "\nUpdating local files...\n\n" 16 | 17 | sed -i "" "s/$CURRENT_VERSION/$LATEST_VERSION/" Dockerfile 18 | git --no-pager diff . 19 | 20 | printf "Continue publishing $LATEST_VERSION?\n" 21 | read -n 1 -p "Press any key to continue, Ctrl+C to exit..." 22 | 23 | printf "\nPublishing!" 24 | sleep 2 # Give the user one more chance to Ctrl+C 25 | 26 | docker image build -t lhci-client . 27 | docker tag lhci-client:latest "patrickhulce/lhci-client:$LATEST_VERSION" 28 | docker tag "patrickhulce/lhci-client:$LATEST_VERSION" patrickhulce/lhci-client:latest 29 | docker push "patrickhulce/lhci-client:$LATEST_VERSION" 30 | docker push patrickhulce/lhci-client:latest 31 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-bullseye-slim 2 | 3 | # Install utilities 4 | RUN apt-get update --fix-missing && apt-get install -y python build-essential && apt-get clean 5 | 6 | WORKDIR /usr/src/lhci 7 | COPY package.json . 8 | COPY lighthouserc.json . 9 | RUN npm install 10 | 11 | EXPOSE 9001 12 | CMD [ "npm", "start" ] 13 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/kubernetes/lhci-data-claim.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: lhci-data-claim 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | resources: 9 | requests: 10 | storage: 50Gi 11 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/kubernetes/lhci-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: lhci-server 5 | spec: 6 | selector: 7 | matchLabels: 8 | name: lhci-server 9 | # there can be only one pod at a time because of the persistent volume claim 10 | strategy: 11 | type: Recreate 12 | template: 13 | metadata: 14 | name: lhci-pod 15 | labels: 16 | name: lhci-server 17 | spec: 18 | containers: 19 | - name: lhci-server 20 | image: docker.io/patrickhulce/lhci-server:0.14.0 21 | volumeMounts: 22 | - mountPath: '/data' 23 | name: lhci-data-volume 24 | # required for ingress health checks to work 25 | ports: 26 | - containerPort: 9001 27 | readinessProbe: 28 | httpGet: 29 | path: /healthz 30 | port: 9001 31 | volumes: 32 | - name: lhci-data-volume 33 | persistentVolumeClaim: 34 | claimName: lhci-data-claim 35 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/kubernetes/lhci-ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: lhci-server 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: 9001 9 | selector: 10 | name: lhci-server 11 | type: NodePort 12 | --- 13 | apiVersion: extensions/v1beta1 14 | kind: Ingress 15 | metadata: 16 | name: lhci-server 17 | annotations: 18 | kubernetes.io/ingress.global-static-ip-name: lhci-example-com 19 | networking.gke.io/managed-certificates: lhci-example-com 20 | spec: 21 | backend: 22 | serviceName: lhci-server 23 | servicePort: 80 24 | --- 25 | apiVersion: networking.gke.io/v1beta2 26 | kind: ManagedCertificate 27 | metadata: 28 | name: lhci-example-com 29 | spec: 30 | domains: 31 | - lhci.example.com 32 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/kubernetes/lhci-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: lhci-server 5 | spec: 6 | ports: 7 | - port: 80 8 | targetPort: 9001 9 | selector: 10 | name: lhci-server 11 | type: LoadBalancer 12 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/lighthouserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "server": { 4 | "port": 9001, 5 | "storage": { 6 | "storageMethod": "sql", 7 | "sqlDialect": "sqlite", 8 | "sqlDatabasePath": "/data/lhci.db" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lhci", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "start": "lhci server --config=./lighthouserc.json" 6 | }, 7 | "dependencies": { 8 | "@lhci/cli": "0.14.0", 9 | "@lhci/server": "0.14.0", 10 | "mysql2": "^2.1.0", 11 | "pg": "^8.2.1", 12 | "pg-hstore": "^2.3.3", 13 | "sqlite3": "^5.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/test-in-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | apt-get update && apt-get install -y curl 6 | curl -vvvv http://localhost:9001/version 7 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | docker image build -t lhci-server . 6 | docker volume create lhci-data-test 7 | docker run --name lhci-server-container --mount='source=lhci-data-test,target=/data' --detach lhci-server 8 | 9 | set +e 10 | docker exec -i lhci-server-container bash < test-in-container.sh 11 | EXIT_CODE=$? 12 | set -e 13 | 14 | docker logs lhci-server-container 15 | docker stop lhci-server-container 16 | docker rm lhci-server-container 17 | docker volume rm lhci-data-test 18 | exit $EXIT_CODE 19 | -------------------------------------------------------------------------------- /docs/recipes/docker-server/update-dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CURRENT_VERSION=$(node -e "console.log(require('./package.json').dependencies['@lhci/server'])") 4 | LATEST_VERSION=$(git describe --abbrev=0 --tags | sed s/v//g) 5 | 6 | if [[ "$CURRENT_VERSION" == "$LATEST_VERSION" ]]; then 7 | echo "Version is already at $LATEST_VERSION, exiting..." 8 | exit 0 9 | fi 10 | 11 | printf "Previous version is $CURRENT_VERSION\n" 12 | printf "Publish docker version $LATEST_VERSION?\n" 13 | read -n 1 -p "Press any key to continue, Ctrl+C to exit..." 14 | 15 | printf "\nUpdating local files...\n\n" 16 | 17 | sed -i "" "s/$CURRENT_VERSION/$LATEST_VERSION/" package.json kubernetes/lhci-deployment.yml 18 | git --no-pager diff . 19 | 20 | printf "Continue publishing $LATEST_VERSION?\n" 21 | read -n 1 -p "Press any key to continue, Ctrl+C to exit..." 22 | 23 | printf "\nPublishing!" 24 | sleep 2 # Give the user one more chance to Ctrl+C 25 | 26 | docker image build -t lhci . 27 | docker tag lhci:latest "patrickhulce/lhci-server:$LATEST_VERSION" 28 | docker tag "patrickhulce/lhci-server:$LATEST_VERSION" patrickhulce/lhci-server:latest 29 | docker push "patrickhulce/lhci-server:$LATEST_VERSION" 30 | docker push patrickhulce/lhci-server:latest 31 | -------------------------------------------------------------------------------- /docs/recipes/heroku-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lhci", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "dependencies": { 6 | "@lhci/server": "0.9.x", 7 | "pg": "^8.2.1", 8 | "pg-hstore": "^2.3.3" 9 | }, 10 | "engines": { 11 | "node": "14.x" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/recipes/heroku-server/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const {createServer} = require('@lhci/server'); 9 | 10 | console.log('Starting server...'); 11 | createServer({ 12 | port: process.env.PORT, 13 | storage: { 14 | storageMethod: 'sql', 15 | sqlDialect: 'postgres', 16 | sqlConnectionSsl: true, 17 | // Unfortunately, Heroku free-tier does not have verifiable certificates. 18 | // See https://help.heroku.com/3DELT3RK/why-can-t-my-third-party-utility-connect-to-heroku-postgres-with-ssl for why. 19 | sqlDialectOptions: {ssl: {rejectUnauthorized: false}}, 20 | sqlConnectionUrl: process.env.DATABASE_URL, 21 | }, 22 | }).then(({port}) => console.log('Listening on port', port)); 23 | -------------------------------------------------------------------------------- /docs/recipes/lhci-server-vpn-proxy/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443; 3 | server_name public-proxy.example.com; 4 | # required to pass LHCI CLI healthcheck 5 | location = /lighthouse/version { 6 | proxy_set_header Host https://lhc-over-vpn.example.com; 7 | proxy_ssl_name https://lhc-over-vpn.example.com; 8 | set $upstream_endpoint https://lhc-over-vpn.example.com; 9 | proxy_pass $upstream_endpoint/version; 10 | } 11 | # match only /v1/* endpoints on LHCI server 12 | location ~ ^/lighthouse/(v1.*) { 13 | proxy_set_header Host https://lhc-over-vpn.example.com; 14 | proxy_ssl_name https://lhc-over-vpn.example.com; 15 | set $upstream_endpoint https://lhc-over-vpn.example.com; 16 | proxy_pass $upstream_endpoint/v1/$new_request_uri$is_args$args; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/recipes/puppeteer-example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /** 9 | * @param {puppeteer.Browser} browser 10 | * @param {{url: string, options: LHCI.CollectCommand.Options}} context 11 | */ 12 | module.exports = async (browser, context) => { 13 | // launch browser for LHCI 14 | const page = await browser.newPage(); 15 | 16 | await page.goto("https://example.com"); 17 | 18 | // close session for next run 19 | await page.close(); 20 | }; 21 | -------------------------------------------------------------------------------- /docs/services-disclaimer.md: -------------------------------------------------------------------------------- 1 | # Lighthouse CI Services 2 | 3 | The services associated with Lighthouse CI are not operated by Google LLC or any of its subsidiaries. Please consider the terms and privacy policies of each service carefully before deciding to use it. 4 | 5 | Eris Ventures LLC is a Google partner collaborating on projects such as Lighthouse CI. 6 | 7 | ## GitHub App 8 | 9 | The APIs associated with the GitHub App are operated by Eris Ventures LLC. Only the minimal permissions necessary are requested that allow setting status checks, the contents of your repository cannot be read. The GitHub App stores only the ID of the GitHub installation, the access token required to set the status, and the date of installation. No personal information is stored in connection with the GitHub App. 10 | 11 | By using the GitHub App you agree to Eris Ventures' [terms of service](https://eris.ventures/lhci-tos.txt) and [privacy policy](https://eris.ventures/lhci-privacy.txt). 12 | 13 | ## Temporary Public Storage 14 | 15 | The APIs associated with the temporary public storage service are operated by Eris Ventures LLC. Any data uploaded to the temporary public storage service is considered public information that can be accessed by anyone in the world at any time for any reason. Data will be retained for a short period of time lasting anywhere from 3 days to 5 weeks depending on available capacity. No personal information is stored in connection with temporary public storage. 16 | 17 | By using the temporary public storage service you agree to Eris Ventures' [terms of service](https://eris.ventures/lhci-tos.txt) and [privacy policy](https://eris.ventures/lhci-privacy.txt). You may not upload any sensitive, unlawful, obscene, or any other information that violates the [terms of service](https://eris.ventures/lhci-tos.txt). 18 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | preset: 'ts-jest/presets/js-with-ts-esm', 10 | transform: { 11 | '^.+\\.m?[tj]sx?$': [ 12 | 'ts-jest', 13 | { 14 | // Disable typechecking. 15 | diagnostics: false, 16 | isolatedModules: true, 17 | }, 18 | ], 19 | }, 20 | transformIgnorePatterns: ['node_modules/@storybook/.*'], 21 | testEnvironment: 'node', 22 | testRunner: require.resolve('jest-circus/runner'), 23 | globalSetup: require.resolve('./packages/server/test/storybook-setup.js'), 24 | globalTeardown: require.resolve('./packages/server/test/storybook-teardown.js'), 25 | moduleNameMapper: { 26 | '\\.css$': 'identity-obj-proxy', 27 | '\\.(svg|png|jpg|jpeg)$': '/packages/server/test/__mocks__/file-mock.js', 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "packages": ["packages/*"], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli/.npmignore: -------------------------------------------------------------------------------- 1 | *.tmp.sql 2 | *.tmp.json 3 | .lighthouseci/ 4 | .cache/ 5 | test/ 6 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # Lighthouse CI CLI 2 | 3 | [![npm version](https://badge.fury.io/js/%40lhci%2Fcli.svg)](https://badge.fury.io/js/%40lhci%2Fcli) 4 | 5 | ## About 6 | 7 | The Lighthouse CI CLI enables running Lighthouse from various CI environments. Read the [Lighthouse CI docs](https://github.com/GoogleChrome/lighthouse-ci/blob/main/README.md) to learn more. 8 | 9 | ## Acronyms 10 | 11 | - CI: Continuous Integration 12 | - CLI: Command Line Interface 13 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lhci/cli", 3 | "version": "0.1.0", 4 | "license": "Apache-2.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/GoogleChrome/lighthouse-ci.git" 8 | }, 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "bin": { 13 | "lhci": "./src/cli.js" 14 | }, 15 | "dependencies": { 16 | "@lhci/utils": "0.1.0", 17 | "chrome-launcher": "^0.13.4", 18 | "compression": "^1.7.4", 19 | "debug": "^4.3.1", 20 | "express": "^4.17.1", 21 | "proxy-agent": "^6.4.0", 22 | "inquirer": "^6.3.1", 23 | "isomorphic-fetch": "^3.0.0", 24 | "lighthouse": "12.1.0", 25 | "lighthouse-logger": "1.2.0", 26 | "open": "^7.1.0", 27 | "tmp": "^0.1.0", 28 | "uuid": "^8.3.1", 29 | "yargs": "^15.4.1", 30 | "yargs-parser": "^13.1.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/cli/src/collect/psi-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const PsiClient = require('@lhci/utils/src/psi-client.js'); 9 | 10 | class PsiRunner { 11 | /** 12 | * @param {string} url 13 | * @param {Partial} [options] 14 | * @return {Promise} 15 | */ 16 | async run(url, options = {}) { 17 | const apiKey = options.psiApiKey; 18 | if (!apiKey) throw new Error('PSI API key must be provided to use the PSI runner'); 19 | const client = new PsiClient({apiKey, endpointURL: options.psiApiEndpoint}); 20 | return JSON.stringify(await client.run(url, {strategy: options.psiStrategy})); 21 | } 22 | 23 | /** 24 | * @param {string} url 25 | * @param {Partial} [options] 26 | * @return {Promise} 27 | */ 28 | async runUntilSuccess(url, options = {}) { 29 | /** @type {Array} */ 30 | const attempts = []; 31 | 32 | while (attempts.length < 3) { 33 | try { 34 | return await this.run(url, options); 35 | } catch (err) { 36 | attempts.push(err); 37 | } 38 | } 39 | 40 | throw attempts[0]; 41 | } 42 | } 43 | 44 | module.exports = PsiRunner; 45 | -------------------------------------------------------------------------------- /packages/cli/src/fetch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2021 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const fetch = require('isomorphic-fetch'); 9 | const {ProxyAgent} = require('proxy-agent'); 10 | 11 | /** @type import('isomorphic-fetch') */ 12 | module.exports = (url, options) => { 13 | /** @type {Parameters[1] & { agent?: import('proxy-agent').ProxyAgent }} */ 14 | const instanceOptions = { 15 | ...options, 16 | }; 17 | 18 | if ( 19 | !instanceOptions.agent && 20 | (process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.NO_PROXY) 21 | ) { 22 | instanceOptions.agent = new ProxyAgent(); 23 | } 24 | 25 | return fetch(url, instanceOptions); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/cli/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../.eslintrc.tests.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/cli/test/autorun.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const {getOverrideArgsForCommand} = require('../src/autorun/autorun.js'); 11 | 12 | describe('getOverrideArgsForCommand', () => { 13 | it('should filter to the correct collect arguments', async () => { 14 | const args = ['--collect.url=http://example.com', '--upload.target=temporary-public-storage']; 15 | const out = getOverrideArgsForCommand('collect', args); 16 | expect(out).toEqual(['--url=http://example.com']); 17 | }); 18 | 19 | it('pass through shared arguments', async () => { 20 | const args = ['--no-lighthouserc', '--noLighthouserc']; 21 | const out = getOverrideArgsForCommand('collect', args); 22 | expect(out).toEqual(['--no-lighthouserc', '--noLighthouserc']); 23 | }); 24 | 25 | it('only support `=` syntax', async () => { 26 | const args = ['--upload.target', 'lhci']; 27 | const out = getOverrideArgsForCommand('upload', args); 28 | expect(out).toEqual(['--target']); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-github/build/bad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bad test page for autorun usage 6 | 7 | 8 | test 9 | 10 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-github/build/good.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | good test page for autorun usage 6 | 7 | 8 | 9 | test 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-github/lighthouserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "collect": { 4 | "numberOfRuns": 1 5 | }, 6 | "assert": { 7 | "assertMatrix": [ 8 | { 9 | "assertions": { 10 | "viewport": "error" 11 | } 12 | } 13 | ], 14 | "includePassedAssertions": true 15 | }, 16 | "upload": { 17 | "target": "temporary-public-storage" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-start-server/autorun-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const express = require('express'); 9 | const app = express(); 10 | app.get('/', (_, res) => { 11 | res.send(` 12 | 13 | 14 | 15 | 16 | good test page for autorun usage 17 | 18 | 19 | test 20 | 21 | 22 | `); 23 | }); 24 | 25 | const {SERVER_START_MESSAGE = 'Server listening on port...', SERVER_START_PORT = 52425} = 26 | process.env; 27 | 28 | app.listen(SERVER_START_PORT, () => process.stdout.write(SERVER_START_MESSAGE)); 29 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-start-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "serve:lhci": "node autorun-server.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-static-dir/build/folder.html/invisible.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | autorun should not see this page 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-static-dir/build/good.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | good test page for autorun usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-static-dir/build/subdir/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | good test page for autorun usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-static-dir/lighthouserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "assert": { 4 | "assertions": { 5 | "viewport": "error" 6 | } 7 | }, 8 | "upload": { 9 | "target": "temporary-public-storage" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/autorun-static-dir/public/bad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bad test page for autorun usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/budgets.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "resourceSizes": [{"resourceType": "script", "budget": 1}] 4 | } 5 | ] 6 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-autodiscover-limit/board.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | board test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-autodiscover-limit/checkout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | checkout test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-autodiscover-limit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-autodiscover-limit/jobs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jobs test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-autodiscover-limit/shop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | shop test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-autodiscover-limit/team.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | team test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-with-urls/child/grandchild.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | grandchild test page 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-without-urls/checkout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | checkout test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/collect-static-dir-without-urls/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | index test page for staticDistDir usage 6 | 7 | 8 | test 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/lighthouserc-extended.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "extends": "./lighthouserc.json", 4 | "assert": { 5 | "assertions": { 6 | "first-contentful-paint": ["error", {"maxNumericValue": 1}] 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/lighthouserc-invalid-port.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "server": { 4 | "port": "foo" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/lighthouserc-matrix.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "assert": { 4 | "assertMatrix": [ 5 | { 6 | "matchingUrlPattern": "app", 7 | "assertions": { 8 | "prioritize-lcp-image": "error" 9 | } 10 | }, 11 | { 12 | "matchingUrlPattern": "non-matching", 13 | "assertions": { 14 | "first-contentful-paint": ["error", {"maxNumericValue": 1}] 15 | } 16 | } 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/lighthouserc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | ci: { 10 | assert: { 11 | assertions: { 12 | 'speed-index': ['error', {minScore: 0.8}], 13 | interactive: ['error', {minScore: 0.8}], 14 | 'performance-budget': ['error', {minScore: 1}], 15 | }, 16 | }, 17 | collect: { 18 | numberOfRuns: 2, 19 | settings: { 20 | budgets: [{resourceSizes: [{resourceType: 'script', budget: 1}]}], 21 | }, 22 | }, 23 | upload: { 24 | serverBaseUrl: 'http://localhost:9009', 25 | }, 26 | server: { 27 | psiCollectCron: process.env.PSI_API_KEY 28 | ? { 29 | psiApiKey: process.env.PSI_API_KEY, 30 | sites: [ 31 | { 32 | projectSlug: 'lighthouse-dashboard', 33 | schedule: '*/2 * * * *', 34 | urls: ['https://example.com'], 35 | }, 36 | ], 37 | } 38 | : undefined, 39 | port: 9009, 40 | storage: { 41 | sqlDatabasePath: 'cli-test-fixtures.tmp.sql', 42 | }, 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/lighthouserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ci": { 3 | "assert": { 4 | "assertions": { 5 | "speed-index": ["error", {"minScore": 0.8}], 6 | "interactive": ["error", {"minScore": 0.8}], 7 | "prioritize-lcp-image": ["error", {"minScore": 1}] 8 | } 9 | }, 10 | "collect": { 11 | "numberOfRuns": 2, 12 | "settings": { 13 | "budgets": [{"resourceSizes": [{"resourceType": "script", "budget": 1}]}] 14 | } 15 | }, 16 | "upload": { 17 | "serverBaseUrl": "http://localhost:9009" 18 | }, 19 | "server": { 20 | "port": 9009, 21 | "storage": { 22 | "storageMethod": "sql", 23 | "sqlDialect": "sqlite", 24 | "sqlDatabasePath": "cli-test-fixtures.tmp.sql" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/puppeteer/auth-server-script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /** @param {import('puppeteer').Browser} */ 9 | module.exports = async browser => { 10 | const page = await browser.newPage(); 11 | await page.goto('http://localhost:52426/login'); 12 | }; 13 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/puppeteer/auth-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const fs = require('fs'); 9 | const express = require('express'); 10 | const app = express(); 11 | app.get('/', (req, res) => { 12 | const cookies = (req.header('cookie') || '').match(/\w+=/g) || []; 13 | if (!cookies.includes('loggedin=')) { 14 | res.status(401); 15 | res.send(`Unauthorized`); 16 | return; 17 | } 18 | 19 | const userAgent = req.header('user-agent') || ''; 20 | if (!userAgent.includes('lighthouseci')) { 21 | fs.writeFileSync('ua.tmp.json', JSON.stringify({userAgent})); 22 | res.status(500); 23 | res.send(`Invalid UA`); 24 | return; 25 | } 26 | 27 | res.send(` 28 | 29 | 30 | 31 | 32 | good test page for collect usage 33 | 34 | 35 | test 36 | 37 | 38 | `); 39 | }); 40 | 41 | app.get('/login', (_, res) => { 42 | res.cookie('loggedin', '1', {expires: new Date(Date.now() + 15 * 60e3), httpOnly: true}); 43 | res.redirect('/'); 44 | }); 45 | 46 | app.get('/public', (_, res) => res.send('

Hello

')); 47 | 48 | app.listen(52426, () => process.stdout.write('Listening...')); 49 | -------------------------------------------------------------------------------- /packages/server/.npmignore: -------------------------------------------------------------------------------- 1 | *.tmp.sql 2 | *.tmp.json 3 | .lighthouseci/ 4 | .cache/ 5 | test/ 6 | -------------------------------------------------------------------------------- /packages/server/.storybook/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict';; 7 | import { dirname, join } from "path"; 8 | 9 | module.exports = { 10 | stories: ['../src/**/*.stories.jsx'], 11 | addons: [getAbsolutePath("@storybook/addon-actions"), getAbsolutePath("@storybook/addon-links")], 12 | 13 | framework: { 14 | name: getAbsolutePath("@storybook/preact-webpack5"), 15 | options: {} 16 | }, 17 | 18 | docs: { 19 | autodocs: true 20 | } 21 | }; 22 | 23 | function getAbsolutePath(value) { 24 | return dirname(require.resolve(join(value, "package.json"))); 25 | } 26 | -------------------------------------------------------------------------------- /packages/server/.storybook/preview.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | @import 'https://fonts.googleapis.com/css?family=Roboto:400,500&display=block'; 8 | @import 'https://fonts.googleapis.com/css?family=Roboto+Mono:400,500&display=block'; 9 | @import 'https://fonts.googleapis.com/icon?family=Material+Icons'; 10 | -------------------------------------------------------------------------------- /packages/server/.storybook/preview.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const {h} = require('preact'); 9 | 10 | require('./preview.css'); 11 | require('../src/ui/app.css'); 12 | 13 | export const parameters = { 14 | layout: 'centered', 15 | }; 16 | 17 | export const decorators = [ 18 | storyFn => ( 19 |
20 | {storyFn()} 21 |
22 | ), 23 | ]; 24 | -------------------------------------------------------------------------------- /packages/server/README.md: -------------------------------------------------------------------------------- 1 | # Lighthouse CI Server (@lhci/server) 2 | 3 | [![npm version](https://badge.fury.io/js/%40lhci%2Fserver.svg)](https://badge.fury.io/js/%40lhci%2Fserver) 4 | 5 | ## About 6 | 7 | The Lighthouse CI Server enables running a server to display Lighthouse CI results. Read the [Lighthouse CI docs](https://github.com/GoogleChrome/lighthouse-ci/blob/main/README.md) and specifically the [LHCI Server docs](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md) to learn more. 8 | 9 | ## Acronyms 10 | 11 | - CI: Continuous Integration 12 | -------------------------------------------------------------------------------- /packages/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lhci/server", 3 | "main": "./src/server.js", 4 | "version": "0.1.0", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/GoogleChrome/lighthouse-ci.git" 9 | }, 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "scripts": { 14 | "clean": "rm -rf ./dist ./storybook-static", 15 | "build": "npm run build:esbuild && npm run build:storybook", 16 | "build:esbuild": "node ../../scripts/build-app.js build ./src/ui/index.html ./dist /app/", 17 | "build:watch": "node ../../scripts/build-app.js watch ./src/ui/index.html ./dist /app/", 18 | "build:source-map-explorer": "npm run clean && npm run build && ../../scripts/source-map-explorer.sh", 19 | "build:storybook": "storybook build", 20 | "start:storybook": "storybook dev -p 6006" 21 | }, 22 | "dependencies": { 23 | "@lhci/utils": "0.1.0", 24 | "bluebird": "^3.7.2", 25 | "body-parser": "^1.18.3", 26 | "compression": "^1.7.4", 27 | "cron": "^1.8.2", 28 | "dayjs": "^1.8.28", 29 | "debug": "^4.3.1", 30 | "express": "^4.16.4", 31 | "express-basic-auth": "^1.2.0", 32 | "morgan": "^1.9.1", 33 | "sequelize": "^6.35.2", 34 | "umzug": "^3.4.0", 35 | "uuid": "^8.3.1" 36 | }, 37 | "devDependencies": { 38 | "clsx": "^1.0.4", 39 | "d3": "^5.15.0", 40 | "plotly.js": "^1.48.3", 41 | "preact": "^10.19.3", 42 | "preact-jsx-runtime": "^1.2.0", 43 | "preact-async-route": "^2.2.1", 44 | "preact-router": "^3.1.0", 45 | "@fontsource/material-icons": "^4.4.5", 46 | "@fontsource/roboto": "^4.4.5", 47 | "@fontsource/roboto-mono": "^4.4.5" 48 | }, 49 | "alias": { 50 | "isomorphic-fetch": "clsx" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/server/src/api/routes/viewer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2024 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const express = require('express'); 9 | const {handleAsyncError} = require('../express-utils.js'); 10 | 11 | /** 12 | * @param {LHCI.ServerCommand.Options} options 13 | * @return {import('express').Router} 14 | */ 15 | function createRouter(options) { 16 | const router = express.Router(); // eslint-disable-line new-cap 17 | 18 | // GET /viewer/origin 19 | router.get( 20 | '/origin', 21 | handleAsyncError(async (_, res) => { 22 | res.json({viewerOrigin: options.viewer?.origin}); 23 | }) 24 | ); 25 | 26 | return router; 27 | } 28 | 29 | module.exports = createRouter; 30 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const crypto = require('crypto'); 9 | 10 | module.exports = { 11 | /** Generates a cryptographically psuedorandom alphanumeric string of length 40. @return {string} */ 12 | generateAdminToken() { 13 | return crypto 14 | .randomBytes(30) 15 | .toString('base64') 16 | .replace(/[^a-z0-9]/gi, 'l'); 17 | }, 18 | /** 19 | * Hashes an admin token with a given salt. In v0.4.x and earlier, salt is the projectId. 20 | * @param {string} token 21 | * @param {string} salt 22 | */ 23 | hashAdminToken(token, salt) { 24 | const hash = crypto.createHmac('sha256', salt); 25 | hash.update(token); 26 | return hash.digest('hex'); 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/build-model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-disable new-cap */ 9 | 10 | const Sequelize = require('sequelize'); 11 | 12 | /** @type {import('sequelize').Model} */ 13 | const ModelRef = /** @type {any} */ (undefined); 14 | 15 | /** @type {LHCI.ServerCommand.TableDefinition} */ 16 | const attributes = { 17 | id: {type: Sequelize.UUID(), primaryKey: true}, 18 | projectId: {type: Sequelize.UUID(), references: {model: ModelRef, key: 'id'}}, 19 | lifecycle: {type: Sequelize.STRING(40)}, 20 | hash: {type: Sequelize.STRING(40)}, 21 | branch: {type: Sequelize.STRING(40)}, 22 | commitMessage: {type: Sequelize.STRING(80)}, 23 | author: {type: Sequelize.STRING(256)}, 24 | avatarUrl: {type: Sequelize.STRING(256)}, 25 | ancestorHash: {type: Sequelize.STRING(40)}, 26 | externalBuildUrl: {type: Sequelize.STRING(256)}, 27 | runAt: {type: Sequelize.DATE(6)}, // should mostly be equal to createdAt but modifiable by the consumer 28 | committedAt: {type: Sequelize.DATE(6)}, 29 | ancestorCommittedAt: {type: Sequelize.DATE(6)}, 30 | createdAt: {type: Sequelize.DATE(6)}, 31 | updatedAt: {type: Sequelize.DATE(6)}, 32 | }; 33 | 34 | module.exports = { 35 | tableName: 'builds', 36 | attributes, 37 | indexes: [], 38 | }; 39 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/20191009-project-token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | 10 | /* eslint-disable new-cap */ 11 | 12 | module.exports = { 13 | /** 14 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 15 | */ 16 | up: async ({queryInterface}) => { 17 | await queryInterface.addColumn('projects', 'token', {type: Sequelize.UUID()}); 18 | await queryInterface.bulkUpdate( 19 | 'projects', 20 | {token: Sequelize.col('id')}, 21 | {token: null}, 22 | {type: Sequelize.QueryTypes.BULKUPDATE} 23 | ); 24 | }, 25 | /** 26 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 27 | */ 28 | down: async ({queryInterface}) => { 29 | await queryInterface.removeColumn('projects', 'token'); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/20191029-committed-at.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | 10 | /* eslint-disable new-cap */ 11 | 12 | module.exports = { 13 | /** 14 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 15 | */ 16 | up: async ({queryInterface}) => { 17 | await queryInterface.addColumn('builds', 'committedAt', {type: Sequelize.DATE(6)}); 18 | await queryInterface.addColumn('builds', 'ancestorCommittedAt', {type: Sequelize.DATE(6)}); 19 | }, 20 | /** 21 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 22 | */ 23 | down: async ({queryInterface}) => { 24 | await queryInterface.removeColumn('builds', 'committedAt'); 25 | await queryInterface.removeColumn('builds', 'ancestorCommittedAt'); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/2019110801-project-slug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | 10 | /* eslint-disable new-cap */ 11 | 12 | module.exports = { 13 | /** 14 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 15 | */ 16 | up: async ({queryInterface}) => { 17 | await queryInterface.addColumn('projects', 'slug', {type: Sequelize.STRING(40)}); 18 | await queryInterface.bulkUpdate( 19 | 'projects', 20 | {slug: Sequelize.col('id')}, 21 | {slug: null}, 22 | {type: Sequelize.QueryTypes.BULKUPDATE} 23 | ); 24 | await queryInterface.addIndex('projects', { 25 | // @ts-ignore - Sequelize types are out of date 26 | name: 'projects_unique_slug', 27 | unique: true, 28 | fields: ['slug'], 29 | }); 30 | }, 31 | /** 32 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 33 | */ 34 | down: async ({queryInterface}) => { 35 | await queryInterface.removeIndex('projects', 'projects_unique_slug'); 36 | await queryInterface.removeColumn('projects', 'slug'); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/2019111001-statistic-version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | 10 | /* eslint-disable new-cap */ 11 | 12 | module.exports = { 13 | /** 14 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 15 | */ 16 | up: async ({queryInterface}) => { 17 | await queryInterface.addColumn('statistics', 'version', {type: Sequelize.DECIMAL(8, 2)}); 18 | await queryInterface.bulkUpdate( 19 | 'statistics', 20 | {version: 1}, 21 | {version: null}, 22 | {type: Sequelize.QueryTypes.BULKUPDATE} 23 | ); 24 | }, 25 | /** 26 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 27 | */ 28 | down: async ({queryInterface}) => { 29 | await queryInterface.removeColumn('statistics', 'version'); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/2020030501-admin-token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | const {hashAdminToken, generateAdminToken} = require('../../auth.js'); 10 | 11 | /* eslint-disable new-cap */ 12 | 13 | module.exports = { 14 | /** 15 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 16 | */ 17 | up: async ({queryInterface}) => { 18 | await queryInterface.addColumn('projects', 'adminToken', {type: Sequelize.STRING(64)}); 19 | await queryInterface.bulkUpdate( 20 | 'projects', 21 | // Because of the useless salt, this will be an invalid admin token that requires resetting 22 | // via the wizard command, but our goal is exactly to create an initial token no one can guess. 23 | {adminToken: hashAdminToken(generateAdminToken(), '0')}, 24 | {adminToken: null}, 25 | {type: Sequelize.QueryTypes.BULKUPDATE} 26 | ); 27 | }, 28 | /** 29 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 30 | */ 31 | down: async ({queryInterface}) => { 32 | await queryInterface.removeColumn('projects', 'adminToken'); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/2020032401-base-branch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | 10 | /* eslint-disable new-cap */ 11 | 12 | module.exports = { 13 | /** 14 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 15 | */ 16 | up: async ({queryInterface}) => { 17 | await queryInterface.addColumn('projects', 'baseBranch', {type: Sequelize.STRING(80)}); 18 | await queryInterface.bulkUpdate( 19 | 'projects', 20 | {baseBranch: 'master'}, 21 | {baseBranch: null}, 22 | {type: Sequelize.QueryTypes.BULKUPDATE} 23 | ); 24 | }, 25 | /** 26 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 27 | */ 28 | down: async ({queryInterface}) => { 29 | await queryInterface.removeColumn('projects', 'baseBranch'); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/migrations/20240122-project-name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2024 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const Sequelize = require('sequelize'); 9 | 10 | /* eslint-disable new-cap */ 11 | 12 | module.exports = { 13 | /** 14 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 15 | */ 16 | up: async ({queryInterface}) => { 17 | await queryInterface.changeColumn('projects', 'name', { 18 | type: Sequelize.STRING(256), 19 | }); 20 | }, 21 | /** 22 | * @param {{queryInterface: import('sequelize').QueryInterface, options: LHCI.ServerCommand.StorageOptions}} _ 23 | */ 24 | down: async ({queryInterface}) => { 25 | await queryInterface.changeColumn('projects', 'name', { 26 | type: Sequelize.STRING(40), 27 | }); 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/project-model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-disable new-cap */ 9 | 10 | const Sequelize = require('sequelize'); 11 | 12 | /** @type {LHCI.ServerCommand.TableDefinition} */ 13 | const attributes = { 14 | id: {type: Sequelize.UUID(), primaryKey: true}, 15 | name: {type: Sequelize.STRING(256)}, 16 | slug: {type: Sequelize.STRING(40)}, 17 | externalUrl: {type: Sequelize.STRING(256)}, 18 | token: {type: Sequelize.UUID()}, 19 | baseBranch: {type: Sequelize.STRING(80)}, 20 | adminToken: {type: Sequelize.STRING(64)}, 21 | createdAt: {type: Sequelize.DATE(6)}, 22 | updatedAt: {type: Sequelize.DATE(6)}, 23 | }; 24 | 25 | module.exports = { 26 | tableName: 'projects', 27 | attributes, 28 | indexes: [], 29 | }; 30 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/run-model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-disable new-cap */ 9 | 10 | const Sequelize = require('sequelize'); 11 | 12 | /** @type {import('sequelize').Model} */ 13 | const ModelRef = /** @type {any} */ (undefined); 14 | 15 | /** @type {LHCI.ServerCommand.TableDefinition} */ 16 | const attributes = { 17 | id: {type: Sequelize.UUID(), primaryKey: true}, 18 | projectId: {type: Sequelize.UUID(), references: {model: ModelRef, key: 'id'}}, 19 | buildId: {type: Sequelize.UUID(), references: {model: ModelRef, key: 'id'}}, 20 | representative: {type: Sequelize.BOOLEAN}, 21 | url: {type: Sequelize.STRING({length: 256})}, 22 | lhr: {type: Sequelize.TEXT('long')}, 23 | createdAt: {type: Sequelize.DATE(6)}, 24 | updatedAt: {type: Sequelize.DATE(6)}, 25 | }; 26 | 27 | module.exports = { 28 | tableName: 'runs', 29 | attributes, 30 | indexes: [], 31 | }; 32 | -------------------------------------------------------------------------------- /packages/server/src/api/storage/sql/statistic-model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-disable new-cap */ 9 | 10 | const {DataTypes} = require('sequelize'); 11 | 12 | /** @type {import('sequelize').Model} */ 13 | const ModelRef = /** @type {any} */ (undefined); 14 | 15 | /** @type {LHCI.ServerCommand.TableDefinition} */ 16 | const attributes = { 17 | id: {type: DataTypes.UUID(), primaryKey: true}, 18 | projectId: {type: DataTypes.UUID(), references: {model: ModelRef, key: 'id'}}, 19 | buildId: {type: DataTypes.UUID(), references: {model: ModelRef, key: 'id'}}, 20 | version: {type: DataTypes.DECIMAL(8, 2)}, 21 | url: {type: DataTypes.STRING({length: 256})}, 22 | name: {type: DataTypes.STRING({length: 100})}, 23 | value: {type: DataTypes.DECIMAL(12, 4)}, 24 | createdAt: {type: DataTypes.DATE(6)}, 25 | updatedAt: {type: DataTypes.DATE(6)}, 26 | }; 27 | 28 | module.exports = { 29 | tableName: 'statistics', 30 | attributes, 31 | indexes: [], 32 | }; 33 | -------------------------------------------------------------------------------- /packages/server/src/cron/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * @param {string} schedule 11 | * @return {string} 12 | */ 13 | function normalizeCronSchedule(schedule) { 14 | if (typeof schedule !== 'string') { 15 | throw new Error(`Schedule must be provided`); 16 | } 17 | 18 | if (process.env.OVERRIDE_SCHEDULE_FOR_TEST) { 19 | return process.env.OVERRIDE_SCHEDULE_FOR_TEST; 20 | } 21 | 22 | const parts = schedule.split(/\s+/).filter(Boolean); 23 | if (parts.length !== 5) { 24 | throw new Error(`Invalid cron format, expected `); 25 | } 26 | 27 | if (parts[0] === '*') { 28 | throw new Error(`Cron schedule "${schedule}" is too frequent`); 29 | } 30 | 31 | return ['0', ...parts].join(' '); 32 | } 33 | module.exports = {normalizeCronSchedule}; 34 | -------------------------------------------------------------------------------- /packages/server/src/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../../.eslintrc.ui.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/document-title.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h, Fragment} from 'preact'; 8 | import {useEffect} from 'preact/hooks'; 9 | 10 | /** @param {{title: string}} props */ 11 | export const DocumentTitle = props => { 12 | useEffect(() => { 13 | document.title = `${props.title} | Lighthouse CI`; 14 | }, [props.title]); 15 | 16 | return ; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/dropdown.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .dropdown { 8 | border-bottom: 1px solid var(--strong-border-color); 9 | padding: calc(var(--base-spacing) / 2) 0; 10 | 11 | font-family: var(--base-font-family); 12 | font-size: var(--base-font-size); 13 | font-weight: var(--medium-font-weight); 14 | } 15 | 16 | .dropdown__label { 17 | color: var(--secondary-text-color); 18 | margin-right: calc(var(--base-spacing) / 2); 19 | } 20 | 21 | .dropdown select { 22 | position: relative; 23 | background: none; 24 | border: none; 25 | border-radius: 0; 26 | -webkit-appearance: none; 27 | 28 | padding-right: 25px; 29 | cursor: pointer; 30 | z-index: 2; 31 | 32 | font-family: var(--base-font-family); 33 | font-size: var(--base-font-size); 34 | font-weight: var(--medium-font-weight); 35 | } 36 | 37 | .dropdown__chevron { 38 | position: absolute; 39 | top: 5px; 40 | right: calc(var(--base-spacing) / 4); 41 | 42 | z-index: 1; 43 | } 44 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/gauge.stories.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {Gauge} from './gauge'; 9 | 10 | export default { 11 | title: 'Components/Gauge', 12 | component: Gauge, 13 | }; 14 | 15 | /** @type {LHCI.NumericAuditDiff} */ 16 | const numericDiff = { 17 | type: 'score', 18 | auditId: '', 19 | baseValue: 0.5, 20 | compareValue: 0.95, 21 | }; 22 | 23 | export const Default = () => ; 24 | export const WithImprovement = () => ( 25 | 26 | ); 27 | export const WithRegression = () => ( 28 | 29 | ); 30 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/lhr-viewer-button.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .lhr-viewer-button { 8 | background: var(--highlighted-background-color); 9 | border-radius: 3px; 10 | padding: calc(var(--base-spacing) / 2); 11 | margin-right: var(--base-spacing); 12 | 13 | display: flex; 14 | flex-direction: row; 15 | align-items: center; 16 | justify-content: center; 17 | 18 | cursor: pointer; 19 | color: var(--base-text-color); 20 | font-weight: var(--medium-font-weight); 21 | overflow: hidden; 22 | white-space: nowrap; 23 | } 24 | 25 | .lhr-viewer-button img { 26 | height: var(--lhr-viewer-button-size); 27 | width: var(--lhr-viewer-button-size); 28 | } 29 | 30 | .lhr-viewer-button span { 31 | margin-left: calc(var(--base-spacing) / 2); 32 | padding-right: calc(var(--base-spacing) / 2); 33 | } 34 | 35 | @media (max-width: 1000px) { 36 | .lhr-viewer-button { 37 | border-radius: 100%; 38 | } 39 | .lhr-viewer-button span { 40 | display: none; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/lhr-viewer-button.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {LhrViewerLink} from './lhr-viewer-link'; 9 | import './lhr-viewer-button.css'; 10 | 11 | // @ts-ignore - ts doesn't know how bundlers work :) 12 | const LH_ICON_PATH = require('../favicon.svg'); 13 | 14 | /** @param {{lhr: import('./lhr-viewer-link').LHResolver, label?: string}} props */ 15 | export const LhrViewerButton = props => { 16 | const {lhr, label = 'Open Report'} = props; 17 | return ( 18 | 19 |
20 | {label} 21 |
22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/lhr-viewer-link.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .lhr-viewer-link--loading { 8 | opacity: 0.5; 9 | cursor: not-allowed; 10 | } 11 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/loading-spinner.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .loading-spinner--container { 8 | width: 100%; 9 | min-height: 300px; 10 | 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | .loading-spinner img { 18 | width: 80px; 19 | height: 80px; 20 | } 21 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/loading-spinner.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import clsx from 'clsx'; 9 | import './loading-spinner.css'; 10 | 11 | // @ts-ignore - tsc doesn't understand bundlers :) 12 | const SVG_PATH = require('./loading-spinner.svg'); 13 | 14 | const LoadingSpinner_ = () => { 15 | return Loading spinner; 16 | }; 17 | 18 | /** @param {{solo?: boolean}} props */ 19 | export const LoadingSpinner = props => { 20 | return ( 21 |
26 | 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/loading-spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/markdown.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h, Fragment} from 'preact'; 8 | import {splitMarkdownLink} from '@lhci/utils/src/markdown.js'; 9 | 10 | /** @param {{text: string}} props */ 11 | export const Markdown = props => { 12 | const segments = splitMarkdownLink(props.text); 13 | 14 | return ( 15 | 16 | {segments.map((segment, i) => { 17 | if (!segment.isLink) return {segment.text}; 18 | 19 | const url = new URL(segment.linkHref); 20 | 21 | const DOCS_ORIGINS = ['https://developers.google.com', 'https://web.dev']; 22 | if (DOCS_ORIGINS.includes(url.origin)) { 23 | url.searchParams.set('utm_source', 'lighthouse'); 24 | url.searchParams.set('utm_medium', 'ci'); 25 | } 26 | 27 | return ( 28 | 29 | {segment.text} 30 | 31 | ); 32 | })} 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/modal.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .modal-backdrop { 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | right: 0; 12 | bottom: 0; 13 | z-index: 10000; 14 | 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | 19 | background: rgb(0, 0, 0, 0.7); 20 | } 21 | 22 | .modal { 23 | position: relative; 24 | width: 60vw; 25 | min-width: 400px; 26 | max-width: 800px; 27 | background: var(--highlighted-background-color); 28 | padding: var(--base-spacing); 29 | padding-right: calc(var(--base-spacing) * 2); 30 | } 31 | 32 | .modal__close { 33 | position: absolute; 34 | top: calc(var(--base-spacing) / 2); 35 | right: calc(var(--base-spacing) / 2); 36 | cursor: pointer; 37 | } 38 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/modal.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {createPortal, useRef, useEffect} from 'preact/compat'; 9 | import clsx from 'clsx'; 10 | import './modal.css'; 11 | 12 | /** @param {{className?: string, children: LHCI.PreactNode, onClose: () => void}} props */ 13 | export const Modal = props => { 14 | const modalRootRef = useRef( 15 | document.getElementById('preact-modal-root') || document.createElement('div') 16 | ); 17 | 18 | useEffect(() => { 19 | modalRootRef.current.id = 'preact-modal-root'; 20 | document.body.appendChild(modalRootRef.current); 21 | return () => document.body.removeChild(modalRootRef.current); 22 | }, []); 23 | 24 | return createPortal( 25 |
26 |
27 | {props.children} 28 |
29 | close 30 |
31 |
32 |
, 33 | modalRootRef.current 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/nbsp.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | 9 | export const Nbsp = () => { 10 | return ; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/paper.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .paper { 8 | background: var(--highlighted-background-color); 9 | border: 1px solid var(--base-border-color); 10 | padding: var(--paper-padding); 11 | margin: var(--base-spacing); 12 | overflow: visible; 13 | } 14 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/paper.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import clsx from 'clsx'; 9 | import './paper.css'; 10 | 11 | /** @param {{children: string|JSX.Element|JSX.Element[], className?: string, key?: any}} props */ 12 | export const Paper = props => { 13 | const {children} = props; 14 | return
{children}
; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/pill.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h, Fragment} from 'preact'; 8 | import clsx from 'clsx'; 9 | import './pill.css'; 10 | 11 | /** @param {{children: string|JSX.Element|JSX.Element[], className?: string, variant?: 'base'|'compare'|'master-branch'|'dev-branch', onClick?: () => void, solid?: boolean, avatar?: Pick}} props */ 12 | export const Pill = props => { 13 | const {children, avatar, variant = 'base'} = props; 14 | return ( 15 |
22 | {avatar ? ( 23 | {avatar.author} 29 | ) : ( 30 | 31 | )} 32 | {children} 33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/plotly.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | // @ts-nocheck 7 | 'use strict'; 8 | 9 | const PlotlyCore = require('plotly.js/lib/core'); 10 | 11 | PlotlyCore.register([require('plotly.js/lib/scatter'), require('plotly.js/lib/bar')]); 12 | 13 | module.exports = {Plotly: PlotlyCore}; 14 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/pwa-gauge.stories.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {PWAGauge} from './pwa-gauge'; 9 | 10 | export default { 11 | title: 'Components/PWA Gauge', 12 | component: PWAGauge, 13 | }; 14 | 15 | const allFalse = {optimized: false, installable: false, fastAndReliable: false}; 16 | const allTrue = {optimized: true, installable: true, fastAndReliable: true}; 17 | 18 | export const Default = () => ; 19 | export const Optimized = () => ; 20 | export const OptimizedAndInstallable = () => ( 21 | 22 | ); 23 | export const OptimizedAndFast = () => ( 24 | 25 | ); 26 | export const Installable = () => ; 27 | export const InstallableAndFast = () => ( 28 | 29 | ); 30 | export const Fast = () => ; 31 | export const PWA = () => ; 32 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/redirect.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {useEffect} from 'preact/hooks'; 9 | import {route} from 'preact-router'; 10 | 11 | /** @param {{to: string, default?: boolean}} props */ 12 | export const Redirect = props => { 13 | useEffect(() => { 14 | route(props.to); 15 | }, []); 16 | 17 | return
; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/score-delta-badge.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .score-delta-badge { 8 | height: var(--build-score-delta-badge-height); 9 | padding: 0 var(--base-spacing); 10 | border-radius: calc(var(--build-score-delta-badge-height) / 2); 11 | line-height: var(--build-score-delta-badge-height); 12 | 13 | background: var(--neutral-color); 14 | color: var(--inverted-text-color); 15 | 16 | font-family: var(--monospace-font-family); 17 | font-size: var(--monospace-font-size); 18 | text-align: center; 19 | } 20 | 21 | .score-delta-badge.score-delta-badge--improvement { 22 | background-color: var(--improvement-color); 23 | } 24 | 25 | .score-delta-badge.score-delta-badge--regression { 26 | background-color: var(--regression-color); 27 | } 28 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/score-delta-badge.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import './score-delta-badge.css'; 9 | import {getDiffLabel} from '@lhci/utils/src/audit-diff-finder'; 10 | import clsx from 'clsx'; 11 | 12 | /** @param {{diff: LHCI.NumericAuditDiff, className?: string}} props */ 13 | export const ScoreDeltaBadge = props => { 14 | const delta = Math.round((props.diff.compareValue - props.diff.baseValue) * 100); 15 | return ( 16 |
23 | {delta < 0 ? delta : `+${delta}`} 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/score-delta-badge.stories.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {ScoreDeltaBadge} from './score-delta-badge'; 9 | 10 | export default { 11 | title: 'Components/Score Delta Badge', 12 | component: ScoreDeltaBadge, 13 | }; 14 | 15 | /** @type {LHCI.NumericAuditDiff} */ 16 | const numericDiff = { 17 | type: 'score', 18 | auditId: '', 19 | baseValue: 0.5, 20 | compareValue: 0.5, 21 | }; 22 | 23 | export const Netural = () => ; 24 | export const Improvement = () => ; 25 | export const Regression = () => ; 26 | -------------------------------------------------------------------------------- /packages/server/src/ui/components/score-icon.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | 9 | /** @param {{score: number}} props */ 10 | export const ScoreIcon = props => { 11 | const score = props.score; 12 | if (score >= 0.9) return ; 13 | if (score >= 0.5) return ; 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | /** @param {{audit: LH.AuditResult}} props */ 24 | export const ScoreWord = props => { 25 | const score = props.audit.score || 0; 26 | if (score >= 0.9) return Pass; 27 | return Fail; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/server/src/ui/entry.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h, render} from 'preact'; 8 | import {App} from './app.jsx'; 9 | 10 | // Fontsource for fonts/icons instead of google cdn 11 | import '@fontsource/roboto/400.css'; 12 | import '@fontsource/roboto/500.css'; 13 | import '@fontsource/roboto-mono/400.css'; 14 | import '@fontsource/roboto-mono/500.css'; 15 | import '@fontsource/material-icons'; 16 | 17 | const preactRoot = document.getElementById('preact-root'); 18 | if (!preactRoot) throw new Error('Missing #preact-root'); 19 | render(, preactRoot); 20 | -------------------------------------------------------------------------------- /packages/server/src/ui/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/src/ui/favicon.png -------------------------------------------------------------------------------- /packages/server/src/ui/hooks/use-previous-value.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {useRef, useEffect} from 'preact/hooks'; 8 | 9 | /** 10 | * @template T 11 | * @param {T} value 12 | * @return {T|null|undefined} 13 | */ 14 | export function usePreviousValue(value) { 15 | /** @type {import('preact').Ref} */ 16 | const ref = useRef(undefined); 17 | 18 | useEffect(() => { 19 | ref.current = value; 20 | }, [value]); 21 | 22 | // This is returned before our useEffect callback runs to update the value. 23 | return ref.current; 24 | } 25 | -------------------------------------------------------------------------------- /packages/server/src/ui/hooks/use-route-params.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | /** 8 | * Returns the route-based parameters from the pathname. 9 | * 10 | * Although not technically a hook at this point in time, if preact-router were to stop being buggy 11 | * (double renders child contents in unclear situations) we could move this to use the built-in hooks. 12 | * 13 | * @return {{projectSlug: string | undefined}} 14 | */ 15 | export function useRouteParams() { 16 | const projectSlug = window.location.pathname.match(/\/app\/projects\/([^/]+)/); 17 | return { 18 | projectSlug: (projectSlug && projectSlug[1]) || undefined, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/server/src/ui/icons.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | i.lh-score-fail { 8 | display: block; 9 | width: var(--base-icon-size); 10 | height: var(--base-icon-size); 11 | } 12 | 13 | i.lh-score-fail polygon { 14 | /* the SVG is on a 120x120 viewBox, so scale up the strokeWidth */ 15 | stroke: var(--fail-color); 16 | stroke-width: 15px; 17 | fill: none; 18 | } 19 | 20 | i.lh-score-average { 21 | display: block; 22 | width: var(--base-icon-size); 23 | height: var(--base-icon-size); 24 | border: 2px solid var(--average-color); 25 | } 26 | 27 | i.lh-score-pass { 28 | display: block; 29 | width: var(--base-icon-size); 30 | height: var(--base-icon-size); 31 | border-radius: 100%; 32 | border: 2px solid var(--pass-color); 33 | } 34 | -------------------------------------------------------------------------------- /packages/server/src/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Lighthouse CI | Dashboard 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/server/src/ui/layout/__mocks__/page.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h, Fragment} from 'preact'; 8 | 9 | /** 10 | * @param {{header?: LHCI.PreactNode, children: LHCI.PreactNode}} props 11 | */ 12 | export const Page = props => { 13 | return ( 14 | 15 | {props.header ?
{props.header}
: null} 16 | {props.children} 17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/server/src/ui/layout/page-body.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | 9 | /** 10 | * @param {{children: LHCI.PreactNode}} props 11 | */ 12 | export const PageBody = props => { 13 | return
{props.children}
; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/server/src/ui/layout/page-header.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .page-header { 8 | position: fixed; 9 | height: var(--page-header-height); 10 | width: 100vw; 11 | 12 | background: var(--highlighted-background-color); 13 | border-bottom: 1px solid var(--base-border-color); 14 | 15 | display: flex; 16 | flex-direction: row; 17 | align-items: center; 18 | z-index: 10; 19 | } 20 | 21 | .page-header > div { 22 | display: flex; 23 | flex-direction: row; 24 | align-items: center; 25 | justify-content: center; 26 | } 27 | 28 | .page-header__left { 29 | height: var(--page-header-height); 30 | width: var(--page-header-height); 31 | flex-shrink: 0; 32 | 33 | border-right: 1px solid var(--base-border-color); 34 | } 35 | 36 | .page-header__center { 37 | height: var(--page-header-height); 38 | width: calc(100% - var(--page-header-height) * 2); 39 | } 40 | 41 | .page-header__sidebar-button { 42 | cursor: pointer; 43 | /* Material Icons aren't actually vertically centered for their line-height, so adjust it */ 44 | position: relative; 45 | top: 2px; 46 | } 47 | 48 | .page-header__right { 49 | height: var(--page-header-height); 50 | width: var(--page-header-height); 51 | flex-shrink: 0; 52 | } 53 | 54 | .page-header__right--with-content { 55 | border-left: 1px solid var(--base-border-color); 56 | } 57 | -------------------------------------------------------------------------------- /packages/server/src/ui/layout/page-header.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import clsx from 'clsx'; 9 | import './page-header.css'; 10 | 11 | /** @param {{children?: LHCI.PreactNode, childrenLeft?: LHCI.PreactNode, childrenRight?: LHCI.PreactNode, setIsSidebarOpen: (isOpen: boolean) => void}} props */ 12 | export const PageHeader = props => { 13 | return ( 14 |
15 |
16 | {props.childrenLeft ? ( 17 | props.childrenLeft 18 | ) : ( 19 |
props.setIsSidebarOpen(true)} 23 | > 24 | menu 25 |
26 | )} 27 |
28 |
{props.children}
29 |
34 | {props.childrenRight} 35 |
36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/build-view/audit-detail/simple-details.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .simple-details--improvement { 8 | color: var(--improvement-text-color); 9 | } 10 | 11 | .simple-details--regression { 12 | color: var(--regression-text-color); 13 | } 14 | 15 | .simple-details__url-hostname { 16 | color: var(--secondary-text-color); 17 | margin-left: calc(var(--base-spacing) / 2); 18 | font-size: var(--subtext-font-size); 19 | } 20 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/build-view/build-view.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .build-view--with-lhr-base-link-hover .lhr-comparison__scores-and-dropdowns .dropdown--base-url, 8 | .build-view--with-lhr-compare-link-hover .lhr-comparison__scores-and-dropdowns .dropdown--compare-url { 9 | background: var(--compare-secondary-highlight-color); 10 | } 11 | 12 | .build-view--with-lhr-base-link-hover 13 | .lhr-comparison__scores-and-dropdowns 14 | .dropdown--base-url 15 | .dropdown__label, 16 | .build-view--with-lhr-compare-link-hover 17 | .lhr-comparison__scores-and-dropdowns 18 | .dropdown--compare-url 19 | .dropdown__label { 20 | padding-left: calc(var(--base-spacing) / 4); 21 | margin-right: calc(var(--base-spacing) / 4); 22 | } 23 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/build-view/lhr-comparison-legend.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .lhr-comparison-legend { 8 | display: flex; 9 | flex-direction: row; 10 | align-items: center; 11 | 12 | margin-right: var(--base-spacing); 13 | width: fit-content; 14 | padding: var(--base-spacing) calc(var(--base-spacing) * 1.5); 15 | 16 | background: var(--highlighted-background-color); 17 | border: 1px solid var(--base-border-color); 18 | border-radius: 9999px; /* force fully rounded corners */ 19 | } 20 | 21 | .lhr-comparison-legend__label { 22 | font-family: var(--monospace-font-family); 23 | font-size: var(--monospace-font-size); 24 | margin-left: calc(var(--base-spacing) / 2); 25 | margin-right: calc(var(--base-spacing) * 1.5); 26 | } 27 | 28 | .lhr-comparison-legend__label:last-child { 29 | margin-right: 0; 30 | } 31 | 32 | .lhr-comparison-legend__chip { 33 | display: 'block'; 34 | width: var(--base-icon-size); 35 | height: var(--base-icon-size); 36 | border-radius: 2px; 37 | } 38 | 39 | .lhr-comparison-legend__chip--regression { 40 | background: var(--regression-color); 41 | } 42 | 43 | .lhr-comparison-legend__chip--improvement { 44 | background: var(--improvement-color); 45 | } 46 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/build-view/lhr-comparison-legend.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import './lhr-comparison-legend.css'; 9 | import {ScoreIcon} from '../../components/score-icon'; 10 | 11 | export const LhrComparisonLegend = () => { 12 | return ( 13 |
14 | 15 | 0-49 16 | 17 | 50-89 18 | 19 | 90-100 20 | 21 | Regression 22 | 23 | Improvement 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/build-view/lhr-comparison-runtime-diff.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .lhr-comparison-runtime-diff { 8 | position: relative; 9 | } 10 | 11 | .lhr-comparison-runtime-diff .lhr-comparison__warning { 12 | margin-bottom: 0; 13 | } 14 | 15 | .lhr-comparison-runtime-diff__diffs { 16 | --table-margin: calc(var(--warning-message-icon-height) + 2 * var(--base-spacing)); 17 | 18 | width: calc(100% - var(--table-margin)); 19 | text-align: left; 20 | 21 | margin-left: var(--table-margin); 22 | } 23 | 24 | .lhr-comparison-runtime-diff__close { 25 | position: absolute; 26 | top: calc(var(--base-spacing) / 2); 27 | right: calc(var(--base-spacing) / 2); 28 | cursor: pointer; 29 | } 30 | 31 | .lhr-comparison-runtime-diff table { 32 | width: 100%; 33 | border-collapse: collapse; 34 | } 35 | 36 | .lhr-comparison-runtime-diff table th { 37 | font-weight: var(--medium-font-weight); 38 | } 39 | 40 | .lhr-comparison-runtime-diff table tbody td { 41 | padding: calc(var(--base-spacing) / 2); 42 | } 43 | 44 | .lhr-comparison-runtime-diff table tbody tr:nth-child(even) { 45 | background: var(--secondary-background-color); 46 | } 47 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/project-dashboard/getting-started.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .getting-started { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | 13 | min-height: 70vh; 14 | 15 | text-align: center; 16 | } 17 | 18 | .getting-started .paper { 19 | max-width: 50vw; 20 | padding: calc(var(--base-spacing) * 2); 21 | } 22 | 23 | .getting-started pre { 24 | display: inline; 25 | } 26 | 27 | .getting-started img { 28 | height: 80px; 29 | width: 80px; 30 | margin-bottom: calc(var(--base-spacing) * 2); 31 | } 32 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/project-dashboard/getting-started.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import {Paper} from '../../components/paper'; 9 | import './getting-started.css'; 10 | 11 | // @ts-ignore - tsc doesn't get bundlers :) 12 | const LH_LOGO_PATH = require('../../logo.svg'); 13 | 14 | /** 15 | * @param {{project: LHCI.ServerCommand.Project}} props 16 | */ 17 | export const ProjectGettingStarted = props => { 18 | return ( 19 |
20 | 21 | 22 |

23 | No build data yet for {props.project.name}! Add the
@lhci/cli
package to your 24 | continuous integration to{' '} 25 | 30 | get started 31 | 32 | . 33 |

34 |
35 |
36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/project-dashboard/project-category-summaries.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .project-category-summaries { 8 | display: flex; 9 | flex-direction: row; 10 | flex-wrap: wrap; 11 | 12 | padding-bottom: 300px; 13 | } 14 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/project-dashboard/project-dashboard.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .dashboard { 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | .dashboard__url-branch-selector { 13 | display: flex; 14 | flex-direction: row; 15 | align-items: center; 16 | justify-content: center; 17 | 18 | margin-bottom: var(--base-spacing); 19 | } 20 | 21 | .dashboard__url-branch-selector > * { 22 | margin-right: var(--base-spacing); 23 | } 24 | 25 | .dashboard__url-branch-selector .dropdown--url select { 26 | max-width: 300px; 27 | } 28 | 29 | .dashboard__url-branch-selector .dropdown--branch select { 30 | max-width: 120px; 31 | } 32 | 33 | .dashboard--scrolled .dashboard__url-branch-selector { 34 | position: fixed; 35 | z-index: 11; 36 | top: 0; 37 | left: var(--page-header-height); 38 | right: var(--page-header-height); 39 | height: var(--page-header-height); 40 | } 41 | 42 | /** fill in some of the space that dissappears when it flips to position: fixed */ 43 | .dashboard--scrolled #dashboard__scroll-height-detector { 44 | height: calc(var(--base-spacing) + var(--base-font-size)); 45 | } 46 | -------------------------------------------------------------------------------- /packages/server/src/ui/routes/project-settings/project-settings.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .form-item { 8 | margin-top: var(--base-spacing); 9 | 10 | display: block; 11 | } 12 | 13 | .form-item input { 14 | padding: calc(var(--base-spacing) / 2) 0; 15 | 16 | border: none; 17 | border-bottom: 1px solid var(--secondary-text-color); 18 | } 19 | 20 | .form-item button { 21 | background: var(--highlighted-background-color); 22 | border-radius: 3px; 23 | padding: calc(var(--base-spacing) / 2); 24 | margin-right: var(--base-spacing); 25 | 26 | cursor: pointer; 27 | color: var(--base-text-color); 28 | font-weight: var(--medium-font-weight); 29 | overflow: hidden; 30 | white-space: nowrap; 31 | } 32 | 33 | .form-item button:hover { 34 | background: var(--secondary-background-color); 35 | } 36 | 37 | .form-item button:disabled { 38 | background: var(--secondary-background-color); 39 | cursor: not-allowed; 40 | opacity: 0.7; 41 | } 42 | -------------------------------------------------------------------------------- /packages/server/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../.eslintrc.tests.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/server/test/__mocks__/file-mock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = 'file-mock'; 9 | -------------------------------------------------------------------------------- /packages/server/test/cron/utils.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const {normalizeCronSchedule} = require('../../src/cron/utils.js'); 11 | 12 | describe('cron/utils', () => { 13 | describe('.normalizeCronSchedule()', () => { 14 | it('should validate string', () => { 15 | expect(() => normalizeCronSchedule(1)).toThrow(/Schedule must be provided/); 16 | }); 17 | 18 | it('should validate cron job', () => { 19 | expect(() => normalizeCronSchedule('* * * * *')).toThrow(/too frequent/); 20 | }); 21 | 22 | it('should validate invalid format', () => { 23 | expect(() => normalizeCronSchedule('* * *')).toThrow(/Invalid cron format/); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/server/test/e2e/__image_snapshots__/project-dashboard-empty-test-js-project-dashboard-render-the-welcome-screen-should-look-correct-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/e2e/__image_snapshots__/project-dashboard-empty-test-js-project-dashboard-render-the-welcome-screen-should-look-correct-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/e2e/__image_snapshots__/project-dashboard-mixed-v-5-v-6-test-js-project-dashboard-render-the-dashboard-should-look-correct-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/e2e/__image_snapshots__/project-dashboard-mixed-v-5-v-6-test-js-project-dashboard-render-the-dashboard-should-look-correct-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/e2e/__image_snapshots__/project-dashboard-mixed-v-5-v-6-test-js-project-dashboard-render-the-dashboard-should-look-correct-on-hover-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/e2e/__image_snapshots__/project-dashboard-mixed-v-5-v-6-test-js-project-dashboard-render-the-dashboard-should-look-correct-on-hover-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/e2e/__image_snapshots__/project-dashboard-test-js-project-dashboard-render-the-dashboard-should-look-correct-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/e2e/__image_snapshots__/project-dashboard-test-js-project-dashboard-render-the-dashboard-should-look-correct-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/e2e/project-dashboard-empty.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest, browser */ 9 | 10 | const {shouldRunE2E, emptyTest} = require('../test-utils.js'); 11 | 12 | describe('Project dashboard', () => { 13 | if (!shouldRunE2E()) return emptyTest(); 14 | 15 | const state = /** @type {LHCI.E2EState} */ ({}); 16 | 17 | require('./steps/setup')(state); 18 | 19 | require('./steps/navigate-to-project')(state, 'Lighthouse Dashboard', {waitFor: 'empty'}); 20 | 21 | describe('render the welcome screen', () => { 22 | it('should look correct', async () => { 23 | expect(await state.page.screenshot({fullPage: true})).toMatchImageSnapshot(); 24 | }); 25 | }); 26 | 27 | require('./steps/teardown')(state); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/server/test/e2e/steps/teardown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const {cleanupE2E} = require('../../test-utils.js'); 11 | 12 | /** @param {LHCI.E2EState} state */ 13 | module.exports = state => { 14 | describe('teardown', () => { 15 | it('should cleanup', async () => { 16 | await cleanupE2E(state); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/server/test/mysql-socket-path-server.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const runTests = require('./server-test-suite.js').runTests; 11 | const runServer = require('../src/server.js').createServer; 12 | 13 | describe('mysql server (socket path)', () => { 14 | if (!process.env.MYSQL_SOCKET_PATH) { 15 | it.skip('should work on mysql', () => {}); 16 | return; 17 | } 18 | 19 | const state = { 20 | port: undefined, 21 | closeServer: undefined, 22 | }; 23 | 24 | beforeAll(async () => { 25 | const {port, close, storageMethod} = await runServer({ 26 | logLevel: 'silent', 27 | port: 0, 28 | storage: { 29 | storageMethod: 'sql', 30 | sqlDialect: 'mysql', 31 | sqlDangerouslyResetDatabase: true, 32 | sqlDialectOptions: { 33 | socketPath: process.env.MYSQL_SOCKET_PATH, 34 | }, 35 | sequelizeOptions: { 36 | database: 'lighthouse_ci_test', 37 | username: 'root', 38 | password: 'mysql', 39 | }, 40 | }, 41 | }); 42 | 43 | state.port = port; 44 | state.closeServer = close; 45 | state.storageMethod = storageMethod; 46 | }); 47 | 48 | afterAll(() => { 49 | state.closeServer(); 50 | }); 51 | 52 | runTests(state); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/server/test/postgres-server.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const runTests = require('./server-test-suite.js').runTests; 11 | const runServer = require('../src/server.js').createServer; 12 | 13 | describe('postgres server', () => { 14 | if (!process.env.POSTGRES_DB_URL) { 15 | it.skip('should work on postgres', () => {}); 16 | return; 17 | } 18 | 19 | const state = { 20 | port: undefined, 21 | closeServer: undefined, 22 | }; 23 | 24 | beforeAll(async () => { 25 | const {port, close, storageMethod} = await runServer({ 26 | logLevel: 'silent', 27 | port: 0, 28 | storage: { 29 | storageMethod: 'sql', 30 | sqlDialect: 'postgres', 31 | sqlConnectionUrl: process.env.POSTGRES_DB_URL, 32 | sqlConnectionSsl: !!process.env.POSTGRES_DB_SSL, 33 | sqlDangerouslyResetDatabase: true, 34 | }, 35 | }); 36 | 37 | state.port = port; 38 | state.closeServer = close; 39 | state.storageMethod = storageMethod; 40 | }); 41 | 42 | afterAll(() => { 43 | state.closeServer(); 44 | }); 45 | 46 | runTests(state); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/server/test/postgres-socket-path-server.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const runTests = require('./server-test-suite.js').runTests; 11 | const runServer = require('../src/server.js').createServer; 12 | 13 | describe('postgres server', () => { 14 | if (!process.env.POSTGRES_SOCKET_PATH) { 15 | it.skip('should work on postgres', () => {}); 16 | return; 17 | } 18 | 19 | const state = { 20 | port: undefined, 21 | closeServer: undefined, 22 | }; 23 | 24 | beforeAll(async () => { 25 | const {port, close, storageMethod} = await runServer({ 26 | logLevel: 'silent', 27 | port: 0, 28 | storage: { 29 | storageMethod: 'sql', 30 | sqlDialect: 'postgres', 31 | sqlDangerouslyResetDatabase: true, 32 | sqlDialectOptions: { 33 | socketPath: process.env.POSTGRES_SOCKET_PATH, 34 | }, 35 | sequelizeOptions: { 36 | database: 'lighthouse_ci_test', 37 | username: 'postgres', 38 | password: 'postgres', 39 | }, 40 | }, 41 | }); 42 | 43 | state.port = port; 44 | state.closeServer = close; 45 | state.storageMethod = storageMethod; 46 | }); 47 | 48 | afterAll(() => { 49 | state.closeServer(); 50 | }); 51 | 52 | runTests(state); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/server/test/sqlite-server.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const path = require('path'); 11 | const runTests = require('./server-test-suite.js').runTests; 12 | const runServer = require('../src/server.js').createServer; 13 | const {safeDeleteFile} = require('../../cli/test/test-utils.js'); 14 | 15 | describe('sqlite server', () => { 16 | const state = { 17 | port: undefined, 18 | closeServer: undefined, 19 | }; 20 | 21 | const dbPath = path.join(__dirname, 'server-test.tmp.sql'); 22 | 23 | beforeAll(async () => { 24 | await safeDeleteFile(dbPath); 25 | 26 | const {port, close, storageMethod} = await runServer({ 27 | logLevel: 'silent', 28 | port: 0, 29 | storage: { 30 | storageMethod: 'sql', 31 | sqlDialect: 'sqlite', 32 | sqlDatabasePath: dbPath, 33 | }, 34 | }); 35 | 36 | state.port = port; 37 | state.closeServer = close; 38 | state.storageMethod = storageMethod; 39 | }); 40 | 41 | afterAll(async () => { 42 | await state.closeServer(); 43 | await safeDeleteFile(dbPath); 44 | }); 45 | 46 | runTests(state); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/server/test/storybook-setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const path = require('path'); 9 | const FallbackServer = require('../../cli/src/collect/fallback-server'); 10 | 11 | const STORYBOOK_STATIC = path.join(__dirname, '../storybook-static'); 12 | 13 | module.exports = async () => { 14 | const server = new FallbackServer(STORYBOOK_STATIC, false); 15 | await server.listen(); 16 | process.env.STORYBOOK_PORT = server.port; 17 | process.fallbackServer = server; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/server/test/storybook-teardown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = async () => { 9 | await process.fallbackServer.close(); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/server/test/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../../.eslintrc.tests.js', '../../../../.eslintrc.ui.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-default-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-default-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1010-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1010-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1140-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1140-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1200-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-1200-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-6-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-6-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-62-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-62-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-641-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-641-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-700-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-700-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-800-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-800-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-930-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-930-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-psi-800-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-psi-800-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-subitems-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-audit-detail-pane-version-subitems-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-custom-base-branch-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-custom-base-branch-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-no-branch-builds-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-no-branch-builds-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-no-master-builds-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-no-master-builds-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-select-base-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-select-base-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-select-compare-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-build-hash-selector-select-compare-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-default-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-default-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1010-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1010-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1140-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1140-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1200-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-1200-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-6-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-6-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-62-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-62-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-641-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-641-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-700-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-700-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-800-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-800-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-930-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-930-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-psi-800-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-build-view-lhr-comparison-version-psi-800-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-gauge-default-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-gauge-default-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-gauge-with-improvement-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-gauge-with-improvement-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-gauge-with-regression-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-gauge-with-regression-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-default-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-default-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-fast-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-fast-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-installable-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-installable-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-installable-and-fast-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-installable-and-fast-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-and-fast-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-and-fast-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-and-installable-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-optimized-and-installable-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-pwa-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-pwa-gauge-pwa-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-score-delta-badge-improvement-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-score-delta-badge-improvement-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-score-delta-badge-netural-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-score-delta-badge-netural-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-score-delta-badge-regression-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-components-score-delta-badge-regression-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-with-hover-card-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-with-hover-card-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-with-version-changes-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-category-score-graph-default-with-version-changes-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-metric-line-graph-default-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-metric-line-graph-default-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-metric-line-graph-with-selected-metric-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/server/test/ui/__image_snapshots__/storybook-test-jsx-image-storyshots-project-dashboard-metric-line-graph-with-selected-metric-1-snap.png -------------------------------------------------------------------------------- /packages/server/test/ui/routes/project-dashboard/graphs/category-score-graph/score-line-graph.test.jsx: -------------------------------------------------------------------------------- 1 | /** @jest-environment jsdom */ 2 | 3 | /** 4 | * @license Copyright 2020 Google Inc. All Rights Reserved. 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 7 | */ 8 | 9 | /* eslint-env jest */ 10 | 11 | import {h} from 'preact'; 12 | import {buildMinMaxByBuildId} from '../../../../../../src/ui/routes/project-dashboard/graphs/category-score/score-line-graph'; 13 | import {cleanup} from '../../../../../test-utils.js'; 14 | 15 | afterEach(cleanup); 16 | 17 | describe('Category Score Graph', () => { 18 | describe('buildMinMaxByBuildId', () => { 19 | it('should set the min/max values by build id', () => { 20 | const statistics = [ 21 | {buildId: 'a', name: 'category_pwa_median', value: 0.4}, 22 | {buildId: 'a', name: 'category_pwa_min', value: 0.1}, 23 | {buildId: 'a', name: 'category_pwa_max', value: 0.7}, 24 | {buildId: 'b', name: 'category_pwa_min', value: 0.6}, 25 | {buildId: 'b', name: 'category_pwa_median', value: 0.8}, 26 | {buildId: 'b', name: 'category_pwa_max', value: 0.99}, 27 | ]; 28 | 29 | expect(buildMinMaxByBuildId(statistics)).toEqual({ 30 | a: {min: 0.1, max: 0.7}, 31 | b: {min: 0.6, max: 0.99}, 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/utils/.npmignore: -------------------------------------------------------------------------------- 1 | *.tmp.sql 2 | *.tmp.json 3 | .lighthouseci/ 4 | .cache/ 5 | test/ 6 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # Lighthouse CI Utils (@lhci/utils) 2 | 3 | [![npm version](https://badge.fury.io/js/%40lhci%2Futils.svg)](https://badge.fury.io/js/%40lhci%2Futils) 4 | 5 | ## About 6 | 7 | The Lighthouse CI Utils supports the `@lhci/cli` and `@lhci/server` packages. Read the [Lighthouse CI docs](https://github.com/GoogleChrome/lighthouse-ci/blob/main/README.md) to learn more. 8 | 9 | ## Acronyms 10 | 11 | - CI: Continuous Integration 12 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lhci/utils", 3 | "version": "0.1.0", 4 | "license": "Apache-2.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/GoogleChrome/lighthouse-ci.git" 8 | }, 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "dependencies": { 13 | "debug": "^4.3.1", 14 | "isomorphic-fetch": "^3.0.0", 15 | "js-yaml": "^3.13.1", 16 | "lighthouse": "12.1.0", 17 | "tree-kill": "^1.2.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/utils/src/presets/no-pwa.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const recommended = require('./recommended.js'); 9 | 10 | // TODO: PWA doesn't exist anymore, so remove? 11 | 12 | module.exports = { 13 | assertions: { 14 | ...recommended.assertions, 15 | // Every PWA audit is disabled 16 | 'is-on-https': 'off', 17 | viewport: 'off', 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/utils/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../.eslintrc.tests.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/viewer/.npmignore: -------------------------------------------------------------------------------- 1 | *.tmp.sql 2 | *.tmp.json 3 | .lighthouseci/ 4 | .cache/ 5 | test/ 6 | -------------------------------------------------------------------------------- /packages/viewer/README.md: -------------------------------------------------------------------------------- 1 | # Lighthouse CI Viewer (@lhci/viewer) 2 | 3 | ## About 4 | 5 | The Lighthouse CI viewer offers drag 'n drop comparison of Lighthouse reports. 6 | 7 | ## Acronyms 8 | 9 | - CI: Continuous Integration 10 | -------------------------------------------------------------------------------- /packages/viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lhci/viewer", 3 | "version": "0.1.0", 4 | "license": "Apache-2.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/GoogleChrome/lighthouse-ci.git" 8 | }, 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "scripts": { 13 | "clean": "rm -rf ./dist", 14 | "start": "npm run build:watch", 15 | "build": "node ../../scripts/build-app.js build ./src/ui/index.html ./dist ./", 16 | "build:watch": "node ../../scripts/build-app.js watch ./src/ui/index.html ./dist ./" 17 | }, 18 | "devDependencies": { 19 | "clsx": "^1.0.4", 20 | "preact": "^10.19.3", 21 | "@fontsource/material-icons": "^4.4.5", 22 | "@fontsource/roboto": "^4.4.5", 23 | "@fontsource/roboto-mono": "^4.4.5" 24 | }, 25 | "alias": { 26 | "isomorphic-fetch": "clsx" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../../.eslintrc.ui.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | body { 8 | width: 100vw; 9 | min-height: 100vh; 10 | position: relative; 11 | } 12 | 13 | .loading-container { 14 | position: fixed; 15 | top: 0; 16 | left: 0; 17 | width: 100vw; 18 | height: 100vh; 19 | 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | 24 | background: rgba(255, 255, 255, 0.8); 25 | z-index: 1000; 26 | } 27 | 28 | .toast-container { 29 | position: absolute; 30 | z-index: 10; 31 | left: var(--base-spacing); 32 | bottom: var(--base-spacing); 33 | } 34 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/components/lhci-components.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | export {LoadingSpinner} from '../../../../server/src/ui/components/loading-spinner.jsx'; 9 | export {Paper} from '../../../../server/src/ui/components/paper.jsx'; 10 | export {LhrViewerButton} from '../../../../server/src/ui/components/lhr-viewer-button.jsx'; 11 | 12 | /** @type {string} */ 13 | // @ts-expect-error - tsc doesn't get parcel :) 14 | export const CONFETTI_PATH = require('../../../../server/src/ui/routes/project-list/confetti.svg'); 15 | 16 | /** @type {string} */ 17 | // @ts-expect-error - tsc doesn't get parcel :) 18 | export const LH_LOGO_PATH = require('../../../../server/src/ui/logo.svg'); 19 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/components/toast.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | .toast { 8 | padding: calc(var(--base-spacing)); 9 | background: var(--neutral-color); 10 | color: var(--inverted-text-color); 11 | } 12 | 13 | .toast--error { 14 | color: var(--fail-color); 15 | } 16 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/components/toast.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h} from 'preact'; 8 | import './toast.css'; 9 | import {useEffect} from 'preact/hooks'; 10 | 11 | /** @typedef {import('../app.jsx').ToastMessage} ToastMessage */ 12 | 13 | /** @param {{toast: ToastMessage, setToasts: import('preact/hooks/src').StateUpdater>}} props */ 14 | export const Toast = props => { 15 | const setToasts = props.setToasts; 16 | const {message, level = 'info'} = props.toast; 17 | 18 | useEffect(() => { 19 | const interval = setTimeout( 20 | () => setToasts(toasts => toasts.filter(t => t !== props.toast)), 21 | 5000 22 | ); 23 | return () => clearInterval(interval); 24 | }, []); 25 | 26 | return
{message}
; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/entry.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | import {h, render} from 'preact'; 8 | import {App} from './app.jsx'; 9 | 10 | // Fontsource for fonts/icons instead of google cdn 11 | import '@fontsource/roboto/400.css'; 12 | import '@fontsource/roboto/500.css'; 13 | import '@fontsource/roboto-mono/400.css'; 14 | import '@fontsource/roboto-mono/500.css'; 15 | import '@fontsource/material-icons'; 16 | 17 | const preactRoot = document.getElementById('preact-root'); 18 | if (!preactRoot) throw new Error('Missing #preact-root'); 19 | render(, preactRoot); 20 | -------------------------------------------------------------------------------- /packages/viewer/src/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | Lighthouse Report Diff Tool 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/viewer/src/viewer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const path = require('path'); 9 | process.stdout.write( 10 | 'Deployable code for the viewer can be found at ' + path.join(__dirname, '../dist') 11 | ); 12 | -------------------------------------------------------------------------------- /packages/viewer/test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2022 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | module.exports = { 9 | extends: ['../../../.eslintrc.tests.js'], 10 | }; 11 | -------------------------------------------------------------------------------- /packages/viewer/test/e2e/__image_snapshots__/different-category-comparison-test-js-viewer-simple-comparison-render-the-comparison-route-should-look-correct-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/viewer/test/e2e/__image_snapshots__/different-category-comparison-test-js-viewer-simple-comparison-render-the-comparison-route-should-look-correct-1-snap.png -------------------------------------------------------------------------------- /packages/viewer/test/e2e/__image_snapshots__/simple-comparison-test-js-viewer-simple-comparison-render-the-comparison-route-should-look-correct-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/viewer/test/e2e/__image_snapshots__/simple-comparison-test-js-viewer-simple-comparison-render-the-comparison-route-should-look-correct-1-snap.png -------------------------------------------------------------------------------- /packages/viewer/test/e2e/__image_snapshots__/simple-comparison-test-js-viewer-simple-comparison-render-the-landing-route-should-look-correct-1-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChrome/lighthouse-ci/de45968a38faf11ca9f7b92dea377f23b84cf820/packages/viewer/test/e2e/__image_snapshots__/simple-comparison-test-js-viewer-simple-comparison-render-the-landing-route-should-look-correct-1-snap.png -------------------------------------------------------------------------------- /packages/viewer/test/e2e/steps/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const {createTestServer, launchBrowser, setupImageSnapshots} = require('../../test-utils.js'); 11 | 12 | setupImageSnapshots(); 13 | 14 | /** @param {LHCI.E2EState} state */ 15 | module.exports = state => { 16 | state.debug = Boolean(process.env.DEBUG); 17 | 18 | describe('initialize', () => { 19 | it('should initialize a server', async () => { 20 | state.server = await createTestServer(); 21 | state.rootURL = `http://localhost:${state.server.port}`; 22 | }); 23 | 24 | it('should initialize a browser', () => launchBrowser(state)); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/viewer/test/e2e/steps/teardown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | /* eslint-env jest */ 9 | 10 | const {cleanupE2E} = require('../../test-utils.js'); 11 | 12 | /** @param {LHCI.E2EState} state */ 13 | module.exports = state => { 14 | describe('teardown', () => { 15 | it('should cleanup', async () => { 16 | await cleanupE2E(state); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/viewer/test/test-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2020 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | const FallbackServer = require('../../cli/src/collect/fallback-server.js'); 11 | 12 | module.exports = { 13 | ...require('../../server/test/test-utils.js'), 14 | createTestServer: async () => { 15 | const pathToBuildDir = path.resolve(__dirname, '../dist'); 16 | if (!fs.existsSync(`${pathToBuildDir}/index.html`)) { 17 | throw new Error('Build viewer before running tests'); 18 | } 19 | 20 | const server = new FallbackServer(pathToBuildDir, false); 21 | await server.listen(); 22 | return server; 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /scripts/ci-dogfood-get-urls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 'use strict'; 7 | 8 | const _ = require('@lhci/utils/src/lodash.js'); 9 | const ApiClient = require('@lhci/utils/src/api-client.js'); 10 | 11 | async function main() { 12 | const rootURL = process.env.LHCI_ROOT_URL; 13 | if (!rootURL) throw new Error(`Missing LHCI_ROOT_URL environment variable`); 14 | 15 | const client = new ApiClient({rootURL}); 16 | 17 | const projects = await client.getProjects(); 18 | const project = projects.find(project => project.name.includes('Viewer')) || projects[0]; 19 | const builds = await client.getBuilds(project.id); 20 | const build = builds.find(build => build.branch.includes('test_1')) || builds[0]; 21 | 22 | process.stdout.write( 23 | [ 24 | new URL(`/app`, rootURL), 25 | new URL(`/app/projects/${project.slug}`, rootURL), 26 | new URL(`/app/projects/${project.slug}/dashboard`, rootURL), 27 | new URL(`/app/projects/${project.slug}/compare/${_.shortId(build.id)}`, rootURL), 28 | ].join('\n') 29 | ); 30 | 31 | process.exit(0); 32 | } 33 | 34 | main().catch(err => { 35 | process.stderr.write(err.stack); 36 | process.exit(1); 37 | }); 38 | -------------------------------------------------------------------------------- /scripts/ci-dogfood.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script requires LHCI_CANARY_SERVER_URL and LHCI_CANARY_SERVER_TOKEN variables to be set. 4 | 5 | set -ox pipefail 6 | 7 | # Start up our LHCI server. 8 | yarn start:server --port=9009 & 9 | # Wait for the server to start before hitting it with data. 10 | sleep 5 11 | 12 | # Seed the database with some data for us to audit. 13 | yarn start:seed-database 14 | # Collect our LHCI results. 15 | rm -rf .lighthouseci/ 16 | for url in $(LHCI_ROOT_URL=http://localhost:9009 node ./scripts/ci-dogfood-get-urls.js); do 17 | yarn start collect "--url=$url" --additive || exit 1 18 | done 19 | 20 | # Assert our results, but don't fail the build yet. 21 | yarn start assert 22 | EXIT_CODE=$? 23 | 24 | if [[ -n "$LHCI_CANARY_SERVER_URL" ]]; then 25 | # Upload the results to our canary server. 26 | yarn start upload \ 27 | --serverBaseUrl="$LHCI_CANARY_SERVER_URL" \ 28 | --token="$LHCI_CANARY_SERVER_TOKEN" 29 | fi 30 | 31 | # Upload the results to temporary public storage too 32 | export LHCI_GITHUB_STATUS_CONTEXT_SUFFIX="-2" 33 | export LHCI_GITHUB_APP_TOKEN="" 34 | export LHCI_GITHUB_TOKEN="$GITHUB_TOKEN" 35 | yarn start upload --target=temporary-public-storage 36 | 37 | 38 | # Kill the LHCI server from earlier. 39 | kill $! 40 | 41 | exit $EXIT_CODE 42 | -------------------------------------------------------------------------------- /scripts/deploy-gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -n "$(git status --porcelain)" ]; then 6 | echo "Cannot deploy to gh-pages when there are pending changes!" 7 | git status --porcelain 8 | exit 1 9 | fi 10 | 11 | if [[ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]]; then 12 | echo "Cannot deploy to gh-pages on a branch other than main!" 13 | git --no-pager branch 14 | exit 1 15 | fi 16 | 17 | if [[ "$(git status | grep -c 'Your branch is ahead')" -ge 1 ]]; then 18 | echo "Cannot deploy to gh-pages on a branch that isn't up to date!" 19 | git status 20 | exit 1 21 | fi 22 | 23 | # Prep the environment 24 | git pull 25 | git branch -D gh-pages || echo 'First time deploy!' 26 | git checkout -b gh-pages 27 | 28 | # Build the packages 29 | yarn clean 30 | yarn build 31 | cp -R packages/viewer/dist ./viewer 32 | cp -R packages/viewer/dist ./difftool 33 | 34 | # Create and push the deploy commit 35 | git add ./viewer ./difftool 36 | git commit -m 'gh pages deploy' 37 | git push -uf origin gh-pages 38 | 39 | # Cleanup 40 | git checkout -f main 41 | -------------------------------------------------------------------------------- /scripts/diff-seed-fixture.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @license Copyright 2019 Google Inc. All Rights Reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 5 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | */ 7 | 'use strict'; 8 | 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | const {createDefaultDataset} = require('../packages/utils/src/seed-data/seed-data.js'); 12 | 13 | const FIXTURE_PATH = path.join(__dirname, '../packages/server/test/fixtures/seed-data.json'); 14 | 15 | function run() { 16 | const existingContents = JSON.parse(fs.readFileSync(FIXTURE_PATH, 'utf8')); 17 | const newContents = createDefaultDataset(); 18 | 19 | newContents.runs.forEach((newRun, i) => { 20 | const oldRun = existingContents.runs[i]; 21 | 22 | it('should match the lhr', () => { 23 | expect(JSON.parse(newRun.lhr)).toEqual(JSON.parse(oldRun.lhr)); 24 | }); 25 | }); 26 | } 27 | 28 | run(); 29 | -------------------------------------------------------------------------------- /scripts/open-seed-report-in-viewer.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @license Copyright 2019 Google Inc. All Rights Reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 5 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | */ 7 | 'use strict'; 8 | 9 | const fs = require('fs'); 10 | const puppeteer = require('puppeteer'); 11 | const {createDefaultDataset} = require('../packages/utils/src/seed-data/seed-data.js'); 12 | 13 | async function run() { 14 | const lhr = JSON.stringify(JSON.parse(createDefaultDataset().runs[0].lhr), null, 2); 15 | const browser = await puppeteer.launch({headless: false, devtools: true}); 16 | const page = await browser.newPage(); 17 | await page.goto('https://googlechrome.github.io/lighthouse/viewer/'); 18 | await page.evaluate(() => console.log('Loaded')); 19 | await page.waitForTimeout(1000); 20 | await page.evaluate(() => console.log('Evaling')); 21 | await page.evaluate(lhr => { 22 | const dataTransfer = new DataTransfer(); 23 | dataTransfer.setData('text', lhr); 24 | const event = new ClipboardEvent('paste', {clipboardData: dataTransfer}); 25 | document.dispatchEvent(event); 26 | }, lhr); 27 | 28 | console.log(lhr); 29 | fs.writeFileSync('lhr.tmp.json', lhr); 30 | } 31 | 32 | run(); 33 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! [ $# -eq 2 ]; then 4 | echo "Expected two args: CURRENT_VERSION NEXT_VERSION" 5 | echo "example: 0.12.0 0.13.0" 6 | exit 1 7 | fi 8 | 9 | set -euox pipefail 10 | 11 | CURRENT_VERSION=$1 12 | NEXT_VERSION=$2 13 | CURRENT_TAG="v$CURRENT_VERSION" 14 | NEXT_TAG="v$NEXT_VERSION" 15 | 16 | if [ -n "$(git status --porcelain)" ]; then 17 | echo "Cannot release when there are pending changes!" 18 | git status --porcelain 19 | exit 1 20 | fi 21 | 22 | if [[ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]]; then 23 | echo "Cannot release on a branch other than main!" 24 | git --no-pager branch 25 | exit 1 26 | fi 27 | 28 | git fetch origin main 29 | if [[ "$(git rev-parse main)" != "$(git rev-parse origin/main)" ]]; then 30 | echo "Can only publish when changes are synced with origin." 31 | exit 1 32 | fi 33 | 34 | # Build the packages 35 | yarn clean 36 | yarn build 37 | 38 | # Release 39 | # hulk npm-publish --lerna 40 | 41 | yarn lerna publish '--force-publish=*' --exact --skip-git --repo-version=$NEXT_VERSION --npm-tag=latest --yes 42 | git checkout lerna.json # lerna prettifies the JSON and isn't useful 43 | git tag "$NEXT_TAG" 44 | node ./scripts/print-changelog.js "$CURRENT_TAG" "$NEXT_TAG" 45 | echo "----" 46 | echo "Take the above changelog and add to GH release" 47 | 48 | git push --tags 49 | 50 | # Do related releases 51 | ./scripts/deploy-gh-pages.sh 52 | 53 | # Update all the version tags 54 | ./scripts/update-version-tags.sh 55 | -------------------------------------------------------------------------------- /scripts/seed-database.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @license Copyright 2019 Google Inc. All Rights Reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 5 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | */ 7 | 'use strict'; 8 | 9 | const {loadAndParseRcFile, resolveRcFilePath} = require('../packages/utils/src/lighthouserc.js'); 10 | const ApiClient = require('../packages/utils/src/api-client.js'); 11 | const {writeSeedDataToApi} = require('../packages/utils/src/seed-data/seed-data.js'); 12 | const {createActualTestDataset} = require('../packages/server/test/test-utils.js'); 13 | const { 14 | createDefaultDataset, 15 | createLoadTestDataset, 16 | } = require('../packages/utils/src/seed-data/dataset-generator.js'); 17 | 18 | if (process.argv.length !== 3 && process.argv.length !== 4) { 19 | process.stderr.write(`Usage ./scripts/seed-database.js [--load]`); 20 | process.exit(1); 21 | } 22 | 23 | async function run() { 24 | const {serverBaseUrl} = loadAndParseRcFile(resolveRcFilePath(process.argv[2])); 25 | if (!serverBaseUrl) throw new Error('RC file did not set the serverBaseUrl'); 26 | 27 | const api = new ApiClient({rootURL: serverBaseUrl}); 28 | await writeSeedDataToApi( 29 | api, 30 | process.argv.includes('--load') 31 | ? createLoadTestDataset() 32 | : process.argv.includes('--actual') 33 | ? createActualTestDataset() 34 | : createDefaultDataset() 35 | ); 36 | } 37 | 38 | run(); 39 | -------------------------------------------------------------------------------- /scripts/source-map-explorer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | sed -i '' 's/sourceMappingURL=\/app\/chunks/sourceMappingURL=./' ./dist/chunks/*.js 6 | source-map-explorer dist/chunks/entry*.js 7 | -------------------------------------------------------------------------------- /scripts/test-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | cd ./docs/recipes/docker-client 6 | bash test.sh 7 | cd ../../../ 8 | 9 | cd ./docs/recipes/docker-server 10 | bash test.sh 11 | cd ../../../ 12 | 13 | -------------------------------------------------------------------------------- /scripts/test-lh-report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | trap 'kill $(jobs -p)' EXIT 6 | 7 | DIRNAME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | LH_ROOT_PATH="$DIRNAME/../.." 9 | 10 | cd $LH_ROOT_PATH 11 | node cli/test/fixtures/static-server.js & 12 | yarn start http://localhost:10200/dobetterweb/dbw_tester.html --chrome-flags="--headless=new" --output-path=./lighthouse-ci/ci-test.report.html 13 | 14 | cd ./lighthouse-ci 15 | export LHCI_CONFIG="./test/fixtures/lighthouserc.json" 16 | yarn start server & 17 | yarn start collect --url=http://localhost:10200/lighthouse-ci/ci-test.report.html 18 | yarn start assert 19 | -------------------------------------------------------------------------------- /scripts/update-seed-fixtures.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * @license Copyright 2019 Google Inc. All Rights Reserved. 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 5 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | */ 7 | 'use strict'; 8 | 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | const {createDefaultDataset} = require('../packages/utils/src/seed-data/seed-data.js'); 12 | 13 | const FIXTURE_PATH = path.join(__dirname, '../packages/server/test/fixtures/seed-data.json'); 14 | 15 | function run() { 16 | fs.writeFileSync(FIXTURE_PATH, JSON.stringify(createDefaultDataset(), null, 2)); 17 | } 18 | 19 | run(); 20 | -------------------------------------------------------------------------------- /scripts/update-version-tags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euox pipefail 4 | 5 | # Update the next tag on NPM with latest 6 | LATEST_VERSION=$(git describe --abbrev=0 --tags | sed s/v//g) 7 | npm dist-tag add "@lhci/utils@$LATEST_VERSION" next 8 | npm dist-tag add "@lhci/server@$LATEST_VERSION" next 9 | npm dist-tag add "@lhci/cli@$LATEST_VERSION" next 10 | 11 | cd ./docs/recipes/docker-client 12 | ./update-dockerhub.sh 13 | cd ../../../ 14 | 15 | cd ./docs/recipes/docker-server 16 | ./update-dockerhub.sh 17 | cd ../../../ 18 | 19 | git status 20 | git --no-pager diff ./docs 21 | git add ./docs 22 | 23 | printf "Continue with the docker commit?\n" 24 | read -n 1 -p "Press any key to continue, Ctrl+C to exit..." 25 | 26 | git commit -m 'chore: update docker images with latest version' 27 | git push 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | "module": "commonjs", 5 | "target": "ES2021", 6 | "lib": ["ES2021"], 7 | "allowJs": true, 8 | "checkJs": true, 9 | "strict": true, 10 | "useUnknownInCatchVariables": false, 11 | 12 | "skipLibCheck": true, 13 | 14 | "jsx": "react", 15 | "jsxFactory": "h", 16 | "jsxFragmentFactory": "Fragment", 17 | "resolveJsonModule": true, 18 | "esModuleInterop": true, 19 | "diagnostics": true 20 | }, 21 | "include": [ 22 | "packages/*/src/**/*.js", 23 | "types/**/*.ts", 24 | "packages/*/src/**/*.jsx", 25 | "scripts/*.js", 26 | "packages/server/test/api/*.js", 27 | "packages/server/test/e2e/**/*.js", 28 | "packages/viewer/test/**/*.js" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /types/autorun.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare global { 8 | namespace LHCI { 9 | namespace AutorunCommand { 10 | export interface Options { 11 | config?: string; 12 | failOnUploadFailure?: boolean; 13 | collect?: CollectCommand.Options; 14 | assert?: AssertCommand.Options; 15 | upload?: UploadCommand.Options; 16 | } 17 | } 18 | } 19 | } 20 | 21 | // empty export to keep file a module 22 | export {}; 23 | -------------------------------------------------------------------------------- /types/healthcheck.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare global { 8 | namespace LHCI { 9 | namespace HealthcheckCommand { 10 | export interface Options { 11 | fatal?: boolean; 12 | checks: string[]; 13 | } 14 | } 15 | } 16 | } 17 | 18 | // empty export to keep file a module 19 | export {}; 20 | -------------------------------------------------------------------------------- /types/lighthouse-logger.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare module 'lighthouse-logger' { 8 | export const bold: string; 9 | export const dim: string; 10 | export const cross: string; 11 | export const reset: string; 12 | export const redify: (s: string | number) => string; 13 | export const greenify: (s: string | number) => string; 14 | } 15 | -------------------------------------------------------------------------------- /types/lighthouserc.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare global { 8 | namespace LHCI { 9 | export interface LighthouseCiConfig { 10 | extends?: string; 11 | assert?: Partial; 12 | collect?: Partial; 13 | upload?: Partial; 14 | server?: Partial; 15 | wizard?: Partial; 16 | } 17 | 18 | export interface LighthouseRc { 19 | ci?: LighthouseCiConfig; 20 | lhci?: LighthouseCiConfig; 21 | 'ci:client'?: LighthouseCiConfig; 22 | 'ci:server'?: LighthouseCiConfig; 23 | } 24 | } 25 | } 26 | 27 | // empty export to keep file a module 28 | export {}; 29 | -------------------------------------------------------------------------------- /types/open.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare global { 8 | namespace LHCI { 9 | namespace OpenCommand { 10 | export interface Options { 11 | url?: string | string[]; 12 | } 13 | } 14 | } 15 | } 16 | 17 | // empty export to keep file a module 18 | export {}; 19 | -------------------------------------------------------------------------------- /types/preact.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare namespace JSX { 8 | type Element = import('preact').JSX.Element; 9 | type HTMLAttributes = import('preact').JSX.HTMLAttributes; 10 | } 11 | -------------------------------------------------------------------------------- /types/wizard.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright 2019 Google Inc. All Rights Reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 4 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 5 | */ 6 | 7 | declare global { 8 | namespace LHCI { 9 | namespace WizardCommand { 10 | export interface Options { 11 | wizard?: 'new-project' | 'reset-admin-token'; 12 | extraHeaders?: Record; 13 | basicAuth?: ServerCommand.Options['basicAuth']; 14 | serverBaseUrl?: string; 15 | storage?: ServerCommand.StorageOptions; 16 | } 17 | } 18 | } 19 | } 20 | 21 | // empty export to keep file a module 22 | export {}; 23 | --------------------------------------------------------------------------------