Upload folder using huggingface_hub
Browse files- .docker/postgres/init-db.sql +5 -0
- .docker/pubsub/setup_pubsub.sh +53 -0
- .github/dependabot.yml +96 -0
- .github/linter_config.yml +59 -0
- .github/pull_request_template.md +29 -0
- .github/requirements.txt +1 -0
- .github/workflows/build.yaml +27 -0
- .github/workflows/conflicts.yml +22 -0
- .github/workflows/docker_build_deploy.yml +66 -0
- .github/workflows/docker_build_deploy_v2.yml +95 -0
- .github/workflows/docker_check.yml +28 -0
- .github/workflows/functional_tests_cron.yml +135 -0
- .github/workflows/functional_tests_pr.yml +100 -0
- .github/workflows/glean-probe-scraper.yml +16 -0
- .github/workflows/lighthouse_cron.yml +54 -0
- .github/workflows/lint.yaml +28 -0
- .github/workflows/production_deploy.yml +81 -0
- .github/workflows/reference_linter.yaml +31 -0
- .github/workflows/release_cron_daily.yml +105 -0
- .github/workflows/release_retag.yaml +45 -0
- .github/workflows/release_retag_v2.yaml +83 -0
- .github/workflows/test_integrations.yml +52 -0
- .github/workflows/unittests.yaml +41 -0
- .gitignore +80 -0
- .husky/pre-commit +1 -0
- .storybook/NextImage.tsx +17 -0
- .storybook/main.ts +56 -0
- .storybook/preview-head.html +29 -0
- .storybook/preview.tsx +218 -0
- .vscode/extensions.json +16 -0
- .vscode/settings.json +30 -0
- CNAME +1 -0
- CODE_OF_CONDUCT.md +15 -0
- LICENSE +373 -0
- README.md +323 -0
- google3c147320b7d46152.html +1 -0
.docker/postgres/init-db.sql
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
CREATE USER blurts WITH ENCRYPTED PASSWORD 'blurts';
|
| 2 |
+
CREATE DATABASE blurts WITH OWNER 'blurts';
|
| 3 |
+
CREATE DATABASE "test-blurts" WITH OWNER 'blurts';
|
| 4 |
+
|
| 5 |
+
ALTER DEFAULT privileges IN SCHEMA public GRANT ALL ON tables to blurts;
|
.docker/pubsub/setup_pubsub.sh
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Ensure the emulator host is set
|
| 4 |
+
PUBSUB_HOST=$1
|
| 5 |
+
PUBSUB_PORT=$2
|
| 6 |
+
PROJECT_ID=$3
|
| 7 |
+
|
| 8 |
+
PUBSUB_EMULATOR_HOST="${PUBSUB_HOST}:${PUBSUB_PORT}"
|
| 9 |
+
|
| 10 |
+
# Start PubSub emulator in the background
|
| 11 |
+
gcloud beta emulators pubsub start --host-port=0.0.0.0:${PUBSUB_PORT} --project=${PROJECT_ID} &
|
| 12 |
+
echo "Waiting for Pub/Sub emulator to be ready..."
|
| 13 |
+
until curl -s "http://${PUBSUB_EMULATOR_HOST}/v1/projects/${PROJECT_ID}/schemas" > /dev/null; do
|
| 14 |
+
echo "Pub/Sub emulator not ready yet..."
|
| 15 |
+
sleep 2
|
| 16 |
+
done
|
| 17 |
+
echo "Pub/Sub emulator is ready!"
|
| 18 |
+
echo "Initializing Pub/Sub emulator..."
|
| 19 |
+
|
| 20 |
+
create_topic_and_subscription () {
|
| 21 |
+
if [ $# -ne 2 ]; then
|
| 22 |
+
echo "create_topic_and_subscription takes 2 arguments"
|
| 23 |
+
exit 1
|
| 24 |
+
fi
|
| 25 |
+
if [ -z "$1" ]; then
|
| 26 |
+
echo "create_topic_and_subscription requires non-empty topic"
|
| 27 |
+
exit 1
|
| 28 |
+
fi
|
| 29 |
+
if [ -z "$2" ]; then
|
| 30 |
+
echo "create_topic_and_subscription requires non-empty subscription"
|
| 31 |
+
exit 1
|
| 32 |
+
fi
|
| 33 |
+
|
| 34 |
+
topic=$1
|
| 35 |
+
subscription=$2
|
| 36 |
+
echo "Creating topic '$topic' with subscription '$subscription'..."
|
| 37 |
+
|
| 38 |
+
# Create the topic using REST API instead of gcloud
|
| 39 |
+
curl -s -X PUT "${PUBSUB_EMULATOR_HOST}/v1/projects/${PROJECT_ID}/topics/${topic}"
|
| 40 |
+
|
| 41 |
+
# Create the subscription using REST API instead of gcloud
|
| 42 |
+
curl -s -X PUT "${PUBSUB_EMULATOR_HOST}/v1/projects/${PROJECT_ID}/subscriptions/${subscription}" \
|
| 43 |
+
-H "Content-Type: application/json" \
|
| 44 |
+
-d "{\"topic\": \"projects/${PROJECT_ID}/topics/${topic}\"}"
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
create_topic_and_subscription hibp-breaches hibp-cron
|
| 48 |
+
|
| 49 |
+
echo "Pub/Sub initialization completed."
|
| 50 |
+
touch /tmp/startup.done
|
| 51 |
+
|
| 52 |
+
# Keep emulator running
|
| 53 |
+
wait
|
.github/dependabot.yml
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See the documentation for all configuration options:
|
| 2 |
+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
| 3 |
+
|
| 4 |
+
version: 2
|
| 5 |
+
updates:
|
| 6 |
+
- package-ecosystem: "github-actions"
|
| 7 |
+
directory: "/"
|
| 8 |
+
schedule:
|
| 9 |
+
interval: "weekly"
|
| 10 |
+
cooldown:
|
| 11 |
+
default-days: 8
|
| 12 |
+
- package-ecosystem: "npm"
|
| 13 |
+
directory: "/"
|
| 14 |
+
schedule:
|
| 15 |
+
interval: "weekly"
|
| 16 |
+
cooldown:
|
| 17 |
+
default-days: 8
|
| 18 |
+
open-pull-requests-limit: 10
|
| 19 |
+
groups:
|
| 20 |
+
eslint:
|
| 21 |
+
patterns:
|
| 22 |
+
- "@typescript-eslint/*"
|
| 23 |
+
- "eslint"
|
| 24 |
+
- "eslint-*"
|
| 25 |
+
exclude-patterns:
|
| 26 |
+
- "eslint-config-next"
|
| 27 |
+
- "eslint-plugin-storybook"
|
| 28 |
+
jest:
|
| 29 |
+
patterns:
|
| 30 |
+
- "babel-jest"
|
| 31 |
+
- "jest"
|
| 32 |
+
- "jest-environment-jsdom"
|
| 33 |
+
testing-library:
|
| 34 |
+
patterns:
|
| 35 |
+
- "@testing-library/dom"
|
| 36 |
+
- "@testing-library/react"
|
| 37 |
+
- "@testing-library/user-event"
|
| 38 |
+
react:
|
| 39 |
+
patterns:
|
| 40 |
+
- "react"
|
| 41 |
+
- "react-dom"
|
| 42 |
+
- "react-test-renderer"
|
| 43 |
+
sentry:
|
| 44 |
+
patterns:
|
| 45 |
+
- "@sentry/*"
|
| 46 |
+
aws-sdk:
|
| 47 |
+
patterns:
|
| 48 |
+
- "@aws-sdk/*"
|
| 49 |
+
storybook:
|
| 50 |
+
patterns:
|
| 51 |
+
- "storybook"
|
| 52 |
+
- "@storybook/*"
|
| 53 |
+
- "eslint-plugin-storybook"
|
| 54 |
+
exclude-patterns:
|
| 55 |
+
- "eslint-config-next"
|
| 56 |
+
fluent:
|
| 57 |
+
patterns:
|
| 58 |
+
- "@fluent/*"
|
| 59 |
+
nextjs:
|
| 60 |
+
patterns:
|
| 61 |
+
- "eslint-config-next"
|
| 62 |
+
- "next"
|
| 63 |
+
- "@next/*"
|
| 64 |
+
react-aria:
|
| 65 |
+
patterns:
|
| 66 |
+
- "react-aria"
|
| 67 |
+
- "react-stately"
|
| 68 |
+
stylelint:
|
| 69 |
+
patterns:
|
| 70 |
+
- "stylelint"
|
| 71 |
+
- "stylelint-scss"
|
| 72 |
+
- "stylelint-config-recommended-scss"
|
| 73 |
+
- package-ecosystem: "docker"
|
| 74 |
+
directory: "/"
|
| 75 |
+
schedule:
|
| 76 |
+
interval: "weekly"
|
| 77 |
+
cooldown:
|
| 78 |
+
default-days: 8
|
| 79 |
+
allow:
|
| 80 |
+
- dependency-type: "all"
|
| 81 |
+
ignore:
|
| 82 |
+
- dependency-name: "node"
|
| 83 |
+
# Odd-numbered versions are unstable releases, so skip those.
|
| 84 |
+
# This is using Ruby's version range syntax, specifically, the "twiddle-wakka"
|
| 85 |
+
# to indicate any matching version until the next major one..
|
| 86 |
+
# See https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#versions-ignore
|
| 87 |
+
# and https://guides.rubygems.org/patterns/#pessimistic-version-constraint
|
| 88 |
+
versions: ["~> 23", "<= 24.10", "~> 25", "~> 27", "~> 29"]
|
| 89 |
+
# The newest major version of faker is ESM-only which is difficult
|
| 90 |
+
# to support with jest
|
| 91 |
+
# Since faker is a zero-dependency library, ignore major version
|
| 92 |
+
# upgrades (can consider the work to update if/when we need any
|
| 93 |
+
# functionality from the new version)
|
| 94 |
+
# See https://github.com/faker-js/faker/issues/3606#issuecomment-3233612736
|
| 95 |
+
- dependency-name: "@faker-js/faker"
|
| 96 |
+
update-types: ["version-update:semver-major"]
|
.github/linter_config.yml
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
| 2 |
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 3 |
+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| 4 |
+
|
| 5 |
+
# ID checks
|
| 6 |
+
# ID01: check that identifiers only use lowercase and hyphens
|
| 7 |
+
# ID02: check that identifiers have a minimum length
|
| 8 |
+
#
|
| 9 |
+
# Syntax checks
|
| 10 |
+
#
|
| 11 |
+
# Specify if syntax features are disabled. If the config is missing, it
|
| 12 |
+
# will considered as enabled.
|
| 13 |
+
#
|
| 14 |
+
# Example:
|
| 15 |
+
# SY01:
|
| 16 |
+
# disabled: true
|
| 17 |
+
#
|
| 18 |
+
# SY01: terms
|
| 19 |
+
# SY02: message references
|
| 20 |
+
# SY03: term references
|
| 21 |
+
# SY04: variants
|
| 22 |
+
# SY05: attributes
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
ID01:
|
| 26 |
+
enabled: false
|
| 27 |
+
exclusions:
|
| 28 |
+
messages: []
|
| 29 |
+
files: []
|
| 30 |
+
ID02:
|
| 31 |
+
enabled: false
|
| 32 |
+
min_length: 6
|
| 33 |
+
exclusions:
|
| 34 |
+
messages: []
|
| 35 |
+
files: []
|
| 36 |
+
CO01:
|
| 37 |
+
enabled: true
|
| 38 |
+
brands:
|
| 39 |
+
- Firefox
|
| 40 |
+
- Mozilla
|
| 41 |
+
- Relay
|
| 42 |
+
- Monitor
|
| 43 |
+
- "{ -brand-mozilla } account"
|
| 44 |
+
exclusions:
|
| 45 |
+
messages:
|
| 46 |
+
- breach-checklist-ssn-header
|
| 47 |
+
- fxa-what-to-do-blurb-3
|
| 48 |
+
- monitor-several-emails
|
| 49 |
+
- rec-bank-acc-subhead
|
| 50 |
+
- rec-cc-subhead
|
| 51 |
+
- dashboard-top-banner-monitor-more-cta
|
| 52 |
+
- announcement-add-up-to-20-emails-plus-title
|
| 53 |
+
files: []
|
| 54 |
+
# Variable comments
|
| 55 |
+
VC:
|
| 56 |
+
disabled: false
|
| 57 |
+
# Placeables style
|
| 58 |
+
PS01:
|
| 59 |
+
disabled: false
|
.github/pull_request_template.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- The following is intended to be helpful to you. Feel free to remove anything that is not. -->
|
| 2 |
+
|
| 3 |
+
# References:
|
| 4 |
+
|
| 5 |
+
Jira: MNTOR-
|
| 6 |
+
Figma:
|
| 7 |
+
|
| 8 |
+
<!-- When adding a new feature: -->
|
| 9 |
+
|
| 10 |
+
# Description
|
| 11 |
+
|
| 12 |
+
# Screenshot (if applicable)
|
| 13 |
+
|
| 14 |
+
Not applicable.
|
| 15 |
+
|
| 16 |
+
# How to test
|
| 17 |
+
|
| 18 |
+
# Checklist (Definition of Done)
|
| 19 |
+
|
| 20 |
+
- [ ] Localization strings (if needed) have been added.
|
| 21 |
+
- [ ] Commits in this PR are minimal and [have descriptive commit messages](https://chris.beams.io/posts/git-commit/).
|
| 22 |
+
- [ ] I've added or updated the relevant sections in readme and/or code comments
|
| 23 |
+
- [ ] I've added a unit test to test for potential regressions of this bug.
|
| 24 |
+
- [ ] If this PR implements a feature flag or experimentation, I've checked that it still works with the flag both on, and with the flag off.
|
| 25 |
+
- [ ] If this PR implements a feature flag or experimentation, the Ship Behind Feature Flag status in Jira has been set
|
| 26 |
+
- [ ] Product Owner accepted the User Story (demo of functionality completed) or waived the privilege.
|
| 27 |
+
- [ ] All acceptance criteria are met.
|
| 28 |
+
- [ ] Jira ticket has been updated (if needed) to match changes made during the development process.
|
| 29 |
+
- [ ] Jira ticket has been updated (if needed) with suggestions for QA when this PR is deployed to stage.
|
.github/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
moz-fluent-linter==0.4.*
|
.github/workflows/build.yaml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Build
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
pull_request:
|
| 7 |
+
push:
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
npm-build:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@v6
|
| 15 |
+
with:
|
| 16 |
+
persist-credentials: false
|
| 17 |
+
- name: Use Node.js
|
| 18 |
+
uses: actions/setup-node@v6
|
| 19 |
+
with:
|
| 20 |
+
node-version: "20.19.x"
|
| 21 |
+
- run: npm ci
|
| 22 |
+
- run: npm run build-glean
|
| 23 |
+
# Verify that the build (incl. type-checking) succeeds
|
| 24 |
+
# Upload sourcemaps to Sentry
|
| 25 |
+
- run: npm run build
|
| 26 |
+
env:
|
| 27 |
+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
.github/workflows/conflicts.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: No unresolved conflicts
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main, localization ]
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
detect-unresolved-conflicts:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- uses: actions/checkout@v6
|
| 14 |
+
with:
|
| 15 |
+
persist-credentials: false
|
| 16 |
+
- name: List files with merge conflict markers
|
| 17 |
+
# Encode conflict markers so this file does not trigger git's conflict detection.
|
| 18 |
+
run: git --no-pager grep "$(echo 'PDw8PDw8PAo=' | base64 -d)" ":(exclude).github/" || true
|
| 19 |
+
- name: Fail or succeed job if any files with merge conflict markers have been checked in
|
| 20 |
+
# Find lines containing conflict markers then count the number of lines.
|
| 21 |
+
# 0 matching lines results in exit code 0, i.e. success.
|
| 22 |
+
run: exit $(git grep "$(echo 'PDw8PDw8PAo=' | base64 -d)" ":(exclude).github/" | wc --lines)
|
.github/workflows/docker_build_deploy.yml
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Build Docker image and publish
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
push:
|
| 7 |
+
branches: [ main ]
|
| 8 |
+
jobs:
|
| 9 |
+
push_to_registry:
|
| 10 |
+
name: Push Docker image to Docker Hub
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- name: Check out the repo
|
| 14 |
+
uses: actions/checkout@v6
|
| 15 |
+
with:
|
| 16 |
+
persist-credentials: false
|
| 17 |
+
|
| 18 |
+
- name: Log in to Docker Hub
|
| 19 |
+
uses: docker/login-action@v3
|
| 20 |
+
with:
|
| 21 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
| 22 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
| 23 |
+
|
| 24 |
+
- name: Extract metadata (tags, labels) for Docker
|
| 25 |
+
id: meta
|
| 26 |
+
uses: docker/metadata-action@v5
|
| 27 |
+
with:
|
| 28 |
+
images: mozilla/blurts-server
|
| 29 |
+
tags: |
|
| 30 |
+
type=semver,pattern={{raw}}
|
| 31 |
+
type=raw,value={{sha}},event=tag
|
| 32 |
+
|
| 33 |
+
- name: Create version.json
|
| 34 |
+
run: |
|
| 35 |
+
echo "{\"commit\":\"$GITHUB_SHA\",\"version\":\"$GITHUB_REF_NAME\",\"source\":\"https://github.com/$GITHUB_REPOSITORY\",\"build\":\"$GITHUB_RUN_ID\"}" > version.json
|
| 36 |
+
|
| 37 |
+
- name: Check Docker Version
|
| 38 |
+
run: docker --version
|
| 39 |
+
- name: Install Latest Docker
|
| 40 |
+
run: |
|
| 41 |
+
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
| 42 |
+
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
| 43 |
+
sudo apt-get update
|
| 44 |
+
sudo apt-get install docker-ce
|
| 45 |
+
|
| 46 |
+
- name: Build Docker image
|
| 47 |
+
env:
|
| 48 |
+
UPLOAD_SENTRY_SOURCEMAPS: true
|
| 49 |
+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
| 50 |
+
SENTRY_RELEASE: ${{ github.ref_name }}
|
| 51 |
+
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
| 52 |
+
run: |
|
| 53 |
+
docker build --tag blurts-server \
|
| 54 |
+
--build-arg SENTRY_RELEASE="$SENTRY_RELEASE" \
|
| 55 |
+
--build-arg NEXT_PUBLIC_SENTRY_DSN="$NEXT_PUBLIC_SENTRY_DSN" \
|
| 56 |
+
--secret id=SENTRY_AUTH_TOKEN \
|
| 57 |
+
.
|
| 58 |
+
|
| 59 |
+
- name: Deploy to Dockerhub
|
| 60 |
+
env:
|
| 61 |
+
DOCKERHUB_REPO: ${{ env.DOCKERHUB_REPO }}
|
| 62 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 63 |
+
run: |
|
| 64 |
+
# deploy main
|
| 65 |
+
docker tag blurts-server $TAGS
|
| 66 |
+
docker push $TAGS
|
.github/workflows/docker_build_deploy_v2.yml
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Build Docker image and publish to GAR
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
push:
|
| 7 |
+
branches:
|
| 8 |
+
- main
|
| 9 |
+
tags:
|
| 10 |
+
- "*"
|
| 11 |
+
|
| 12 |
+
jobs:
|
| 13 |
+
build_and_push_to_gar:
|
| 14 |
+
# Define permissions at the job level
|
| 15 |
+
permissions:
|
| 16 |
+
contents: "read" # Needed for checkout
|
| 17 |
+
id-token: "write" # Needed for GCP auth
|
| 18 |
+
packages: "none" # Explicitly disable package permissions
|
| 19 |
+
name: Build and Push Docker image to GAR
|
| 20 |
+
runs-on: ubuntu-latest
|
| 21 |
+
environment: build
|
| 22 |
+
env:
|
| 23 |
+
GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }} # Base name for GAR image
|
| 24 |
+
GAR_REGISTRY: us-docker.pkg.dev
|
| 25 |
+
steps:
|
| 26 |
+
- name: Check out the repo
|
| 27 |
+
uses: actions/checkout@v6
|
| 28 |
+
with:
|
| 29 |
+
persist-credentials: false
|
| 30 |
+
|
| 31 |
+
- name: Authenticate to Google Cloud
|
| 32 |
+
id: gcp-auth
|
| 33 |
+
uses: google-github-actions/auth@v3
|
| 34 |
+
with:
|
| 35 |
+
token_format: access_token
|
| 36 |
+
workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
|
| 37 |
+
service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
|
| 38 |
+
|
| 39 |
+
- name: Login to Artifact Registry
|
| 40 |
+
id: gar-login
|
| 41 |
+
uses: docker/login-action@v3
|
| 42 |
+
with:
|
| 43 |
+
registry: ${{ env.GAR_REGISTRY }}
|
| 44 |
+
username: oauth2accesstoken
|
| 45 |
+
password: ${{ steps.gcp-auth.outputs.access_token }}
|
| 46 |
+
|
| 47 |
+
- name: Extract metadata (tags, labels) for Docker
|
| 48 |
+
id: meta
|
| 49 |
+
uses: docker/metadata-action@v5
|
| 50 |
+
with:
|
| 51 |
+
# Only generate the image name for GAR
|
| 52 |
+
images: ${{ env.GAR_IMAGE_BASE }}
|
| 53 |
+
tags: |
|
| 54 |
+
# Generate tag based on short commit SHA
|
| 55 |
+
type=sha,format=short,prefix=
|
| 56 |
+
|
| 57 |
+
- name: Create version.json
|
| 58 |
+
run: |
|
| 59 |
+
# Use full sha here for version.json content
|
| 60 |
+
echo "{\"commit\":\"$GITHUB_SHA\",\"version\":\"$GITHUB_REF_NAME\",\"source\":\"https://github.com/$GITHUB_REPOSITORY\",\"build\":\"$GITHUB_RUN_ID\"}" > version.json
|
| 61 |
+
|
| 62 |
+
- name: Set up QEMU
|
| 63 |
+
uses: docker/setup-qemu-action@v3
|
| 64 |
+
|
| 65 |
+
- name: Set up Docker Buildx
|
| 66 |
+
id: buildx
|
| 67 |
+
uses: docker/setup-buildx-action@v3
|
| 68 |
+
|
| 69 |
+
- name: Build and push Docker image to GAR
|
| 70 |
+
id: build-and-push
|
| 71 |
+
env:
|
| 72 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 73 |
+
uses: docker/build-push-action@v6
|
| 74 |
+
with:
|
| 75 |
+
context: .
|
| 76 |
+
# Push is true to push to GAR after build
|
| 77 |
+
push: true
|
| 78 |
+
# Tags generated by the metadata action (only GAR tag)
|
| 79 |
+
tags: ${{ env.TAGS }}
|
| 80 |
+
# Pass build arguments
|
| 81 |
+
build-args: |
|
| 82 |
+
SENTRY_RELEASE=${{ github.sha }}
|
| 83 |
+
NEXT_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}
|
| 84 |
+
# Pass secrets securely to the build
|
| 85 |
+
secrets: |
|
| 86 |
+
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
| 87 |
+
# Enable build cache for faster builds (optional but recommended)
|
| 88 |
+
cache-from: type=gha
|
| 89 |
+
cache-to: type=gha,mode=max
|
| 90 |
+
|
| 91 |
+
- name: Print Image URI
|
| 92 |
+
env:
|
| 93 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 94 |
+
run: |
|
| 95 |
+
echo "Pushed GAR image: $TAGS"
|
.github/workflows/docker_check.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Build Docker image check
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
pull_request:
|
| 7 |
+
jobs:
|
| 8 |
+
docker_build:
|
| 9 |
+
name: Build Docker image
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
steps:
|
| 12 |
+
- name: Check Docker Version
|
| 13 |
+
run: docker --version
|
| 14 |
+
|
| 15 |
+
- name: Install Latest Docker
|
| 16 |
+
run: |
|
| 17 |
+
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
|
| 18 |
+
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
| 19 |
+
sudo apt-get update
|
| 20 |
+
sudo apt-get install docker-ce
|
| 21 |
+
|
| 22 |
+
- name: Check out the repo
|
| 23 |
+
uses: actions/checkout@v6
|
| 24 |
+
with:
|
| 25 |
+
persist-credentials: false
|
| 26 |
+
|
| 27 |
+
- name: Build Docker image
|
| 28 |
+
run: docker build .
|
.github/workflows/functional_tests_cron.yml
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Functional Test Suite (cron)
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
schedule:
|
| 7 |
+
- cron: '0 8 * * *'
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
inputs:
|
| 10 |
+
test_env:
|
| 11 |
+
description: 'Environment to run the functional test suite against'
|
| 12 |
+
required: false
|
| 13 |
+
default: ''
|
| 14 |
+
type: choice
|
| 15 |
+
options:
|
| 16 |
+
- ''
|
| 17 |
+
- stage
|
| 18 |
+
- production
|
| 19 |
+
jobs:
|
| 20 |
+
functional-tests:
|
| 21 |
+
name: Functional tests (${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}) [${{ github.event_name }}]
|
| 22 |
+
timeout-minutes: 60
|
| 23 |
+
runs-on: ubuntu-latest
|
| 24 |
+
strategy:
|
| 25 |
+
matrix:
|
| 26 |
+
test_env: ${{ github.event_name == 'schedule' && fromJson('["stage","production"]') || fromJson('["unset"]') }}
|
| 27 |
+
steps:
|
| 28 |
+
- name: Fail manual run if environment is missing
|
| 29 |
+
if: github.event_name == 'workflow_dispatch' && inputs.test_env == ''
|
| 30 |
+
run: |
|
| 31 |
+
echo "Please select an environment for manual runs"
|
| 32 |
+
exit 1
|
| 33 |
+
|
| 34 |
+
- uses: actions/checkout@v6
|
| 35 |
+
with:
|
| 36 |
+
persist-credentials: false
|
| 37 |
+
- uses: actions/setup-node@v6
|
| 38 |
+
with:
|
| 39 |
+
node-version: 20.19.x
|
| 40 |
+
|
| 41 |
+
- name: Install dependencies
|
| 42 |
+
run: npm ci
|
| 43 |
+
- name: Store Playwright’s Version
|
| 44 |
+
run: |
|
| 45 |
+
# Get the current Playwright version listed in package.json
|
| 46 |
+
PLAYWRIGHT_VERSION=$(npx playwright --version | sed 's/Version //')
|
| 47 |
+
echo "Playwright Version: $PLAYWRIGHT_VERSION"
|
| 48 |
+
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
|
| 49 |
+
|
| 50 |
+
- name: Cache Playwright Browsers for Playwright's Version
|
| 51 |
+
id: cache-playwright-browsers
|
| 52 |
+
uses: actions/cache@v4
|
| 53 |
+
with:
|
| 54 |
+
path: ~/.cache/ms-playwright
|
| 55 |
+
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
|
| 56 |
+
|
| 57 |
+
- name: Setup Playwright Browser
|
| 58 |
+
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
| 59 |
+
run: npx playwright install --with-deps
|
| 60 |
+
|
| 61 |
+
- name: Run Playwright tests - ${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
|
| 62 |
+
if: github.event.pull_request.user.login != 'dependabot[bot]'
|
| 63 |
+
run: npm run functional-tests
|
| 64 |
+
timeout-minutes: 40
|
| 65 |
+
env:
|
| 66 |
+
E2E_TEST_ENV: ${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
|
| 67 |
+
E2E_TEST_SECRET: ${{ secrets.E2E_TEST_SECRET }}
|
| 68 |
+
E2E_TEST_ACCOUNT_BASE_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_BASE_EMAIL }}
|
| 69 |
+
E2E_TEST_ACCOUNT_BASE_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_BASE_PASSWORD }}
|
| 70 |
+
- uses: actions/upload-artifact@v5
|
| 71 |
+
if: always()
|
| 72 |
+
with:
|
| 73 |
+
name: playwright-report-${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
|
| 74 |
+
path: playwright-report/
|
| 75 |
+
retention-days: 30
|
| 76 |
+
- uses: actions/upload-artifact@v5
|
| 77 |
+
if: always()
|
| 78 |
+
with:
|
| 79 |
+
name: test-results-${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}
|
| 80 |
+
path: src/functional-tests/test-results/
|
| 81 |
+
retention-days: 30
|
| 82 |
+
|
| 83 |
+
- name: Send GitHub Action trigger data to Slack workflow
|
| 84 |
+
id: slack
|
| 85 |
+
uses: slackapi/slack-github-action@v2.1.1
|
| 86 |
+
if: failure() && github.ref == 'refs/heads/main'
|
| 87 |
+
with:
|
| 88 |
+
webhook: ${{ secrets.SLACK_GHA_FAILURES_WEBHOOK }}
|
| 89 |
+
webhook-type: webhook-trigger
|
| 90 |
+
# For posting a message using Block Kit
|
| 91 |
+
payload: |
|
| 92 |
+
{
|
| 93 |
+
"blocks": [
|
| 94 |
+
{
|
| 95 |
+
"type": "section",
|
| 96 |
+
"text": {
|
| 97 |
+
"type": "mrkdwn",
|
| 98 |
+
"text": "*Link to job:* *<https://github.com/mozilla/blurts-server/actions/runs/${{ github.run_id }}|Functional tests>*"
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"type": "divider"
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"type": "section",
|
| 106 |
+
"fields": [
|
| 107 |
+
{
|
| 108 |
+
"type": "mrkdwn",
|
| 109 |
+
"text": "*Workflow:* \n ${{ github.workflow }}"
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"type": "mrkdwn",
|
| 113 |
+
"text": "*Environment:* \n ${{ github.event_name == 'workflow_dispatch' && inputs.test_env || matrix.test_env }}"
|
| 114 |
+
},
|
| 115 |
+
{
|
| 116 |
+
"type": "mrkdwn",
|
| 117 |
+
"text": "*Status:* \n ${{ job.status }}"
|
| 118 |
+
}
|
| 119 |
+
]
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"type": "section",
|
| 123 |
+
"fields": [
|
| 124 |
+
{
|
| 125 |
+
"type": "mrkdwn",
|
| 126 |
+
"text": "*Ref:*\n ${{ github.ref }} "
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"type": "mrkdwn",
|
| 130 |
+
"text": "*Triggered by:*\n ${{ github.triggering_actor }}"
|
| 131 |
+
}
|
| 132 |
+
]
|
| 133 |
+
}
|
| 134 |
+
]
|
| 135 |
+
}
|
.github/workflows/functional_tests_pr.yml
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Functional Test Suite (PR)
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
push:
|
| 7 |
+
branches: [ main ]
|
| 8 |
+
pull_request:
|
| 9 |
+
branches: [ main ]
|
| 10 |
+
jobs:
|
| 11 |
+
functional-tests:
|
| 12 |
+
timeout-minutes: 60
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
# Service containers to run with `container-job`
|
| 15 |
+
services:
|
| 16 |
+
# Label used to access the service container
|
| 17 |
+
postgres:
|
| 18 |
+
# Docker Hub image
|
| 19 |
+
image: postgres
|
| 20 |
+
# Provide the password for postgres
|
| 21 |
+
env:
|
| 22 |
+
POSTGRES_USER: postgres
|
| 23 |
+
POSTGRES_PASSWORD: postgres
|
| 24 |
+
POSTGRES_DB: blurts
|
| 25 |
+
# Set health checks to wait until postgres has started
|
| 26 |
+
options: >-
|
| 27 |
+
--health-cmd pg_isready
|
| 28 |
+
--health-interval 10s
|
| 29 |
+
--health-timeout 5s
|
| 30 |
+
--health-retries 5
|
| 31 |
+
ports:
|
| 32 |
+
- 5432:5432
|
| 33 |
+
|
| 34 |
+
steps:
|
| 35 |
+
- uses: actions/checkout@v6
|
| 36 |
+
with:
|
| 37 |
+
persist-credentials: false
|
| 38 |
+
- uses: actions/setup-node@v6
|
| 39 |
+
with:
|
| 40 |
+
node-version: 20.19.x
|
| 41 |
+
|
| 42 |
+
- name: Install dependencies
|
| 43 |
+
run: npm ci
|
| 44 |
+
- name: Setting up postgres
|
| 45 |
+
run: npm run db:migrate
|
| 46 |
+
env:
|
| 47 |
+
DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts
|
| 48 |
+
- name: Store Playwright’s Version
|
| 49 |
+
run: |
|
| 50 |
+
# Get the current Playwright version listed in package.json
|
| 51 |
+
PLAYWRIGHT_VERSION=$(npx playwright --version | sed 's/Version //')
|
| 52 |
+
echo "Playwright Version: $PLAYWRIGHT_VERSION"
|
| 53 |
+
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
|
| 54 |
+
- name: Cache Playwright Browsers for Playwright's Version
|
| 55 |
+
id: cache-playwright-browsers
|
| 56 |
+
uses: actions/cache@v4
|
| 57 |
+
with:
|
| 58 |
+
path: ~/.cache/ms-playwright
|
| 59 |
+
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
|
| 60 |
+
|
| 61 |
+
- name: Setup Playwright Browser
|
| 62 |
+
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
| 63 |
+
run: npx playwright install --with-deps
|
| 64 |
+
|
| 65 |
+
- name: Run Playwright tests
|
| 66 |
+
if: github.event.pull_request.user.login != 'dependabot[bot]'
|
| 67 |
+
run: npm run functional-tests
|
| 68 |
+
timeout-minutes: 20
|
| 69 |
+
env:
|
| 70 |
+
ADMINS: ${{ secrets.ADMINS }}
|
| 71 |
+
DATABASE_URL: postgres://postgres:postgres@localhost:5432/blurts
|
| 72 |
+
E2E_TEST_SECRET: ${{ secrets.E2E_TEST_SECRET }}
|
| 73 |
+
E2E_TEST_ACCOUNT_BASE_EMAIL: ${{ secrets.E2E_TEST_ACCOUNT_BASE_EMAIL }}
|
| 74 |
+
E2E_TEST_ACCOUNT_BASE_PASSWORD: ${{ secrets.E2E_TEST_ACCOUNT_BASE_PASSWORD }}
|
| 75 |
+
E2E_TEST_ENV: local
|
| 76 |
+
HIBP_API_TOKEN: ${{ secrets.HIBP_API_TOKEN }}
|
| 77 |
+
HIBP_KANON_API_ROOT: "http://localhost:6060/api/mock/hibp"
|
| 78 |
+
HIBP_KANON_API_TOKEN: ${{ secrets.HIBP_KANON_API_TOKEN }}
|
| 79 |
+
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
| 80 |
+
NEXTAUTH_URL: ${{ secrets.NEXTAUTH_URL }}
|
| 81 |
+
OAUTH_ACCOUNT_URI: ${{ secrets.OAUTH_ACCOUNT_URI }}
|
| 82 |
+
OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET_LOCAL }}
|
| 83 |
+
ONEREP_API_BASE: "http://localhost:6060/api/mock/onerep/"
|
| 84 |
+
ONEREP_API_KEY: ${{ secrets.ONEREP_API_KEY }}
|
| 85 |
+
PREMIUM_PLAN_ID_MONTHLY_US: ${{ secrets.STAGE_PREMIUM_PLAN_ID_MONTHLY_US }}
|
| 86 |
+
PREMIUM_PLAN_ID_YEARLY_US: ${{ secrets.STAGE_PREMIUM_PLAN_ID_YEARLY_US }}
|
| 87 |
+
PREMIUM_PRODUCT_ID: ${{ secrets.STAGE_PREMIUM_PRODUCT_ID }}
|
| 88 |
+
REDIS_URL: "redis://redis.mock"
|
| 89 |
+
- uses: actions/upload-artifact@v5
|
| 90 |
+
if: always()
|
| 91 |
+
with:
|
| 92 |
+
name: playwright-report
|
| 93 |
+
path: playwright-report/
|
| 94 |
+
retention-days: 30
|
| 95 |
+
- uses: actions/upload-artifact@v5
|
| 96 |
+
if: always()
|
| 97 |
+
with:
|
| 98 |
+
name: test-results
|
| 99 |
+
path: src/functional-tests/test-results/
|
| 100 |
+
retention-days: 30
|
.github/workflows/glean-probe-scraper.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Glean probe-scraper
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
push:
|
| 7 |
+
paths:
|
| 8 |
+
- src/telemetry/metrics.yaml
|
| 9 |
+
- src/telemetry/backend-metrics.yaml
|
| 10 |
+
pull_request:
|
| 11 |
+
paths:
|
| 12 |
+
- src/telemetry/metrics.yaml
|
| 13 |
+
- src/telemetry/backend-metrics.yaml
|
| 14 |
+
jobs:
|
| 15 |
+
glean-probe-scraper:
|
| 16 |
+
uses: mozilla/probe-scraper/.github/workflows/glean.yaml@main
|
.github/workflows/lighthouse_cron.yml
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Lighthouse Report Cron
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
schedule:
|
| 7 |
+
- cron: '0 6 * * *'
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
inputs:
|
| 10 |
+
environment:
|
| 11 |
+
description: 'Environment to run LHCI against'
|
| 12 |
+
required: false
|
| 13 |
+
default: 'stage'
|
| 14 |
+
type: choice
|
| 15 |
+
options:
|
| 16 |
+
- stage
|
| 17 |
+
- prod
|
| 18 |
+
jobs:
|
| 19 |
+
lhci:
|
| 20 |
+
name: Lighthouse Report - ${{ inputs.environment != null && inputs.environment || 'stage' }}
|
| 21 |
+
runs-on: ubuntu-latest
|
| 22 |
+
environment: ${{ inputs.environment != null && inputs.environment || 'stage' }}
|
| 23 |
+
permissions:
|
| 24 |
+
contents: read
|
| 25 |
+
id-token: write
|
| 26 |
+
steps:
|
| 27 |
+
- uses: actions/checkout@v6
|
| 28 |
+
with:
|
| 29 |
+
persist-credentials: false
|
| 30 |
+
- name: Use Node.js 20.19.x
|
| 31 |
+
uses: actions/setup-node@v6
|
| 32 |
+
with:
|
| 33 |
+
node-version: 20.19.x
|
| 34 |
+
- name: Run Lighthouse CI
|
| 35 |
+
run: |
|
| 36 |
+
npm install -g @lhci/cli@0.14.x
|
| 37 |
+
npm run lighthouse
|
| 38 |
+
env:
|
| 39 |
+
LIGHTHOUSE_COLLECT_URL: ${{ secrets.LIGHTHOUSE_COLLECT_URL }}
|
| 40 |
+
- name: Build cronjobs
|
| 41 |
+
run: |
|
| 42 |
+
npm ci
|
| 43 |
+
npm run build-cronjobs
|
| 44 |
+
- name: Authenticate to Google Cloud
|
| 45 |
+
uses: google-github-actions/auth@v3
|
| 46 |
+
with:
|
| 47 |
+
workload_identity_provider: ${{ secrets.GC_LIGHTHOUSE_WORKLOAD_IDENTITY_PROVIDER }}
|
| 48 |
+
service_account: ${{ secrets.GC_LIGHTHOUSE_SERVICE_ACCOUNT }}
|
| 49 |
+
- name: Report results
|
| 50 |
+
run: npm run cron:report-lighthouse-results
|
| 51 |
+
env:
|
| 52 |
+
BQ_LIGHTHOUSE_PROJECT: ${{ secrets.BQ_LIGHTHOUSE_PROJECT }}
|
| 53 |
+
BQ_LIGHTHOUSE_DATASET: ${{ secrets.BQ_LIGHTHOUSE_DATASET }}
|
| 54 |
+
BQ_LIGHTHOUSE_TABLE: ${{ secrets.BQ_LIGHTHOUSE_TABLE }}
|
.github/workflows/lint.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Lint
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
pull_request:
|
| 7 |
+
push:
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
npm-lint:
|
| 11 |
+
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
|
| 14 |
+
steps:
|
| 15 |
+
- uses: actions/checkout@v6
|
| 16 |
+
with:
|
| 17 |
+
persist-credentials: false
|
| 18 |
+
- name: Use Node.js
|
| 19 |
+
uses: actions/setup-node@v6
|
| 20 |
+
with:
|
| 21 |
+
node-version: '20.19.x'
|
| 22 |
+
- run: npm ci
|
| 23 |
+
- run: npm run build-glean
|
| 24 |
+
- run: npm run build-nimbus
|
| 25 |
+
# Mirror old linter from CircleCI, verifies that linter succeeds
|
| 26 |
+
- run: npm run lint
|
| 27 |
+
- run: node src/scripts/build/checkNodeVersionAlignment.js
|
| 28 |
+
- run: node src/scripts/build/checkGithubActionsBestPractices.js
|
.github/workflows/production_deploy.yml
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Monitor 1-click Deployment
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
workflow_dispatch:
|
| 7 |
+
inputs:
|
| 8 |
+
environment:
|
| 9 |
+
description: 'Environment to deploy to'
|
| 10 |
+
required: true
|
| 11 |
+
default: 'prod'
|
| 12 |
+
type: choice
|
| 13 |
+
options:
|
| 14 |
+
- stage
|
| 15 |
+
- prod
|
| 16 |
+
- dev
|
| 17 |
+
originalImageTag:
|
| 18 |
+
description: 'The original image tag that has been deployed'
|
| 19 |
+
required: true
|
| 20 |
+
type: string
|
| 21 |
+
pattern: '^[a-f0-9]{7,12}$'
|
| 22 |
+
jobs:
|
| 23 |
+
pull_retag_push:
|
| 24 |
+
name: Pull, Retag, and Push Images
|
| 25 |
+
runs-on: ubuntu-latest
|
| 26 |
+
environment: build
|
| 27 |
+
permissions:
|
| 28 |
+
contents: "read" # Needed for checkout
|
| 29 |
+
id-token: "write" # Needed for GCP auth
|
| 30 |
+
packages: "none" # Explicitly disable package permissions
|
| 31 |
+
env:
|
| 32 |
+
GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }}
|
| 33 |
+
GAR_REGISTRY: us-docker.pkg.dev # Define GAR registry hostname
|
| 34 |
+
DOCKERHUB_IMAGE: mozilla/blurts-server # Define Docker Hub image name
|
| 35 |
+
SAFE_IMAGE_TAG: ${{ inputs.originalImageTag }}
|
| 36 |
+
SAFE_ENVIRONMENT: ${{ inputs.environment }}
|
| 37 |
+
steps:
|
| 38 |
+
- name: Checkout Repository
|
| 39 |
+
uses: actions/checkout@v6
|
| 40 |
+
with:
|
| 41 |
+
persist-credentials: false
|
| 42 |
+
|
| 43 |
+
- name: Log in to Docker Hub
|
| 44 |
+
uses: docker/login-action@v3
|
| 45 |
+
with:
|
| 46 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
| 47 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
| 48 |
+
|
| 49 |
+
- name: Authenticate to Google Cloud
|
| 50 |
+
id: gcp-auth
|
| 51 |
+
uses: google-github-actions/auth@v3
|
| 52 |
+
with:
|
| 53 |
+
token_format: access_token
|
| 54 |
+
workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
|
| 55 |
+
service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
|
| 56 |
+
|
| 57 |
+
- name: Login to Artifact Registry
|
| 58 |
+
id: gar-login
|
| 59 |
+
uses: docker/login-action@v3
|
| 60 |
+
with:
|
| 61 |
+
registry: ${{ env.GAR_REGISTRY }}
|
| 62 |
+
username: oauth2accesstoken
|
| 63 |
+
password: ${{ steps.gcp-auth.outputs.access_token }}
|
| 64 |
+
|
| 65 |
+
- name: Pull Docker Hub image
|
| 66 |
+
run: docker pull "$DOCKERHUB_IMAGE:$SAFE_IMAGE_TAG"
|
| 67 |
+
|
| 68 |
+
- name: Retag Docker Hub image
|
| 69 |
+
run: docker tag "$DOCKERHUB_IMAGE:$SAFE_IMAGE_TAG" "$DOCKERHUB_IMAGE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
|
| 70 |
+
|
| 71 |
+
- name: Push Docker Hub image
|
| 72 |
+
run: docker push "$DOCKERHUB_IMAGE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
|
| 73 |
+
|
| 74 |
+
- name: Pull GAR image
|
| 75 |
+
run: docker pull "$GAR_IMAGE_BASE:$SAFE_IMAGE_TAG"
|
| 76 |
+
|
| 77 |
+
- name: Retag GAR image
|
| 78 |
+
run: docker tag "$GAR_IMAGE_BASE:$SAFE_IMAGE_TAG" "$GAR_IMAGE_BASE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
|
| 79 |
+
|
| 80 |
+
- name: Push GAR image
|
| 81 |
+
run: docker push "$GAR_IMAGE_BASE:$SAFE_ENVIRONMENT-$SAFE_IMAGE_TAG"
|
.github/workflows/reference_linter.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Lint Reference Files
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
push:
|
| 7 |
+
pull_request:
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
jobs:
|
| 10 |
+
l10n-lint:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- name: Clone repository
|
| 14 |
+
uses: actions/checkout@v6
|
| 15 |
+
with:
|
| 16 |
+
persist-credentials: false
|
| 17 |
+
- name: Set up Python 3
|
| 18 |
+
uses: actions/setup-python@v6
|
| 19 |
+
with:
|
| 20 |
+
python-version: '3.10'
|
| 21 |
+
cache: 'pip'
|
| 22 |
+
- name: Install Python dependencies
|
| 23 |
+
run: |
|
| 24 |
+
pip install -r .github/requirements.txt
|
| 25 |
+
- name: Lint reference strings
|
| 26 |
+
run: |
|
| 27 |
+
moz-fluent-lint ./locales/en/ --config .github/linter_config.yml
|
| 28 |
+
- name: Lint pending strings
|
| 29 |
+
if: always() # This step should run even if the previous one fails
|
| 30 |
+
run: |
|
| 31 |
+
moz-fluent-lint ./locales-pending/ --config .github/linter_config.yml
|
.github/workflows/release_cron_daily.yml
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Daily Pre-release
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
schedule:
|
| 7 |
+
- cron: '0 0 * * 1-5' # Runs Mon-Fri at midnight UTC
|
| 8 |
+
workflow_dispatch: # Allows manual trigger if needed
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
create-pre-release:
|
| 12 |
+
permissions:
|
| 13 |
+
contents: write
|
| 14 |
+
id-token: write
|
| 15 |
+
runs-on: ubuntu-latest
|
| 16 |
+
environment: build
|
| 17 |
+
env:
|
| 18 |
+
GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }}
|
| 19 |
+
GAR_REGISTRY: us-docker.pkg.dev
|
| 20 |
+
DOCKERHUB_IMAGE: mozilla/blurts-server # Define Docker Hub image name
|
| 21 |
+
|
| 22 |
+
steps:
|
| 23 |
+
- name: Checkout main branch
|
| 24 |
+
uses: actions/checkout@v6
|
| 25 |
+
with:
|
| 26 |
+
ref: main
|
| 27 |
+
persist-credentials: false
|
| 28 |
+
|
| 29 |
+
- name: Get current date
|
| 30 |
+
run: |
|
| 31 |
+
echo "CURRENT_DATE=$(date +%Y.%m.%d)" >> $GITHUB_ENV
|
| 32 |
+
echo "tag_name: $(date +%Y.%m.%d)"
|
| 33 |
+
|
| 34 |
+
- name: Create Pre-release
|
| 35 |
+
env:
|
| 36 |
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 37 |
+
run: |
|
| 38 |
+
curl -X POST \
|
| 39 |
+
-H "Authorization: token $GITHUB_TOKEN" \
|
| 40 |
+
-H "Accept: application/vnd.github+json" \
|
| 41 |
+
-H "X-GitHub-Api-Version: 2022-11-28" \
|
| 42 |
+
https://api.github.com/repos/${{ github.repository }}/releases \
|
| 43 |
+
-d '{
|
| 44 |
+
"tag_name": "${{ env.CURRENT_DATE }}",
|
| 45 |
+
"target_commitish": "main",
|
| 46 |
+
"name": "${{ env.CURRENT_DATE }}",
|
| 47 |
+
"body": "Daily pre-release for ${{ env.CURRENT_DATE }}.",
|
| 48 |
+
"prerelease": true,
|
| 49 |
+
"draft": false,
|
| 50 |
+
"generate_release_notes": true
|
| 51 |
+
}'
|
| 52 |
+
|
| 53 |
+
# We cannot rely on the release_retag.yaml workflow because of the
|
| 54 |
+
# auth scope of the default github token. It's a good security practice
|
| 55 |
+
# to prevent a github action being triggered by another.
|
| 56 |
+
# So we will deliberately push to dockerhub below
|
| 57 |
+
- name: Log in to Docker Hub
|
| 58 |
+
uses: docker/login-action@v3
|
| 59 |
+
with:
|
| 60 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
| 61 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
| 62 |
+
|
| 63 |
+
- name: Authenticate to Google Cloud
|
| 64 |
+
id: gcp-auth
|
| 65 |
+
uses: google-github-actions/auth@v3
|
| 66 |
+
with:
|
| 67 |
+
token_format: access_token
|
| 68 |
+
workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
|
| 69 |
+
service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
|
| 70 |
+
|
| 71 |
+
- name: Login to Artifact Registry
|
| 72 |
+
id: gar-login
|
| 73 |
+
uses: docker/login-action@v3
|
| 74 |
+
with:
|
| 75 |
+
registry: ${{ env.GAR_REGISTRY }}
|
| 76 |
+
username: oauth2accesstoken
|
| 77 |
+
password: ${{ steps.gcp-auth.outputs.access_token }}
|
| 78 |
+
|
| 79 |
+
- name: Extract metadata (tags, labels) for Docker
|
| 80 |
+
id: meta
|
| 81 |
+
uses: docker/metadata-action@v5
|
| 82 |
+
with:
|
| 83 |
+
images: ${{ env.DOCKERHUB_IMAGE }}
|
| 84 |
+
tags: type=sha,format=short,prefix=
|
| 85 |
+
|
| 86 |
+
- name: Pull Docker image from GAR with commit tag
|
| 87 |
+
env:
|
| 88 |
+
VERSION: ${{ steps.meta.outputs.version }}
|
| 89 |
+
run: docker pull ${{ env.GAR_IMAGE_BASE }}:$VERSION
|
| 90 |
+
|
| 91 |
+
- name: Tag Docker image for Docker Hub with release tag
|
| 92 |
+
env:
|
| 93 |
+
VERSION: ${{ steps.meta.outputs.version }}
|
| 94 |
+
run: docker tag ${{ env.GAR_IMAGE_BASE }}:$VERSION ${{ env.DOCKERHUB_IMAGE }}:${{ env.CURRENT_DATE }}
|
| 95 |
+
|
| 96 |
+
- name: Push Docker image to Docker Hub with release tag
|
| 97 |
+
run: docker push ${{ env.DOCKERHUB_IMAGE }}:${{ env.CURRENT_DATE }}
|
| 98 |
+
|
| 99 |
+
- name: Tag Docker image for GAR with release tag
|
| 100 |
+
env:
|
| 101 |
+
VERSION: ${{ steps.meta.outputs.version }}
|
| 102 |
+
run: docker tag ${{ env.GAR_IMAGE_BASE }}:$VERSION ${{ env.GAR_IMAGE_BASE }}:${{ env.CURRENT_DATE }}
|
| 103 |
+
|
| 104 |
+
- name: Push Docker image to GAR with release tag
|
| 105 |
+
run: docker push ${{ env.GAR_IMAGE_BASE }}:${{ env.CURRENT_DATE }}
|
.github/workflows/release_retag.yaml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Retag and Push Docker Image on Release
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
# GH release should always create a tag automatically
|
| 6 |
+
on:
|
| 7 |
+
push:
|
| 8 |
+
tags:
|
| 9 |
+
- '*'
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
retag-and-push:
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
|
| 15 |
+
steps:
|
| 16 |
+
- name: Check out the repo
|
| 17 |
+
uses: actions/checkout@v6
|
| 18 |
+
with:
|
| 19 |
+
persist-credentials: false
|
| 20 |
+
|
| 21 |
+
- name: Log in to Docker Hub
|
| 22 |
+
uses: docker/login-action@v3
|
| 23 |
+
with:
|
| 24 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
| 25 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
| 26 |
+
|
| 27 |
+
- name: Extract metadata (tags, labels) for Docker
|
| 28 |
+
id: meta
|
| 29 |
+
uses: docker/metadata-action@v5
|
| 30 |
+
with:
|
| 31 |
+
images: mozilla/blurts-server
|
| 32 |
+
tags: type=sha,format=short,prefix=
|
| 33 |
+
|
| 34 |
+
- name: Pull Docker image with commit tag
|
| 35 |
+
env:
|
| 36 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 37 |
+
run: docker pull $TAGS
|
| 38 |
+
|
| 39 |
+
- name: Tag Docker image with release tag
|
| 40 |
+
env:
|
| 41 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 42 |
+
run: docker tag $TAGS mozilla/blurts-server:${{ github.ref_name }}
|
| 43 |
+
|
| 44 |
+
- name: Push Docker image with release tag
|
| 45 |
+
run: docker push mozilla/blurts-server:${{ github.ref_name }}
|
.github/workflows/release_retag_v2.yaml
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Retag and Push GAR Image on Release v2
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
push:
|
| 7 |
+
tags:
|
| 8 |
+
- '*'
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
retag-and-push-gar:
|
| 12 |
+
permissions:
|
| 13 |
+
contents: "read" # Needed for checkout
|
| 14 |
+
id-token: "write" # Needed for GCP auth
|
| 15 |
+
packages: "none" # Explicitly disable package permissions
|
| 16 |
+
name: Retag and Push GAR image
|
| 17 |
+
runs-on: ubuntu-latest
|
| 18 |
+
environment: build
|
| 19 |
+
env:
|
| 20 |
+
GAR_IMAGE_BASE: ${{ vars.GAR_REPO }}/${{ github.event.repository.name }}
|
| 21 |
+
steps:
|
| 22 |
+
- name: Check out the repo
|
| 23 |
+
uses: actions/checkout@v6
|
| 24 |
+
with:
|
| 25 |
+
persist-credentials: false # Not strictly needed for retagging, but good practice
|
| 26 |
+
|
| 27 |
+
- name: Authenticate to Google Cloud
|
| 28 |
+
id: gcp-auth
|
| 29 |
+
uses: google-github-actions/auth@v3
|
| 30 |
+
with:
|
| 31 |
+
token_format: access_token
|
| 32 |
+
workload_identity_provider: ${{ vars.GCPV2_GITHUB_WORKLOAD_IDENTITY_PROVIDER }}
|
| 33 |
+
service_account: ${{ vars.GCP_GAR_SERVICE_ACCOUNT }}
|
| 34 |
+
|
| 35 |
+
- name: Login to Artifact Registry
|
| 36 |
+
id: gar-login
|
| 37 |
+
uses: docker/login-action@v3
|
| 38 |
+
with:
|
| 39 |
+
registry: us-docker.pkg.dev
|
| 40 |
+
username: oauth2accesstoken
|
| 41 |
+
password: ${{ steps.gcp-auth.outputs.access_token }}
|
| 42 |
+
|
| 43 |
+
- name: Extract metadata (tags, labels) for Docker
|
| 44 |
+
id: meta
|
| 45 |
+
uses: docker/metadata-action@v5
|
| 46 |
+
with:
|
| 47 |
+
# Use the GAR image base
|
| 48 |
+
images: ${{ env.GAR_IMAGE_BASE }}
|
| 49 |
+
tags: |
|
| 50 |
+
# Only generate the tag based on short commit SHA
|
| 51 |
+
type=sha,format=short,prefix=
|
| 52 |
+
|
| 53 |
+
- name: Determine Release-tagged image name
|
| 54 |
+
id: release_tag_info
|
| 55 |
+
run: echo "name=${{ env.GAR_IMAGE_BASE }}:${{ github.ref_name }}" >> $GITHUB_OUTPUT
|
| 56 |
+
|
| 57 |
+
- name: Pull Docker image with commit tag from GAR
|
| 58 |
+
env:
|
| 59 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 60 |
+
run: |
|
| 61 |
+
echo "Pulling $TAGS"
|
| 62 |
+
docker pull $TAGS
|
| 63 |
+
|
| 64 |
+
- name: Tag Docker image with release tag
|
| 65 |
+
env:
|
| 66 |
+
TAGS: ${{ steps.meta.outputs.tags }}
|
| 67 |
+
NAME: ${{ steps.release_tag_info.outputs.name }}
|
| 68 |
+
run: |
|
| 69 |
+
echo "Tagging $TAGS as $NAME"
|
| 70 |
+
docker tag $TAGS $NAME
|
| 71 |
+
|
| 72 |
+
- name: Push Docker image with release tag to GAR
|
| 73 |
+
env:
|
| 74 |
+
NAME: ${{ steps.release_tag_info.outputs.name }}
|
| 75 |
+
run: |
|
| 76 |
+
echo "Pushing $NAME"
|
| 77 |
+
docker push $NAME
|
| 78 |
+
|
| 79 |
+
- name: Print Image URI
|
| 80 |
+
env:
|
| 81 |
+
NAME: ${{ steps.release_tag_info.outputs.name }}
|
| 82 |
+
run: |
|
| 83 |
+
echo "Retagged and pushed GAR image: $NAME"
|
.github/workflows/test_integrations.yml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: "Test Integrations"
|
| 2 |
+
permissions: {}
|
| 3 |
+
|
| 4 |
+
on:
|
| 5 |
+
push:
|
| 6 |
+
branches: [ main ]
|
| 7 |
+
pull_request:
|
| 8 |
+
branches: [ main ]
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
test-integrations:
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@v6
|
| 15 |
+
with:
|
| 16 |
+
persist-credentials: false
|
| 17 |
+
|
| 18 |
+
# Log into Docker Hub to avoid getting rate-limited
|
| 19 |
+
- name: Login to Docker Hub
|
| 20 |
+
# Not giving dependabot secrets access, so continue if login fails
|
| 21 |
+
# and accept rate limit possibility
|
| 22 |
+
continue-on-error: true
|
| 23 |
+
uses: docker/login-action@v3
|
| 24 |
+
with:
|
| 25 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
| 26 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
| 27 |
+
|
| 28 |
+
# Kick off starting docker compose first, since it can take a bit and can run in the background
|
| 29 |
+
- name: Start Docker Compose services
|
| 30 |
+
run: docker compose --env-file=./.env.ci up -d
|
| 31 |
+
|
| 32 |
+
# While we wait for docker compose to be healthy we install node and needed packages for this service
|
| 33 |
+
- name: Set up node
|
| 34 |
+
uses: actions/setup-node@v6
|
| 35 |
+
with:
|
| 36 |
+
node-version: 20.19.x
|
| 37 |
+
|
| 38 |
+
- name: Install dependencies
|
| 39 |
+
run: npm ci
|
| 40 |
+
|
| 41 |
+
# Wait for the docker services we started earlier to all be healthy
|
| 42 |
+
- name: Wait for services to be healthy
|
| 43 |
+
run: docker compose --env-file=./.env.ci up --wait
|
| 44 |
+
|
| 45 |
+
- name: Set up postgres
|
| 46 |
+
run: npm run db:migrate
|
| 47 |
+
env:
|
| 48 |
+
DATABASE_URL: postgres://blurts:blurts@localhost:5432/test-blurts
|
| 49 |
+
|
| 50 |
+
# Let's run those integration tests!
|
| 51 |
+
- name: Run service integration tests
|
| 52 |
+
run: npm run test-integrations
|
.github/workflows/unittests.yaml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Unit Tests
|
| 2 |
+
|
| 3 |
+
permissions: {}
|
| 4 |
+
|
| 5 |
+
on:
|
| 6 |
+
pull_request:
|
| 7 |
+
push:
|
| 8 |
+
schedule:
|
| 9 |
+
- cron: '0 8 * * *'
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
unit-tests:
|
| 13 |
+
|
| 14 |
+
runs-on: ubuntu-latest
|
| 15 |
+
|
| 16 |
+
steps:
|
| 17 |
+
- uses: actions/checkout@v6
|
| 18 |
+
with:
|
| 19 |
+
persist-credentials: false
|
| 20 |
+
- name: Use Node.js
|
| 21 |
+
uses: actions/setup-node@v6
|
| 22 |
+
with:
|
| 23 |
+
node-version: '20.19.x'
|
| 24 |
+
- run: npm ci
|
| 25 |
+
- run: npm run build-glean
|
| 26 |
+
# Run the regular tests on push
|
| 27 |
+
- run: npm test
|
| 28 |
+
if: github.event_name != 'schedule'
|
| 29 |
+
# But measure coverage without ignore markers in the nighly job
|
| 30 |
+
- name: Remove `c8 ignore` markers to output full unit test coverage if on the `report-coverage` branch
|
| 31 |
+
run: find src/ -type f -name "*.js" -or -name "*.ts" -or -name "*.jsx" -or -name "*.tsx" -exec sed --in-place --expression='s/c8 ignore/c8 TEMPORARILY DO NOT ignore/g' {} \;
|
| 32 |
+
if: github.event_name == 'schedule'
|
| 33 |
+
# (and set an arbitrary coverage threshold for that "true" coverage):
|
| 34 |
+
- run: npm test -- --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
|
| 35 |
+
if: github.event_name == 'schedule'
|
| 36 |
+
- uses: actions/upload-artifact@v5
|
| 37 |
+
if: always()
|
| 38 |
+
with:
|
| 39 |
+
name: coverage-report
|
| 40 |
+
path: coverage/
|
| 41 |
+
retention-days: 30
|
.gitignore
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
| 2 |
+
.node-version
|
| 3 |
+
coverage
|
| 4 |
+
.coveralls.yml
|
| 5 |
+
.DS_Store
|
| 6 |
+
breach_hashsets
|
| 7 |
+
version.json
|
| 8 |
+
loadtests/__pycache__
|
| 9 |
+
loadtests/venv
|
| 10 |
+
.tmp
|
| 11 |
+
public/dist
|
| 12 |
+
dist
|
| 13 |
+
public/logo_cache/
|
| 14 |
+
src/client/images/logo_cache
|
| 15 |
+
|
| 16 |
+
# Playwright
|
| 17 |
+
**/test-results/
|
| 18 |
+
**/playwright-report/
|
| 19 |
+
**/playwright/.cache/
|
| 20 |
+
**/blob-report/
|
| 21 |
+
**/functional-test-cache/
|
| 22 |
+
**/traces/
|
| 23 |
+
**/*-snapshots/
|
| 24 |
+
|
| 25 |
+
# Storybook
|
| 26 |
+
*/storybook.log
|
| 27 |
+
storybook-static/
|
| 28 |
+
|
| 29 |
+
# The rest of this file was generated by create-next-app:
|
| 30 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 31 |
+
|
| 32 |
+
# dependencies
|
| 33 |
+
/node_modules
|
| 34 |
+
/.pnp
|
| 35 |
+
.pnp.js
|
| 36 |
+
|
| 37 |
+
# testing
|
| 38 |
+
/coverage
|
| 39 |
+
|
| 40 |
+
# load testing
|
| 41 |
+
/src/scripts/loadtest/reports/
|
| 42 |
+
|
| 43 |
+
# next.js
|
| 44 |
+
/.next/
|
| 45 |
+
/out/
|
| 46 |
+
|
| 47 |
+
# production
|
| 48 |
+
/build
|
| 49 |
+
|
| 50 |
+
# misc
|
| 51 |
+
.DS_Store
|
| 52 |
+
*.pem
|
| 53 |
+
|
| 54 |
+
# debug
|
| 55 |
+
npm-debug.log*
|
| 56 |
+
yarn-debug.log*
|
| 57 |
+
yarn-error.log*
|
| 58 |
+
|
| 59 |
+
# local env files
|
| 60 |
+
.env*.local
|
| 61 |
+
|
| 62 |
+
# vercel
|
| 63 |
+
.vercel
|
| 64 |
+
|
| 65 |
+
# typescript
|
| 66 |
+
*.tsbuildinfo
|
| 67 |
+
next-env.d.ts
|
| 68 |
+
|
| 69 |
+
# Sentry Auth Token
|
| 70 |
+
.sentryclirc
|
| 71 |
+
|
| 72 |
+
# Glean
|
| 73 |
+
.venv
|
| 74 |
+
/src/telemetry/generated/
|
| 75 |
+
|
| 76 |
+
# Lighthouse CI
|
| 77 |
+
.lighthouseci
|
| 78 |
+
|
| 79 |
+
# vscode debugger
|
| 80 |
+
.vscode/launch.json
|
.husky/pre-commit
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
npx lint-staged
|
.storybook/NextImage.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
| 2 |
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 3 |
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
| 4 |
+
|
| 5 |
+
import { StaticImageData } from "next/image";
|
| 6 |
+
|
| 7 |
+
const NextImage = ({
|
| 8 |
+
src,
|
| 9 |
+
alt,
|
| 10 |
+
...props
|
| 11 |
+
}: {
|
| 12 |
+
src: StaticImageData;
|
| 13 |
+
alt: string;
|
| 14 |
+
// eslint-disable-next-line @next/next/no-img-element
|
| 15 |
+
}) => <img src={`/${src.src}`} alt={alt} {...props} />;
|
| 16 |
+
|
| 17 |
+
export default NextImage;
|
.storybook/main.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
| 2 |
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 3 |
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
| 4 |
+
|
| 5 |
+
import type { StorybookConfig } from "@storybook/nextjs";
|
| 6 |
+
const config: StorybookConfig = {
|
| 7 |
+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
| 8 |
+
|
| 9 |
+
addons: [
|
| 10 |
+
"@storybook/addon-a11y",
|
| 11 |
+
"@storybook/addon-links",
|
| 12 |
+
"@storybook/addon-docs",
|
| 13 |
+
],
|
| 14 |
+
|
| 15 |
+
framework: {
|
| 16 |
+
name: "@storybook/nextjs",
|
| 17 |
+
options: {},
|
| 18 |
+
},
|
| 19 |
+
|
| 20 |
+
docs: {},
|
| 21 |
+
|
| 22 |
+
staticDirs: [
|
| 23 |
+
// See https://github.com/storybookjs/storybook/tree/4f0c895bc53116272ef598f19e8d869213be49a9/code/frameworks/nextjs#nextfontlocal
|
| 24 |
+
{
|
| 25 |
+
from: "../src/app/fonts",
|
| 26 |
+
to: "src/app/fonts",
|
| 27 |
+
},
|
| 28 |
+
],
|
| 29 |
+
|
| 30 |
+
async webpackFinal(config) {
|
| 31 |
+
config.module ??= {};
|
| 32 |
+
config.module.rules ??= [];
|
| 33 |
+
config.module.rules.push({
|
| 34 |
+
test: /\.ftl/,
|
| 35 |
+
type: "asset/source",
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
// There is an issue with serving static assests in Storybook 8.4. The fix
|
| 39 |
+
// will be included in version 8.6.0:
|
| 40 |
+
// https://github.com/storybookjs/storybook/issues/29576
|
| 41 |
+
config.resolve = {
|
| 42 |
+
...(config.resolve ?? {}),
|
| 43 |
+
alias: {
|
| 44 |
+
...(config.resolve?.alias ?? {}),
|
| 45 |
+
"next/image": require.resolve("./NextImage.tsx"),
|
| 46 |
+
},
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
return config;
|
| 50 |
+
},
|
| 51 |
+
|
| 52 |
+
typescript: {
|
| 53 |
+
reactDocgen: "react-docgen-typescript",
|
| 54 |
+
},
|
| 55 |
+
};
|
| 56 |
+
export default config;
|
.storybook/preview-head.html
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!--
|
| 2 |
+
This Source Code Form is subject to the terms of the Mozilla Public
|
| 3 |
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 4 |
+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| 5 |
+
-->
|
| 6 |
+
<style>
|
| 7 |
+
body {
|
| 8 |
+
/*
|
| 9 |
+
The <AppDecorator> in preview.tsx should properly set the Inter font as
|
| 10 |
+
the default font using next/font, but some components are rendered outside
|
| 11 |
+
of the decorator by being appended to the <body> using a React Portal.
|
| 12 |
+
Specifically, React Aria does this for modal elements like the <AppPicker>
|
| 13 |
+
popover. Thus, this is a manual workaround to apply the Inter font there
|
| 14 |
+
too. On the actual website, outside of Storybook, this should not be a
|
| 15 |
+
problem, as the font is applied directly to the <body> using next/font in
|
| 16 |
+
the root layout (src/app/layout.tsx).
|
| 17 |
+
*/
|
| 18 |
+
font-family: Inter;
|
| 19 |
+
}
|
| 20 |
+
/*
|
| 21 |
+
Make sure that stories set to render fullscreen (see
|
| 22 |
+
https://storybook.js.org/docs/react/configure/story-layout) also take up the
|
| 23 |
+
full height of the page. This is especially relevant for stories that
|
| 24 |
+
demonstrate full pages.
|
| 25 |
+
*/
|
| 26 |
+
.sb-main-fullscreen #storybook-root {
|
| 27 |
+
height: 100%;
|
| 28 |
+
}
|
| 29 |
+
</style>
|
.storybook/preview.tsx
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
| 2 |
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 3 |
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
| 4 |
+
|
| 5 |
+
import React, { useEffect } from "react";
|
| 6 |
+
import { Inter } from "next/font/google";
|
| 7 |
+
import type { Preview } from "@storybook/nextjs";
|
| 8 |
+
import { action } from "storybook/actions";
|
| 9 |
+
import { linkTo } from "@storybook/addon-links";
|
| 10 |
+
import { sb } from "storybook/test";
|
| 11 |
+
import "../src/app/globals.css";
|
| 12 |
+
import { metropolis } from "../src/app/fonts/Metropolis/metropolis";
|
| 13 |
+
import { TestComponentWrapper } from "../src/TestComponentWrapper";
|
| 14 |
+
|
| 15 |
+
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
| 16 |
+
|
| 17 |
+
const AppDecorator: Preview["decorators"] = (storyFn) => {
|
| 18 |
+
useEffect(() => {
|
| 19 |
+
// We have to add these classes to the body, rather than simply wrapping the
|
| 20 |
+
// storyFn in a container, because some components (most notably, the ones
|
| 21 |
+
// that use useModalOverlay()) append elements to the end of the body using
|
| 22 |
+
// a React Portal, thus breaking out of a container element.
|
| 23 |
+
document.body.classList.add(inter.className);
|
| 24 |
+
document.body.classList.add(inter.variable);
|
| 25 |
+
document.body.classList.add(metropolis.variable);
|
| 26 |
+
}, []);
|
| 27 |
+
|
| 28 |
+
return <TestComponentWrapper>{storyFn()}</TestComponentWrapper>;
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
sb.mock("../src/app/hooks/locationSuggestions.ts");
|
| 32 |
+
|
| 33 |
+
// Arguments to the `storySort` callback, left as documentation.
|
| 34 |
+
type _SortData = {
|
| 35 |
+
type: "story";
|
| 36 |
+
id: string;
|
| 37 |
+
name: string;
|
| 38 |
+
title: string;
|
| 39 |
+
importPath: string;
|
| 40 |
+
tags: Array<"story" | string>;
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
const preview: Preview = {
|
| 44 |
+
parameters: {
|
| 45 |
+
controls: {
|
| 46 |
+
matchers: {
|
| 47 |
+
color: /(background|color)$/i,
|
| 48 |
+
date: /Date$/,
|
| 49 |
+
},
|
| 50 |
+
},
|
| 51 |
+
// https://storybook.js.org/docs/react/configure/story-layout
|
| 52 |
+
layout: "fullscreen",
|
| 53 |
+
options: {
|
| 54 |
+
// https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#sorting-stories
|
| 55 |
+
// @ts-ignore Storybook appears to not parse this as TypeScript, so we can't
|
| 56 |
+
// add `SortData` type annotations. See
|
| 57 |
+
// https://github.com/storybookjs/storybook/issues/21702#issuecomment-1517154204
|
| 58 |
+
storySort: (a, b) =>
|
| 59 |
+
a.title.localeCompare(b.title, undefined, { numeric: true }),
|
| 60 |
+
},
|
| 61 |
+
nextjs: {
|
| 62 |
+
// See https://storybook.js.org/blog/integrate-nextjs-and-storybook-automatically/#nextnavigation
|
| 63 |
+
appDirectory: true,
|
| 64 |
+
navigation: {
|
| 65 |
+
push(path: string, ...otherArgs: unknown[]) {
|
| 66 |
+
action("nextNavigation.push")(path, ...otherArgs);
|
| 67 |
+
|
| 68 |
+
if (path === "/") {
|
| 69 |
+
linkTo("Pages/Public/Landing page", "US visitors")();
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
if (path === "/breaches") {
|
| 73 |
+
linkTo("Pages/Public/Breach index")();
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
if (path.startsWith("/breach-details/")) {
|
| 77 |
+
linkTo("Pages/Public/Breach listing")();
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
if (path === "/terms/expiration-offer") {
|
| 81 |
+
linkTo("Pages/Public/Terms/Plus expiration offer")();
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
if (path === "/user/dashboard") {
|
| 85 |
+
linkTo(
|
| 86 |
+
"Pages/Logged in/Dashboard",
|
| 87 |
+
"US user, without Premium, with unresolved scan results, with unresolved breaches",
|
| 88 |
+
)();
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if (
|
| 92 |
+
path === "/user/dashboard/fix/data-broker-profiles/start-free-scan"
|
| 93 |
+
) {
|
| 94 |
+
linkTo("Pages/Logged in/Guided resolution/1a. Free scan")();
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
if (
|
| 98 |
+
path ===
|
| 99 |
+
"/user/dashboard/fix/data-broker-profiles/view-data-brokers"
|
| 100 |
+
) {
|
| 101 |
+
linkTo(
|
| 102 |
+
"Pages/Logged in/Guided resolution/1b. Scan results",
|
| 103 |
+
"With a few unresolved scan results (free)",
|
| 104 |
+
)();
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
if (
|
| 108 |
+
path === "/user/dashboard/fix/data-broker-profiles/manual-remove"
|
| 109 |
+
) {
|
| 110 |
+
linkTo(
|
| 111 |
+
"Pages/Logged in/Guided resolution/1c. Manually resolve brokers",
|
| 112 |
+
)();
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
if (
|
| 116 |
+
path ===
|
| 117 |
+
"/user/dashboard/fix/high-risk-data-breaches/social-security-number"
|
| 118 |
+
) {
|
| 119 |
+
linkTo(
|
| 120 |
+
"Pages/Logged in/Guided resolution/2. High-risk data breaches",
|
| 121 |
+
"2a. Social Security Number",
|
| 122 |
+
)();
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
if (
|
| 126 |
+
path === "/user/dashboard/fix/high-risk-data-breaches/credit-card"
|
| 127 |
+
) {
|
| 128 |
+
linkTo(
|
| 129 |
+
"Pages/Logged in/Guided resolution/2. High-risk data breaches",
|
| 130 |
+
"2b. Credit card",
|
| 131 |
+
)();
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
if (
|
| 135 |
+
path === "/user/dashboard/fix/high-risk-data-breaches/bank-account"
|
| 136 |
+
) {
|
| 137 |
+
linkTo(
|
| 138 |
+
"Pages/Logged in/Guided resolution/2. High-risk data breaches",
|
| 139 |
+
"2c. Bank account",
|
| 140 |
+
)();
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if (path === "/user/dashboard/fix/high-risk-data-breaches/pin") {
|
| 144 |
+
linkTo(
|
| 145 |
+
"Pages/Logged in/Guided resolution/2. High-risk data breaches",
|
| 146 |
+
"2d. PIN",
|
| 147 |
+
)();
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
if (path === "/user/dashboard/fix/high-risk-data-breaches/done") {
|
| 151 |
+
linkTo(
|
| 152 |
+
"Pages/Logged in/Guided resolution/2. High-risk data breaches",
|
| 153 |
+
"2e. Done",
|
| 154 |
+
)();
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
if (path === "/user/dashboard/fix/leaked-passwords/passwords") {
|
| 158 |
+
linkTo(
|
| 159 |
+
"Pages/Logged in/Guided resolution/3. Leaked passwords",
|
| 160 |
+
"3a. Passwords",
|
| 161 |
+
)();
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
if (
|
| 165 |
+
path === "/user/dashboard/fix/leaked-passwords/security-questions"
|
| 166 |
+
) {
|
| 167 |
+
linkTo(
|
| 168 |
+
"Pages/Logged in/Guided resolution/3. Leaked passwords",
|
| 169 |
+
"3b. Security questions",
|
| 170 |
+
)();
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
if (
|
| 174 |
+
path === "/user/dashboard/fix/leaked-passwords/passwords-done" ||
|
| 175 |
+
path ===
|
| 176 |
+
"/user/dashboard/fix/leaked-passwords/security-questions-done"
|
| 177 |
+
) {
|
| 178 |
+
linkTo(
|
| 179 |
+
"Pages/Logged in/Guided resolution/3. Leaked passwords",
|
| 180 |
+
"3c. Done",
|
| 181 |
+
)();
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
if (path === "/user/dashboard/fix/security-recommendations/phone") {
|
| 185 |
+
linkTo(
|
| 186 |
+
"Pages/Logged in/Guided resolution/4. Security recommendations",
|
| 187 |
+
"4a. Phone number",
|
| 188 |
+
)();
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
if (path === "/user/dashboard/fix/security-recommendations/email") {
|
| 192 |
+
linkTo(
|
| 193 |
+
"Pages/Logged in/Guided resolution/4. Security recommendations",
|
| 194 |
+
"4b. Email address",
|
| 195 |
+
)();
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
if (path === "/user/dashboard/fix/security-recommendations/ip") {
|
| 199 |
+
linkTo(
|
| 200 |
+
"Pages/Logged in/Guided resolution/4. Security recommendations",
|
| 201 |
+
"4c. IP address",
|
| 202 |
+
)();
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
if (path === "/user/dashboard/fix/security-recommendations/done") {
|
| 206 |
+
linkTo(
|
| 207 |
+
"Pages/Logged in/Guided resolution/4. Security recommendations",
|
| 208 |
+
"4d. Done",
|
| 209 |
+
)();
|
| 210 |
+
}
|
| 211 |
+
},
|
| 212 |
+
},
|
| 213 |
+
},
|
| 214 |
+
},
|
| 215 |
+
decorators: [AppDecorator],
|
| 216 |
+
};
|
| 217 |
+
|
| 218 |
+
export default preview;
|
.vscode/extensions.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
| 3 |
+
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
| 4 |
+
|
| 5 |
+
// List of extensions which should be recommended for users of this workspace.
|
| 6 |
+
"recommendations": [
|
| 7 |
+
"dbaeumer.vscode-eslint",
|
| 8 |
+
"stylelint.vscode-stylelint",
|
| 9 |
+
"esbenp.prettier-vscode",
|
| 10 |
+
"eamodio.gitlens",
|
| 11 |
+
"Orta.vscode-jest",
|
| 12 |
+
"macabeus.vscode-fluent",
|
| 13 |
+
],
|
| 14 |
+
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
| 15 |
+
"unwantedRecommendations": []
|
| 16 |
+
}
|
.vscode/settings.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"eslint.enable": true,
|
| 3 |
+
"eslint.format.enable": true,
|
| 4 |
+
"editor.tabSize": 2,
|
| 5 |
+
"editor.formatOnSave": false,
|
| 6 |
+
"editor.formatOnSaveMode": "modificationsIfAvailable",
|
| 7 |
+
"eslint.validate": [
|
| 8 |
+
"javascript"
|
| 9 |
+
],
|
| 10 |
+
"gitlens.advanced.blame.customArguments": [
|
| 11 |
+
"--ignore-revs-file",
|
| 12 |
+
".git-blame-ignore-revs"
|
| 13 |
+
],
|
| 14 |
+
"[css][scss]": {
|
| 15 |
+
"editor.codeActionsOnSave": {
|
| 16 |
+
"source.fixAll.stylelint": "explicit"
|
| 17 |
+
}
|
| 18 |
+
},
|
| 19 |
+
"[javascript][typescript][typescriptreact]": {
|
| 20 |
+
"editor.codeActionsOnSave": {
|
| 21 |
+
"source.fixAll.eslint": "explicit"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"[javascript][typescript][typescriptreact][markdown][html][css][scss]": {
|
| 25 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
| 26 |
+
},
|
| 27 |
+
"[fluent]": {
|
| 28 |
+
"files.trimTrailingWhitespace": true
|
| 29 |
+
},
|
| 30 |
+
}
|
CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
abdulelathmanhgwaith.link
|
CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Community Participation Guidelines
|
| 2 |
+
|
| 3 |
+
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
|
| 4 |
+
For more details, please read the
|
| 5 |
+
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
|
| 6 |
+
|
| 7 |
+
## How to Report
|
| 8 |
+
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
|
| 9 |
+
|
| 10 |
+
<!--
|
| 11 |
+
## Project Specific Etiquette
|
| 12 |
+
|
| 13 |
+
In some cases, there will be additional project etiquette i.e.: (https://bugzilla.mozilla.org/page.cgi?id=etiquette.html).
|
| 14 |
+
Please update for your project.
|
| 15 |
+
-->
|
LICENSE
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Mozilla Public License Version 2.0
|
| 2 |
+
==================================
|
| 3 |
+
|
| 4 |
+
1. Definitions
|
| 5 |
+
--------------
|
| 6 |
+
|
| 7 |
+
1.1. "Contributor"
|
| 8 |
+
means each individual or legal entity that creates, contributes to
|
| 9 |
+
the creation of, or owns Covered Software.
|
| 10 |
+
|
| 11 |
+
1.2. "Contributor Version"
|
| 12 |
+
means the combination of the Contributions of others (if any) used
|
| 13 |
+
by a Contributor and that particular Contributor's Contribution.
|
| 14 |
+
|
| 15 |
+
1.3. "Contribution"
|
| 16 |
+
means Covered Software of a particular Contributor.
|
| 17 |
+
|
| 18 |
+
1.4. "Covered Software"
|
| 19 |
+
means Source Code Form to which the initial Contributor has attached
|
| 20 |
+
the notice in Exhibit A, the Executable Form of such Source Code
|
| 21 |
+
Form, and Modifications of such Source Code Form, in each case
|
| 22 |
+
including portions thereof.
|
| 23 |
+
|
| 24 |
+
1.5. "Incompatible With Secondary Licenses"
|
| 25 |
+
means
|
| 26 |
+
|
| 27 |
+
(a) that the initial Contributor has attached the notice described
|
| 28 |
+
in Exhibit B to the Covered Software; or
|
| 29 |
+
|
| 30 |
+
(b) that the Covered Software was made available under the terms of
|
| 31 |
+
version 1.1 or earlier of the License, but not also under the
|
| 32 |
+
terms of a Secondary License.
|
| 33 |
+
|
| 34 |
+
1.6. "Executable Form"
|
| 35 |
+
means any form of the work other than Source Code Form.
|
| 36 |
+
|
| 37 |
+
1.7. "Larger Work"
|
| 38 |
+
means a work that combines Covered Software with other material, in
|
| 39 |
+
a separate file or files, that is not Covered Software.
|
| 40 |
+
|
| 41 |
+
1.8. "License"
|
| 42 |
+
means this document.
|
| 43 |
+
|
| 44 |
+
1.9. "Licensable"
|
| 45 |
+
means having the right to grant, to the maximum extent possible,
|
| 46 |
+
whether at the time of the initial grant or subsequently, any and
|
| 47 |
+
all of the rights conveyed by this License.
|
| 48 |
+
|
| 49 |
+
1.10. "Modifications"
|
| 50 |
+
means any of the following:
|
| 51 |
+
|
| 52 |
+
(a) any file in Source Code Form that results from an addition to,
|
| 53 |
+
deletion from, or modification of the contents of Covered
|
| 54 |
+
Software; or
|
| 55 |
+
|
| 56 |
+
(b) any new file in Source Code Form that contains any Covered
|
| 57 |
+
Software.
|
| 58 |
+
|
| 59 |
+
1.11. "Patent Claims" of a Contributor
|
| 60 |
+
means any patent claim(s), including without limitation, method,
|
| 61 |
+
process, and apparatus claims, in any patent Licensable by such
|
| 62 |
+
Contributor that would be infringed, but for the grant of the
|
| 63 |
+
License, by the making, using, selling, offering for sale, having
|
| 64 |
+
made, import, or transfer of either its Contributions or its
|
| 65 |
+
Contributor Version.
|
| 66 |
+
|
| 67 |
+
1.12. "Secondary License"
|
| 68 |
+
means either the GNU General Public License, Version 2.0, the GNU
|
| 69 |
+
Lesser General Public License, Version 2.1, the GNU Affero General
|
| 70 |
+
Public License, Version 3.0, or any later versions of those
|
| 71 |
+
licenses.
|
| 72 |
+
|
| 73 |
+
1.13. "Source Code Form"
|
| 74 |
+
means the form of the work preferred for making modifications.
|
| 75 |
+
|
| 76 |
+
1.14. "You" (or "Your")
|
| 77 |
+
means an individual or a legal entity exercising rights under this
|
| 78 |
+
License. For legal entities, "You" includes any entity that
|
| 79 |
+
controls, is controlled by, or is under common control with You. For
|
| 80 |
+
purposes of this definition, "control" means (a) the power, direct
|
| 81 |
+
or indirect, to cause the direction or management of such entity,
|
| 82 |
+
whether by contract or otherwise, or (b) ownership of more than
|
| 83 |
+
fifty percent (50%) of the outstanding shares or beneficial
|
| 84 |
+
ownership of such entity.
|
| 85 |
+
|
| 86 |
+
2. License Grants and Conditions
|
| 87 |
+
--------------------------------
|
| 88 |
+
|
| 89 |
+
2.1. Grants
|
| 90 |
+
|
| 91 |
+
Each Contributor hereby grants You a world-wide, royalty-free,
|
| 92 |
+
non-exclusive license:
|
| 93 |
+
|
| 94 |
+
(a) under intellectual property rights (other than patent or trademark)
|
| 95 |
+
Licensable by such Contributor to use, reproduce, make available,
|
| 96 |
+
modify, display, perform, distribute, and otherwise exploit its
|
| 97 |
+
Contributions, either on an unmodified basis, with Modifications, or
|
| 98 |
+
as part of a Larger Work; and
|
| 99 |
+
|
| 100 |
+
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
| 101 |
+
for sale, have made, import, and otherwise transfer either its
|
| 102 |
+
Contributions or its Contributor Version.
|
| 103 |
+
|
| 104 |
+
2.2. Effective Date
|
| 105 |
+
|
| 106 |
+
The licenses granted in Section 2.1 with respect to any Contribution
|
| 107 |
+
become effective for each Contribution on the date the Contributor first
|
| 108 |
+
distributes such Contribution.
|
| 109 |
+
|
| 110 |
+
2.3. Limitations on Grant Scope
|
| 111 |
+
|
| 112 |
+
The licenses granted in this Section 2 are the only rights granted under
|
| 113 |
+
this License. No additional rights or licenses will be implied from the
|
| 114 |
+
distribution or licensing of Covered Software under this License.
|
| 115 |
+
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
| 116 |
+
Contributor:
|
| 117 |
+
|
| 118 |
+
(a) for any code that a Contributor has removed from Covered Software;
|
| 119 |
+
or
|
| 120 |
+
|
| 121 |
+
(b) for infringements caused by: (i) Your and any other third party's
|
| 122 |
+
modifications of Covered Software, or (ii) the combination of its
|
| 123 |
+
Contributions with other software (except as part of its Contributor
|
| 124 |
+
Version); or
|
| 125 |
+
|
| 126 |
+
(c) under Patent Claims infringed by Covered Software in the absence of
|
| 127 |
+
its Contributions.
|
| 128 |
+
|
| 129 |
+
This License does not grant any rights in the trademarks, service marks,
|
| 130 |
+
or logos of any Contributor (except as may be necessary to comply with
|
| 131 |
+
the notice requirements in Section 3.4).
|
| 132 |
+
|
| 133 |
+
2.4. Subsequent Licenses
|
| 134 |
+
|
| 135 |
+
No Contributor makes additional grants as a result of Your choice to
|
| 136 |
+
distribute the Covered Software under a subsequent version of this
|
| 137 |
+
License (see Section 10.2) or under the terms of a Secondary License (if
|
| 138 |
+
permitted under the terms of Section 3.3).
|
| 139 |
+
|
| 140 |
+
2.5. Representation
|
| 141 |
+
|
| 142 |
+
Each Contributor represents that the Contributor believes its
|
| 143 |
+
Contributions are its original creation(s) or it has sufficient rights
|
| 144 |
+
to grant the rights to its Contributions conveyed by this License.
|
| 145 |
+
|
| 146 |
+
2.6. Fair Use
|
| 147 |
+
|
| 148 |
+
This License is not intended to limit any rights You have under
|
| 149 |
+
applicable copyright doctrines of fair use, fair dealing, or other
|
| 150 |
+
equivalents.
|
| 151 |
+
|
| 152 |
+
2.7. Conditions
|
| 153 |
+
|
| 154 |
+
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
| 155 |
+
in Section 2.1.
|
| 156 |
+
|
| 157 |
+
3. Responsibilities
|
| 158 |
+
-------------------
|
| 159 |
+
|
| 160 |
+
3.1. Distribution of Source Form
|
| 161 |
+
|
| 162 |
+
All distribution of Covered Software in Source Code Form, including any
|
| 163 |
+
Modifications that You create or to which You contribute, must be under
|
| 164 |
+
the terms of this License. You must inform recipients that the Source
|
| 165 |
+
Code Form of the Covered Software is governed by the terms of this
|
| 166 |
+
License, and how they can obtain a copy of this License. You may not
|
| 167 |
+
attempt to alter or restrict the recipients' rights in the Source Code
|
| 168 |
+
Form.
|
| 169 |
+
|
| 170 |
+
3.2. Distribution of Executable Form
|
| 171 |
+
|
| 172 |
+
If You distribute Covered Software in Executable Form then:
|
| 173 |
+
|
| 174 |
+
(a) such Covered Software must also be made available in Source Code
|
| 175 |
+
Form, as described in Section 3.1, and You must inform recipients of
|
| 176 |
+
the Executable Form how they can obtain a copy of such Source Code
|
| 177 |
+
Form by reasonable means in a timely manner, at a charge no more
|
| 178 |
+
than the cost of distribution to the recipient; and
|
| 179 |
+
|
| 180 |
+
(b) You may distribute such Executable Form under the terms of this
|
| 181 |
+
License, or sublicense it under different terms, provided that the
|
| 182 |
+
license for the Executable Form does not attempt to limit or alter
|
| 183 |
+
the recipients' rights in the Source Code Form under this License.
|
| 184 |
+
|
| 185 |
+
3.3. Distribution of a Larger Work
|
| 186 |
+
|
| 187 |
+
You may create and distribute a Larger Work under terms of Your choice,
|
| 188 |
+
provided that You also comply with the requirements of this License for
|
| 189 |
+
the Covered Software. If the Larger Work is a combination of Covered
|
| 190 |
+
Software with a work governed by one or more Secondary Licenses, and the
|
| 191 |
+
Covered Software is not Incompatible With Secondary Licenses, this
|
| 192 |
+
License permits You to additionally distribute such Covered Software
|
| 193 |
+
under the terms of such Secondary License(s), so that the recipient of
|
| 194 |
+
the Larger Work may, at their option, further distribute the Covered
|
| 195 |
+
Software under the terms of either this License or such Secondary
|
| 196 |
+
License(s).
|
| 197 |
+
|
| 198 |
+
3.4. Notices
|
| 199 |
+
|
| 200 |
+
You may not remove or alter the substance of any license notices
|
| 201 |
+
(including copyright notices, patent notices, disclaimers of warranty,
|
| 202 |
+
or limitations of liability) contained within the Source Code Form of
|
| 203 |
+
the Covered Software, except that You may alter any license notices to
|
| 204 |
+
the extent required to remedy known factual inaccuracies.
|
| 205 |
+
|
| 206 |
+
3.5. Application of Additional Terms
|
| 207 |
+
|
| 208 |
+
You may choose to offer, and to charge a fee for, warranty, support,
|
| 209 |
+
indemnity or liability obligations to one or more recipients of Covered
|
| 210 |
+
Software. However, You may do so only on Your own behalf, and not on
|
| 211 |
+
behalf of any Contributor. You must make it absolutely clear that any
|
| 212 |
+
such warranty, support, indemnity, or liability obligation is offered by
|
| 213 |
+
You alone, and You hereby agree to indemnify every Contributor for any
|
| 214 |
+
liability incurred by such Contributor as a result of warranty, support,
|
| 215 |
+
indemnity or liability terms You offer. You may include additional
|
| 216 |
+
disclaimers of warranty and limitations of liability specific to any
|
| 217 |
+
jurisdiction.
|
| 218 |
+
|
| 219 |
+
4. Inability to Comply Due to Statute or Regulation
|
| 220 |
+
---------------------------------------------------
|
| 221 |
+
|
| 222 |
+
If it is impossible for You to comply with any of the terms of this
|
| 223 |
+
License with respect to some or all of the Covered Software due to
|
| 224 |
+
statute, judicial order, or regulation then You must: (a) comply with
|
| 225 |
+
the terms of this License to the maximum extent possible; and (b)
|
| 226 |
+
describe the limitations and the code they affect. Such description must
|
| 227 |
+
be placed in a text file included with all distributions of the Covered
|
| 228 |
+
Software under this License. Except to the extent prohibited by statute
|
| 229 |
+
or regulation, such description must be sufficiently detailed for a
|
| 230 |
+
recipient of ordinary skill to be able to understand it.
|
| 231 |
+
|
| 232 |
+
5. Termination
|
| 233 |
+
--------------
|
| 234 |
+
|
| 235 |
+
5.1. The rights granted under this License will terminate automatically
|
| 236 |
+
if You fail to comply with any of its terms. However, if You become
|
| 237 |
+
compliant, then the rights granted under this License from a particular
|
| 238 |
+
Contributor are reinstated (a) provisionally, unless and until such
|
| 239 |
+
Contributor explicitly and finally terminates Your grants, and (b) on an
|
| 240 |
+
ongoing basis, if such Contributor fails to notify You of the
|
| 241 |
+
non-compliance by some reasonable means prior to 60 days after You have
|
| 242 |
+
come back into compliance. Moreover, Your grants from a particular
|
| 243 |
+
Contributor are reinstated on an ongoing basis if such Contributor
|
| 244 |
+
notifies You of the non-compliance by some reasonable means, this is the
|
| 245 |
+
first time You have received notice of non-compliance with this License
|
| 246 |
+
from such Contributor, and You become compliant prior to 30 days after
|
| 247 |
+
Your receipt of the notice.
|
| 248 |
+
|
| 249 |
+
5.2. If You initiate litigation against any entity by asserting a patent
|
| 250 |
+
infringement claim (excluding declaratory judgment actions,
|
| 251 |
+
counter-claims, and cross-claims) alleging that a Contributor Version
|
| 252 |
+
directly or indirectly infringes any patent, then the rights granted to
|
| 253 |
+
You by any and all Contributors for the Covered Software under Section
|
| 254 |
+
2.1 of this License shall terminate.
|
| 255 |
+
|
| 256 |
+
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
| 257 |
+
end user license agreements (excluding distributors and resellers) which
|
| 258 |
+
have been validly granted by You or Your distributors under this License
|
| 259 |
+
prior to termination shall survive termination.
|
| 260 |
+
|
| 261 |
+
************************************************************************
|
| 262 |
+
* *
|
| 263 |
+
* 6. Disclaimer of Warranty *
|
| 264 |
+
* ------------------------- *
|
| 265 |
+
* *
|
| 266 |
+
* Covered Software is provided under this License on an "as is" *
|
| 267 |
+
* basis, without warranty of any kind, either expressed, implied, or *
|
| 268 |
+
* statutory, including, without limitation, warranties that the *
|
| 269 |
+
* Covered Software is free of defects, merchantable, fit for a *
|
| 270 |
+
* particular purpose or non-infringing. The entire risk as to the *
|
| 271 |
+
* quality and performance of the Covered Software is with You. *
|
| 272 |
+
* Should any Covered Software prove defective in any respect, You *
|
| 273 |
+
* (not any Contributor) assume the cost of any necessary servicing, *
|
| 274 |
+
* repair, or correction. This disclaimer of warranty constitutes an *
|
| 275 |
+
* essential part of this License. No use of any Covered Software is *
|
| 276 |
+
* authorized under this License except under this disclaimer. *
|
| 277 |
+
* *
|
| 278 |
+
************************************************************************
|
| 279 |
+
|
| 280 |
+
************************************************************************
|
| 281 |
+
* *
|
| 282 |
+
* 7. Limitation of Liability *
|
| 283 |
+
* -------------------------- *
|
| 284 |
+
* *
|
| 285 |
+
* Under no circumstances and under no legal theory, whether tort *
|
| 286 |
+
* (including negligence), contract, or otherwise, shall any *
|
| 287 |
+
* Contributor, or anyone who distributes Covered Software as *
|
| 288 |
+
* permitted above, be liable to You for any direct, indirect, *
|
| 289 |
+
* special, incidental, or consequential damages of any character *
|
| 290 |
+
* including, without limitation, damages for lost profits, loss of *
|
| 291 |
+
* goodwill, work stoppage, computer failure or malfunction, or any *
|
| 292 |
+
* and all other commercial damages or losses, even if such party *
|
| 293 |
+
* shall have been informed of the possibility of such damages. This *
|
| 294 |
+
* limitation of liability shall not apply to liability for death or *
|
| 295 |
+
* personal injury resulting from such party's negligence to the *
|
| 296 |
+
* extent applicable law prohibits such limitation. Some *
|
| 297 |
+
* jurisdictions do not allow the exclusion or limitation of *
|
| 298 |
+
* incidental or consequential damages, so this exclusion and *
|
| 299 |
+
* limitation may not apply to You. *
|
| 300 |
+
* *
|
| 301 |
+
************************************************************************
|
| 302 |
+
|
| 303 |
+
8. Litigation
|
| 304 |
+
-------------
|
| 305 |
+
|
| 306 |
+
Any litigation relating to this License may be brought only in the
|
| 307 |
+
courts of a jurisdiction where the defendant maintains its principal
|
| 308 |
+
place of business and such litigation shall be governed by laws of that
|
| 309 |
+
jurisdiction, without reference to its conflict-of-law provisions.
|
| 310 |
+
Nothing in this Section shall prevent a party's ability to bring
|
| 311 |
+
cross-claims or counter-claims.
|
| 312 |
+
|
| 313 |
+
9. Miscellaneous
|
| 314 |
+
----------------
|
| 315 |
+
|
| 316 |
+
This License represents the complete agreement concerning the subject
|
| 317 |
+
matter hereof. If any provision of this License is held to be
|
| 318 |
+
unenforceable, such provision shall be reformed only to the extent
|
| 319 |
+
necessary to make it enforceable. Any law or regulation which provides
|
| 320 |
+
that the language of a contract shall be construed against the drafter
|
| 321 |
+
shall not be used to construe this License against a Contributor.
|
| 322 |
+
|
| 323 |
+
10. Versions of the License
|
| 324 |
+
---------------------------
|
| 325 |
+
|
| 326 |
+
10.1. New Versions
|
| 327 |
+
|
| 328 |
+
Mozilla Foundation is the license steward. Except as provided in Section
|
| 329 |
+
10.3, no one other than the license steward has the right to modify or
|
| 330 |
+
publish new versions of this License. Each version will be given a
|
| 331 |
+
distinguishing version number.
|
| 332 |
+
|
| 333 |
+
10.2. Effect of New Versions
|
| 334 |
+
|
| 335 |
+
You may distribute the Covered Software under the terms of the version
|
| 336 |
+
of the License under which You originally received the Covered Software,
|
| 337 |
+
or under the terms of any subsequent version published by the license
|
| 338 |
+
steward.
|
| 339 |
+
|
| 340 |
+
10.3. Modified Versions
|
| 341 |
+
|
| 342 |
+
If you create software not governed by this License, and you want to
|
| 343 |
+
create a new license for such software, you may create and use a
|
| 344 |
+
modified version of this License if you rename the license and remove
|
| 345 |
+
any references to the name of the license steward (except to note that
|
| 346 |
+
such modified license differs from this License).
|
| 347 |
+
|
| 348 |
+
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
| 349 |
+
Licenses
|
| 350 |
+
|
| 351 |
+
If You choose to distribute Source Code Form that is Incompatible With
|
| 352 |
+
Secondary Licenses under the terms of this version of the License, the
|
| 353 |
+
notice described in Exhibit B of this License must be attached.
|
| 354 |
+
|
| 355 |
+
Exhibit A - Source Code Form License Notice
|
| 356 |
+
-------------------------------------------
|
| 357 |
+
|
| 358 |
+
This Source Code Form is subject to the terms of the Mozilla Public
|
| 359 |
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 360 |
+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| 361 |
+
|
| 362 |
+
If it is not possible or desirable to put the notice in a particular
|
| 363 |
+
file, then You may include the notice in a location (such as a LICENSE
|
| 364 |
+
file in a relevant directory) where a recipient would be likely to look
|
| 365 |
+
for such a notice.
|
| 366 |
+
|
| 367 |
+
You may add additional accurate notices of copyright ownership.
|
| 368 |
+
|
| 369 |
+
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
| 370 |
+
---------------------------------------------------------
|
| 371 |
+
|
| 372 |
+
This Source Code Form is "Incompatible With Secondary Licenses", as
|
| 373 |
+
defined by the Mozilla Public License, v. 2.0.
|
README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Firefox Monitor Server
|
| 2 |
+
|
| 3 |
+
[](https://github.com/mozilla/blurts-server/actions/workflows/functional_tests_cron.yml)
|
| 4 |
+
|
| 5 |
+
## Summary
|
| 6 |
+
|
| 7 |
+
Firefox Monitor notifies users when their credentials have been compromised in a data breach.
|
| 8 |
+
|
| 9 |
+
This code is for the monitor.mozilla.org service & website.
|
| 10 |
+
|
| 11 |
+
Breach data is powered by [haveibeenpwned.com](https://haveibeenpwned.com/).
|
| 12 |
+
|
| 13 |
+
See the [Have I Been Pwned about page](https://haveibeenpwned.com/About) for
|
| 14 |
+
the "what" and "why" of data breach alerts.
|
| 15 |
+
|
| 16 |
+
## Architecture
|
| 17 |
+
|
| 18 |
+

|
| 19 |
+
|
| 20 |
+
## Development
|
| 21 |
+
|
| 22 |
+
### Requirements
|
| 23 |
+
|
| 24 |
+
- [Volta](https://volta.sh/) (installs the correct version of Node and npm)
|
| 25 |
+
- [Postgres](https://www.postgresql.org/) | Note: On a Mac, we recommend downloading the [Postgres.app](https://postgresapp.com/) instead.
|
| 26 |
+
- [Python](https://www.python.org/downloads/) | [With Homebrew](https://docs.brew.sh/Homebrew-and-Python)
|
| 27 |
+
- [k6](https://grafana.com/docs/k6/latest/set-up/install-k6/) | k6 load testing tool
|
| 28 |
+
|
| 29 |
+
### Code style
|
| 30 |
+
|
| 31 |
+
Linting and formatting is enforced via [ESLint](https://eslint.org/) and [Stylelint](https://stylelint.io/) for JS and CSS. Both are installed as dev-dependencies and can be run with `npm run lint`. A push to origin will also trigger linting.
|
| 32 |
+
|
| 33 |
+
ESLint rules are based on [eslint-config-standard](https://github.com/standard/eslint-config-standard). To fix all auto-fixable problems, run `npx eslint . --fix`
|
| 34 |
+
|
| 35 |
+
Stylelint rules are based on [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard). To fix all auto-fixable problems, run `npx stylelint public/css/ --fix`
|
| 36 |
+
|
| 37 |
+
### GIT
|
| 38 |
+
|
| 39 |
+
We track commits that are largely style/formatting via `.git-blame-ignore-revs`. This allows Git Blame to ignore the format commit author and show the original code author. In order to enable this in GitLens, add the following to VS Code `settings.json`:
|
| 40 |
+
|
| 41 |
+
```
|
| 42 |
+
"gitlens.advanced.blame.customArguments": [
|
| 43 |
+
"--ignore-revs-file",
|
| 44 |
+
".git-blame-ignore-revs"
|
| 45 |
+
],
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### Database
|
| 49 |
+
|
| 50 |
+
To create the database tables you have two options: manually, or using docker-compose
|
| 51 |
+
|
| 52 |
+
#### Manual setup
|
| 53 |
+
|
| 54 |
+
1. Create the `blurts` database:
|
| 55 |
+
|
| 56 |
+
```sh
|
| 57 |
+
createdb blurts
|
| 58 |
+
createdb test-blurts # for tests
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
2. Update the `DATABASE_URL` value in your `.env.local` (see step 3 under
|
| 62 |
+
"Install") file with your local db credentials:
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
DATABASE_URL="postgres://<username>:<password>@localhost:<port>/blurts"
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
3. Run the migrations:
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
npm run db:migrate
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
#### Via docker-compose
|
| 75 |
+
|
| 76 |
+
This will automatically provision the databases 'blurts' and 'test-blurts', and a user 'blurts' with password 'blurts'. The connection string in your .env.local file should be: "postgres://blurts:blurts@localhost:5432/blurts"
|
| 77 |
+
|
| 78 |
+
1. Ensure that you have an up-to-date .env.local file.
|
| 79 |
+
|
| 80 |
+
```$sh
|
| 81 |
+
cp .env.local.example .env.local
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
2. Start docker containers (this will stand up all services defined, including pubsub)
|
| 85 |
+
|
| 86 |
+
```sh
|
| 87 |
+
docker compose --env-file .env.local up -d
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
3. To tear down (this will delete stored data):
|
| 91 |
+
|
| 92 |
+
```sh
|
| 93 |
+
docker compose --env-file .env.local down
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Install
|
| 97 |
+
|
| 98 |
+
1. Clone and change to the directory:
|
| 99 |
+
|
| 100 |
+
```sh
|
| 101 |
+
git clone https://github.com/mozilla/blurts-server.git
|
| 102 |
+
cd blurts-server
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
2. Install dependencies:
|
| 106 |
+
|
| 107 |
+
```sh
|
| 108 |
+
npm install
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
3. Copy the `.env.local.example` file to `.env.local`:
|
| 112 |
+
|
| 113 |
+
```sh
|
| 114 |
+
cp .env.local.example .env.local
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
4. Install fluent linter (requires Python)
|
| 118 |
+
|
| 119 |
+
```sh
|
| 120 |
+
pip install -r .github/requirements.txt
|
| 121 |
+
|
| 122 |
+
OR
|
| 123 |
+
|
| 124 |
+
pip3 install -r .github/requirements.txt
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
5. Generate required Glean files (needs re-ran anytime Glean `.yaml` files are updated):
|
| 128 |
+
|
| 129 |
+
```sh
|
| 130 |
+
npm run build-glean
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
6. Generate required Nimbus files (needs re-ran anytime Nimbus' `config/nimbus.yaml` file is updated):
|
| 134 |
+
|
| 135 |
+
```sh
|
| 136 |
+
npm run build-nimbus
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
7. Create location data: Running the script manually is only needed for local development. The location data is being used in the onboarding exposures scan for autocompleting the “City and state” input.
|
| 140 |
+
|
| 141 |
+
```sh
|
| 142 |
+
npm run create-location-data
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
8. Ensure that you have the right `env` variables/keys set in your `.env.local` file. You can retrieve the variables from the Firefox Monitor 1Password Vault, or through [Magic-Wormhole](https://magic-wormhole.readthedocs.io/en/latest/), by asking one of the our engineers.
|
| 146 |
+
|
| 147 |
+
### Run
|
| 148 |
+
|
| 149 |
+
1. To run the server similar to production using a build phase, which includes minified and bundled assets:
|
| 150 |
+
|
| 151 |
+
```sh
|
| 152 |
+
npm start
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
**_OR_**
|
| 156 |
+
|
| 157 |
+
Run in "dev mode" with:
|
| 158 |
+
|
| 159 |
+
```sh
|
| 160 |
+
npm run dev
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
2. You may receive the error `Required environment variable was not set`. If this is the case, get the required env var(s) from another team member or ask in #fx-monitor-engineering. Otherwise, if the server started successfully, navigate to [localhost:6060](http://localhost:6060/)
|
| 164 |
+
|
| 165 |
+
### PubSub
|
| 166 |
+
|
| 167 |
+
Monitor uses GCP PubSub for processing incoming breach data, this can be tested locally using an emulator: <https://cloud.google.com/pubsub/docs/emulator>
|
| 168 |
+
|
| 169 |
+
You can run the emulator manually or via docker-compose.
|
| 170 |
+
|
| 171 |
+
#### Manual Setup
|
| 172 |
+
|
| 173 |
+
##### Run the GCP PubSub emulator
|
| 174 |
+
|
| 175 |
+
```sh
|
| 176 |
+
gcloud beta emulators pubsub start --project=your-project-name
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
(Set `your-project-name` as the value for `GCP_PUBSUB_PROJECT_ID` in your `.env.local`.)
|
| 180 |
+
|
| 181 |
+
#### Docker Compose Setup
|
| 182 |
+
|
| 183 |
+
This will automatically provision a pubsub topic named 'hibp-breaches' with a subscription named 'hibp-cron'.
|
| 184 |
+
|
| 185 |
+
1. Ensure that you have an up-to-date .env.local file.
|
| 186 |
+
|
| 187 |
+
```$sh
|
| 188 |
+
cp .env.local.example .env.local
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
2. Start docker containers (this will stand up all services defined, including postgres)
|
| 192 |
+
|
| 193 |
+
```sh
|
| 194 |
+
docker compose --env-file .env.local up -d
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
### In a different shell, set the environment to point at the emulator and run Monitor in dev mode
|
| 198 |
+
|
| 199 |
+
```sh
|
| 200 |
+
$(gcloud beta emulators pubsub env-init)
|
| 201 |
+
npm run dev
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
### Incoming WebHook requests from HIBP will be of the form
|
| 205 |
+
|
| 206 |
+
```sh
|
| 207 |
+
curl -d '{ "breachName": "000webhost", "hashPrefix": "test", "hashSuffixes": ["test"] }' \
|
| 208 |
+
-H "Authorization: Bearer unsafe-default-token-for-dev" \
|
| 209 |
+
http://localhost:6060/api/v1/hibp/notify
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
This emulates HIBP notifying our API that a new breach was found. Our API will
|
| 213 |
+
then add it to the (emulated) pubsub queue.
|
| 214 |
+
|
| 215 |
+
You can also use this request with staging credentials and endpoint to manually trigger alerts in the staging environment. For instructions on how to generate the hashPrefix and hashSuffix values, see [instructions below](#testing-the-breach-alerts-cron-job-locally).
|
| 216 |
+
|
| 217 |
+
### This pubsub queue will be consumed by this cron job, which is responsible for looking up and emailing impacted users
|
| 218 |
+
|
| 219 |
+
```sh
|
| 220 |
+
NODE_ENV="development" npm run dev:cron:breach-alerts
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
### Emails
|
| 224 |
+
|
| 225 |
+
Monitor generates multiple emails that get sent to subscribers. To preview or test-send these emails see documentation [here](docs/monitor-emails.md).
|
| 226 |
+
|
| 227 |
+
### Mozilla accounts ("FxA", formerly known as Firefox accounts)
|
| 228 |
+
|
| 229 |
+
The repo comes with a development FxA oauth app pre-configured in `.env`, which
|
| 230 |
+
should work fine running the app on <http://localhost:6060>. You'll need to get
|
| 231 |
+
the `OAUTH_CLIENT_SECRET` value from a team member or someone in #fxmonitor-engineering.
|
| 232 |
+
|
| 233 |
+
## Testing
|
| 234 |
+
|
| 235 |
+
The unit test suite can be run via `npm test`.
|
| 236 |
+
|
| 237 |
+
At the beginning of a test suite run, the `test-blurts` database will be populated with test tables and seed data found in `src/db/seeds/`
|
| 238 |
+
|
| 239 |
+
At the end of a test suite, coverage info will be sent to [Coveralls](https://coveralls.io/) to assess coverage changes and provide a neat badge. To upload coverage locally, you need a root `.coveralls.yml` which contains a token – get this from another member of the Monitor team.
|
| 240 |
+
|
| 241 |
+
The [functional tests](https://github.com/mozilla/blurts-server/src/functional-tests) use Playwright and can be run via `npm run functional-tests`.
|
| 242 |
+
|
| 243 |
+
#### Test Firefox Integration
|
| 244 |
+
|
| 245 |
+
_**TODO:** the following functionality is disabled but the instructions are left here for posterity._
|
| 246 |
+
|
| 247 |
+
Firefox's internal about:protections page ("Protections Dashboard") fetches and
|
| 248 |
+
displays breach stats for Firefox users who are signed into their FXA.
|
| 249 |
+
|
| 250 |
+
To test this part of Monitor:
|
| 251 |
+
|
| 252 |
+
1. [Set a Firefox profile to use the staging Firefox Accounts
|
| 253 |
+
server.](https://mozilla.github.io/ecosystem-platform/docs/process/using-the-staging-environment#working-with-staging-firefox-accounts)
|
| 254 |
+
2. In the same profile, go to about:config and replace [all
|
| 255 |
+
`https://monitor.firefox.com`
|
| 256 |
+
values](https://searchfox.org/mozilla-central/search?q=monitor.firefox.com&path=browser/app/profile/firefox.js) with `http://localhost:6060`
|
| 257 |
+
3. Restart Firefox with that profile.
|
| 258 |
+
4. Go to `about:protections`
|
| 259 |
+
5. Everything should be using your localhost instance of Monitor.
|
| 260 |
+
|
| 261 |
+
#### Load testing
|
| 262 |
+
|
| 263 |
+
k6 is used for load testing.
|
| 264 |
+
|
| 265 |
+
See <https://grafana.com/docs/k6/latest/get-started/running-k6/> for more information.
|
| 266 |
+
|
| 267 |
+
##### HIBP breach alerts
|
| 268 |
+
|
| 269 |
+
To test the HIBP breach alerts endpoint, use:
|
| 270 |
+
|
| 271 |
+
```sh
|
| 272 |
+
export SERVER_URL=...
|
| 273 |
+
export HIBP_NOTIFY_TOKEN=...
|
| 274 |
+
npm run loadtest:hibp-webhook
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
You can customise the number of requests to send in parallel ("virtual users") by setting the
|
| 278 |
+
[`K6_VUS`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#vus) environment
|
| 279 |
+
variable (default 1000), and for how long to send those requests by setting the
|
| 280 |
+
[`K6_DURATION`](https://grafana.com/docs/k6/latest/using-k6/k6-options/reference/#duration)
|
| 281 |
+
environment variable (default 30s).
|
| 282 |
+
|
| 283 |
+
You can also enforce the alert being sent for a specific email address via the
|
| 284 |
+
`LOADTEST_BREACHED_EMAIL` environment variable.
|
| 285 |
+
|
| 286 |
+
#### Testing the Breach Alerts cron job locally
|
| 287 |
+
|
| 288 |
+
1. Ensure SMTP_URL environment variable is unset; this will log to JSON instead of attempting to send an email
|
| 289 |
+
1. Follow instructions to start blurts server locally, including the database and emulated GCP PubSub topic
|
| 290 |
+
1. Create a new account, and note the email address you used for the next step
|
| 291 |
+
1. Update the email address below and paste into your terminal
|
| 292 |
+
|
| 293 |
+
```sh
|
| 294 |
+
# Replace with whatever email address you used above, or omit and
|
| 295 |
+
# export env var first to persist between runs
|
| 296 |
+
# `export HIBP_TEST_EAMIL=replace-me-email@example.com`
|
| 297 |
+
HIBP_TEST_EMAIL="replace-me-email@example.com"; \
|
| 298 |
+
HASH=$(echo -n "$HIBP_TEST_EMAIL" | sha1sum | awk '{print $1}'); \
|
| 299 |
+
PREFIX=${HASH:0:7}; \
|
| 300 |
+
SUFFIX=${HASH:7}; \
|
| 301 |
+
curl -d "{\"breachName\": \"000webhost\", \"hashPrefix\": \"$PREFIX\", \"hashSuffixes\": [\"$SUFFIX\"]}" \
|
| 302 |
+
-H "Authorization: Bearer unsafe-default-token-for-dev" \
|
| 303 |
+
-H "Content-Type: application/json" \
|
| 304 |
+
http://localhost:6060/api/v1/hibp/notify
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
Note that the database must be seeded with breaches or else this request will not trigger emails due to validation error. The breachName must match the name of a breach in the database. Query the `breaches` table in the database for additional breach names to test more than once for the same email address (a user will be notified for a breach only once). Alternatively you can delete the record that was created in the `email_notifications` table to retest.
|
| 308 |
+
|
| 309 |
+
## Localization
|
| 310 |
+
|
| 311 |
+
All text that is visible to the user is defined in [Fluent](https://projectfluent.org/) files inside `/locales/en/` and `/locales-pending/`. After strings get added to files in the former directory on our `main` branch, they will be made available to our volunteer localizers via Pontoon, Mozilla's localization platform. Be sure to reference the [localization documentation](https://mozilla-l10n.github.io/documentation/localization/dev_best_practices.html) for best practices. It's best to only move the strings to `/locales/en/` when they are more-or-less final and ready for localization. Your PR should be automatically tagged with a reviewer from the [Mozilla L10n team](https://wiki.mozilla.org/L10n:Mozilla_Team) to approve your request.
|
| 312 |
+
|
| 313 |
+
You can check translation status via the [Pontoon site](https://pontoon.mozilla.org/projects/firefox-monitor-website/). After strings have been localized, [a pull request](https://github.com/mozilla/blurts-server/pulls?q=is%3Apr+label%3Al10n+) with the updated strings is automatically opened against our repository. Please be mindful that Mozilla localizers are volunteers, and translations come from different locales at different times – usually after a week or more.
|
| 314 |
+
|
| 315 |
+
To use the strings in code, you need to obtain a `ReactLocalization` instance, which selects the right version of your desired string for the user. How to do that depends on where your code runs: in tests, in a cron job, in a server component, or on the client-side. Generally, you will import a `getL10n` function from one of the modules in `/src/app/functions/l10n/`, except for [Client Components](https://nextjs.org/docs/app/building-your-application/rendering/client-components), which use [the `useL10n` hook](./src/app/hooks/l10n.ts). Look at existing code for inspiration.
|
| 316 |
+
|
| 317 |
+
## Preview Deployment
|
| 318 |
+
|
| 319 |
+
We use GCP Cloudrun for dev review – official stage and production apps are built by the Dockerfile and Github Actions. Everything that is merged into `main` will deploy automatically to stage. The ADR for preview deployment can be found [here](https://github.com/mozilla/blurts-server/blob/main/docs/adr/0008-preview-deployment.md)
|
| 320 |
+
|
| 321 |
+
_**TODO:** add full deploy process similar to Relay_
|
| 322 |
+
|
| 323 |
+
_**TODO:** consider whether we can re-enable Heroku Review Apps_
|
google3c147320b7d46152.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
google-site-verification: google3c147320b7d46152.html
|