@@ -1,3 +1,5 @@
[šŸ‘ˆ Back to README](README.md)

# Contributing

We love pull requests from everyone. By participating in this project, you
@@ -9,7 +11,7 @@ rigid or confusing. If you'd like to contribute and don't understand something
here, reach out on IRC, and we'll be happy to offer solutions.

[mozcoc]: https://www.mozilla.org/en-US/about/governance/policies/participation
[coc]: https://github.com/mozilla/testpilot/blob/master/CODE_OF_CONDUCT.md
[coc]: https://github.com/mozilla/testpilot/blob/master/docs/code_of_conduct.md
[dev setup]: https://github.com/mozilla/testpilot#development

### Saying Hello
@@ -29,11 +31,11 @@ the Mozilla [wiki][wiki] has instructions.
We use the same process for community members and paid staff.

#### Tests
Tests should be written for new code contributed.
* [server tests example](testpilot/experiments/tests.py)
* [client tests example](testpilot/frontend/static-src/test/)

[Further docs on testing](./TESTING.md).
Tests should be written for new code contributed. See [`docs/development/testing.md`][testing]
for more information.

[testing]: ./docs/development/testing.md

#### Branches

@@ -65,6 +67,11 @@ some changes or improvements or alternatives.

We try to avoid landing any code without at least a cursory review.

General rules for picking up reviews:
- if you are going to review a PR, assign yourself to it
- assigned person is responsible for helping get the PR over the finish line
- if a PR doesn't have an assigned person, it's up for grabs

Reviewers will run through roughly the following checklist:
- Does the code do what it says it does?
- Is the proposed fix the right fix? Does it address the underlying cause of the bug?

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

172 README.md
@@ -1,150 +1,56 @@
Test Pilot
==========
**NOTE:** The Test Pilot program has ended. The master branch in this repository reflects the state of the project before it ended. [The farewell message mini-site lives in the `eol` branch!](https://github.com/mozilla/testpilot/tree/eol)

![Test Pilot Logo](/testpilot/frontend/static-src/images/copter.png)

[![Circle CI](https://circleci.com/gh/mozilla/testpilot/tree/master.svg?style=svg&circle-token=88ea3e1a6d9b7558092b75358c6ab9251739b9b5)](https://circleci.com/gh/mozilla/testpilot/tree/master)
[![Coverage status](https://img.shields.io/coveralls/mozilla/testpilot/master.svg)](https://coveralls.io/r/mozilla/testpilot)
[![Requirements Status](https://requires.io/github/mozilla/testpilot/requirements.svg?branch=master)](https://requires.io/github/mozilla/testpilot/requirements/?branch=master)
<img src="frontend/src/app/components/Copter/img/copter.png" alt="Test Pilot Logo" width="328" height="265">

# Test Pilot

Test Pilot is an opt-in platform that allows us to perform controlled tests of new high-visibility product concepts in the general release channel of Firefox.

Test Pilot is not intended to replace trains for most features, nor is it a test bed for concepts we do not believe have a strong chance of shipping in general release. Rather, it is reserved for features that require user feedback, testing, and tuning before they ship with the browser.

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [More Information](#more-information)
- [Development](#development)
- [Quickstart](#quickstart)
- [OS X hosts](#os-x-hosts)
- [Ubuntu Linux hosts](#ubuntu-linux-hosts)
- [Windows hosts](#windows-hosts)
- [Next Steps](#next-steps)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## More Information

- Wiki: https://wiki.mozilla.org/Test_Pilot
- IRC: #testpilot on irc.mozilla.org
- [Test Pilot Metrics](docs/README-METRICS.md)

## Development

### Quickstart

This project uses Docker in development. You'll get a lot of benefit
by acquainting yourself [with Docker and its documentation][docker-docs].
However, you can get started on Test Pilot development with a minimum of Docker
know-how:

[docker-docs]: https://docs.docker.com/

By default `js-lint` and `sass-lint` watch options are set to true. If you would like
to override these, you can do so in [debug-config.json](./debug-config.json).
if you'd like to run linters on `pre-commit` you can use this [pre-commit-hook](https://gist.github.com/meandavejustice/39f7edc046f3458aa076).

See some of our [Tips and Tricks](docs/README-DOCKER.md).

#### First Thing's First

Make sure you clone the Test Pilot repo:

`git clone https://github.com/mozilla/testpilot.git`

#### OS X hosts

1. [Install Docker Toolbox](http://docs.docker.com/mac/started/)

2. Make sure you have a default Docker machine:

`docker-machine create --driver virtualbox default`

3. Make sure the default machine is running:

`docker-machine start default`

4. Make sure your shell can see the default Docker machine:

`eval "$(docker-machine env default)"`
Test Pilot is not intended to replace trains for most features, nor is it a test bed for concepts we do not believe have a strong chance of shipping in general release. Rather, it is reserved for features that require user feedback, testing and tuning before they ship with the browser.

5. Check the IP address of the default Docker machine:
[![Build](https://img.shields.io/circleci/project/mozilla/testpilot.svg)](https://circleci.com/gh/mozilla/testpilot/)
[![codecov](https://codecov.io/gh/mozilla/testpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/mozilla/testpilot)

`docker-machine ip default`
---

6. Use this IP address to add an entry for `testpilot.dev` in `/etc/hosts`:
## Table of Contents

`192.168.99.100 testpilot.dev`
- Developing Test Pilot
- [Quickstart](docs/development/quickstart.md) - Get your development environment working.
- [Testing](docs/development/testing.md) - Automated testing.
- [Storybook](docs/development/storybook.md) - Interactive development & testing for UI components.
- [Add-on](addon/README.md) - Working on the Test Pilot add-on.
- [Deployment](docs/development/deployment.md) - Deploying Test Pilot to staging and production
- [Dev Deployment](docs/development/dev-deployment.md) - Deploying Test Pilot to the dev environment
- [Verifying deployments](docs/development/verification.md) - Verifying Test Pilot deployments.
- Developing experiments
- [Recommended process](docs/experiments/developing_an_experiment.md)
- [Experiment metrics](docs/experiments/ga.md) - The use of Google Analytics to track experiment data.
- [Experiment Feedback Integration](docs/examples/feedback-buttons.md)
- Metrics
- [Google Analytics](docs/metrics/ga.md) - How we use Google Analytics.
- [New features](docs/metrics/new_features.md) - Everything needed to instrument something new.
- [Experiment content](docs/content/reference.md) - Management of experiment content.
- [Graduating experiments](docs/content/graduation.md) - Graduation of old experiments.
- [Process](docs/process.md) - How we create, triage, and assign work.
- [FAQ](docs/faq.md)
- [Contributing to Test Pilot](CONTRIBUTING.md)
- [Code of conduct](docs/code_of_conduct.md)
- [License](LICENSE)

You can do this manually, or the [bin/update-ip.sh][update-ip] script can
take care of this for you.
---

[update-ip]: https://github.com/mozilla/testpilot/blob/master/bin/update-ip.sh
## Localization

7. Don't forget to cd into your Test Pilot directory:
Test Pilot localization is managed via [Pontoon](https://pontoon.mozilla.org/projects/test-pilot-website/), not direct pull requests to the repository. If you want to fix a typo, add a new language, or simply know more about localization, please get in touch with the [existing localization team](https://pontoon.mozilla.org/teams/) for your language or Mozilla’s [l10n-drivers](https://wiki.mozilla.org/L10n:Mozilla_Team#Mozilla_Corporation) for guidance.

`cd testpilot`
---

8. Create and setup the Docker containers (this will take some time):

`docker-compose up`

#### Ubuntu Linux hosts

1. [Install Docker](http://docs.docker.com/linux/started/)

2. [Install Docker Compose](https://docs.docker.com/compose/install/)

3. Add an entry for `testpilot.dev` in `/etc/hosts`:

`127.0.0.1 testpilot.dev`

You can do this manually, or the [bin/update-ip.sh][update-ip] script can
take care of this for you.

4. Don't forget to cd into your Test Pilot directory:

`cd testpilot`

5. Create and setup the Docker containers (this will take some time):

`sudo docker-compose up`

#### Windows hosts

* **Help wanted**: Getting things working on Windows may be similar to OS X,
but the team has little experience with that environment.

### Next Steps

* Start editing files - changes should be picked up automatically.

* Visit the Django server, using the hostname you added to `/etc/hosts`:

`http://testpilot.dev:8000/`

* Visit Django admin, login with username `admin` and password `admin`:

`http://testpilot.dev:8000/admin/`

* For further reading:

* [`README-DOCKER.md`](./docs/README-DOCKER.md) - for more hints & tips on Docker in
development, including how to set up custom configurations and run common
tests & checks.

* [`circle.yml`](./circle.yml) - to see what checks are run automatically in Circle
CI, which you should ensure pass locally before submitting a Pull Request on
GitHub

* [`addon/README.md`](./addon/README.md) - for more details on the addon this
site uses to enable advanced features.
## More Information

* [`docs/WORK.md`](./docs/WORK.md) - information on how we create, triage and assign work.
- Wiki: https://wiki.mozilla.org/Test_Pilot
- IRC: #testpilot on irc.mozilla.org

* [`docs/DEPLOYMENT.md`](./docs/DEPLOYMENT.md) - process for deploying Test Pilot to stage and production.
---

* [`docs/FAQs.md`](./docs/FAQs.md) - frequently asked questions.
<img src="https://avatars2.githubusercontent.com/u/131524?s=200&v=4" width="50"></img>

This file was deleted.

@@ -1,2 +1,8 @@
rules:
func-names: 0
extends:
- ../.eslintrc

overrides:
-
files: ['background.js', 'options/options.js']
rules:
import/unambiguous: off

This file was deleted.

This file was deleted.

@@ -1,189 +1,68 @@
# Test Pilot
The add-on where ideas come to idea
Test Pilot main add-on
----------------------

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
This add-on serves a few basic purposes common to participation in any Test Pilot experiment:

- [installation](#installation)
- [configuration](#configuration)
- [development](#development)
- [running once for testing](#running-once-for-testing)
- [packaging](#packaging)
- [distributing](#distributing)
- [Events](#events)
- [Talking to the addon](#talking-to-the-addon)
- [Maintainers](#maintainers)
- [Attribution](#attribution)
* Toolbar button as a quick shortcut to Test Pilot.
* Notifications when experiments are updated and new ones arrive.
* Daily metric reporting about participation in experiments.

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Building and Development

## installation
First, ensure that you've followed [the quickstart guide](../docs/development/quickstart.md) for working on the Test Pilot site itself. This includes ensuring you've got the right version of Node.js (currently, [v6.x LTS](https://nodejs.org/dist/latest-v6.x/)).

`npm install`

## configuration

prod: check the `CONFIG` property in `package.json`
dev: [dev-prefs.json](dev-prefs.json)

## development
```
cd addon
npm start
```

A relatively easy path for working on this addon involves the following steps:
If you just want an XPI package of the add-on, use one of these commands:
```
npm run package # builds for local dev by default
```

1. Install [Firefox Developer Edition][devedition].
If your packaging for another environment then you must include some environment variables:
```
ENVIRONMENT_TITLE=production ENVIRONMENT_URL=https://testpilot.firefox.com/ npm run package
```

2. [Disable add-on signature checks.][sigchecks] TL;DR: Enter `about:config` in
the URL bar. Set `xpinstall.signatures.required` and
`xpinstall.whitelist.required` flags to false.
Here is the full list of urls for each environment:

3. Install the [Extension Auto-Installer][autoinstaller] Add-on in Firefox
Developer Edition.
```
ENVIRONMENT_TITLE=local ENVIRONMENT_URL=https://example.com/
ENVIRONMENT_TITLE=dev ENVIRONMENT_URL=https://testpilot.dev.mozaws.net/
ENVIRONMENT_TITLE=l10n ENVIRONMENT_URL=https://testpilot-l10n.dev.mozaws.net/
ENVIRONMENT_TITLE=stage ENVIRONMENT_URL=https://testpilot.stage.mozaws.net/
ENVIRONMENT_TITLE=production ENVIRONMENT_URL=https://testpilot.firefox.com/
```

4. Run `npm start` to fire up a watcher that will build the Test Pilot add-on
whenever files change and auto-update the installed version in Firefox.
If you'd like to actively work on the add-on, here are some additional steps to set up a more convenient workflow:

5. Read all about [setting up an extension development
environment][extensiondev] on MDN.
1. Install [Firefox Developer Edition][devedition]. (Nightly should work, too, but Dev Edition is preferred.)

[devedition]: https://www.mozilla.org/en-US/firefox/developer/
[sigchecks]: https://wiki.mozilla.org/Add-ons/Extension_Signing#FAQ
[autoinstaller]: https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/
[extensiondev]: https://developer.mozilla.org/en-US/Add-ons/Setting_up_extension_development_environment

For UI hacking you can run `npm run watch-ui` to easily debug `lib/templates.js` and `data/panel.css`

## running once for testing

* Install [Firefox Beta][fxbeta]

* `npm run once`

This should package the add-on and fire up Firefox Beta using a fresh profile
with the add-on installed.

[fxbeta]: https://www.mozilla.org/en-US/firefox/channel/#beta

## packaging

`npm run sign`

## distributing

We serve the add-on from the `/static/addon/` directory. We will need
to get the add-on signed via [AMO](http://addons.mozilla.org/) and move
it into the correct directory. This is all packaged into a script,
`npm run sign` in the [package.json](./package.json).

## Events

Accepted:
* `install-experiment`
* `uninstall-experiment`
* `uninstall-self`
* `sync-installed`

Emitted:
* `sync-installed-result`
* `addon-install:install-started`
* `addon-install:install-new`
* `addon-install:install-cancelled`
* `addon-install:install-ended`
* `addon-install:install-failed`
* `addon-install:download-started`
* `addon-install:download-progress`
* `addon-install:download-ended`
* `addon-install:download-cancelled`
* `addon-install:download-failed`
* `addon-uninstall:uninstall-started`
* `addon-uninstall:uninstall-ended`
* `addon-manage:enabled`
* `addon-manage:disabled`
* `addon-self:installed`
* `addon-self:enabled`
* `addon-self:upgraded`
* `addon-self:uninstalled`

Any emitted events prefixed with `addon-install:` will have an associated object
which will be structured as such:

``` json
{
"name": "Fox Splitter",
"error": 0,
"state": 6,
"version": "2.1.2012122901.1-signed",
"progress": 517308,
"maxProgress": 517308
}

Finally, to start up an add-on build with file watching:
```
The event `addon-install:install-ended` will include some extra properties:

``` json
{
"id": "foxsplitter@sakura.ne.jp",
"name": "Fox Splitter",
"error": 0,
"state": 6,
"version": "2.1.2012122901.1-signed",
"progress": 517308,
"maxProgress": 517308,
"description": "Splits browser window as you like.",
"homepageURL": "http://piro.sakura.ne.jp/xul/foxsplitter/index.html.en",
"iconURL": "file:///tmp/074a4b62-239e-49bb-b75a-4935c349855c/extensions/foxsplitter@piro.sakura.ne.jp/icon.png",
"size": 511221,
"signedState": 2,
"permissions": 13
}
npm start
```

#### Talking to the add-on
This command will watch as you edit files, rebuilding the add-on with every change and re-installing it in your browser by way of the web-ext tool.

You will need to setup the following function (or an equivalent) to send messages to the add-on.
1. make sure you select the correct environment in the test pilot options menu from `about:addons`. Defaults to 'production'.

``` javascript
function sendToAddon (data) {
document.documentElement.dispatchEvent(new CustomEvent(
'from-web-to-addon', { bubbles: true, detail: data }
));
}
```
Then you can use the `sendToAddon` method to send messages.
## General architecture

``` javascript
sendToAddon({type: 'loaded'});
```
and to setup listeners.

``` javascript
window.addEventListener("from-addon-to-web", function (event) {
if (!event.detail || !event.detail.type) { return; }
statusUpdate(event.detail.type, event.detail);
switch (event.detail.type) {
case 'sync-installed':
syncInstalledAddons(event.detail);
break;
default:
console.log('WEB RECEIVED FROM ADDON', JSON.stringify(event.detail, null, ' '));
break;
}
}, false);
```
This add-on is a [WebExtension][].

Submit updates:
``` javascript
sendToAddon({type: 'install-experiment', detail: {xpi_url: 'https://people.mozilla.com/~jhirsch/universal-search-addon/addon.xpi'}});
```
[WebExtension]: https://developer.mozilla.org/en-US/Add-ons/WebExtensions

The entry point for the webextension lives in [`./background.js`](./background.js).

## Maintainers
* A daily report of how many experiments are currently enabled

* Dave Justice <djustice@mozilla.com>
* Les Orchard <lorchard@mozilla.com>

## Attribution
The details of what measurements are implemented for any particular experiment can be found with the respective source code repositories for each experiment, generally in a document at `docs/METRICS.md`.

Arrow Icon made by
[Appzgear](http://www.flaticon.com/authors/appzgear) from
[www.flaticon.com](http://www.flaticon.com) is licensed by
[CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
For more general details on where these metric events are sent for measurement &
analysis, read [`../docs/metrics/ga.md`](../docs/metrics/ga.md).
@@ -0,0 +1,186 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* global browser, currentEnvironment */

function log(...args) {
// console.log(...["[TESTPILOT v2] (background)"].concat(args)); // eslint-disable-lint no-console
}

const storage = browser.storage.local;
const RESOURCE_UPDATE_INTERVAL = 4 * 60 * 60 * 1000; // 4 hours
const ONE_DAY = 60 * 60 * 1000 * 24;
const TWO_WEEKS = 2 * 7 * ONE_DAY;

/* browser action constants */
const BROWSER_ACTION_LINK_BASE = [
"experiments",
"?utm_source=testpilot-addon",
"&utm_medium=firefox-browser",
"&utm_campaign=testpilot-doorhanger"
].join("");
const BROWSER_ACTION_LINK_NOT_BADGED = BROWSER_ACTION_LINK_BASE +
"&utm_content=not+badged";
const BROWSER_ACTION_LINK_BADGED = BROWSER_ACTION_LINK_BASE +
"&utm_content=badged";
let BROWSER_ACTION_LINK = BROWSER_ACTION_LINK_NOT_BADGED;

const resources = {
experiments: [],
news_updates: []
};

function uuidv4() {
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16)
);
}

function getInstalledTxpAddons() {
return browser.management.getAll().then((infoArray) => {
const installed = infoArray.map((exp) => exp.id);
const txpAddons = resources.experiments.map((exp) => exp.id);

return installed.filter(function(n) {
return txpAddons.includes(n);
});
});
}

async function setup() {
setInterval(fetchResources, RESOURCE_UPDATE_INTERVAL);
setupBrowserAction();
await fetchResources();
setDailyPing();
}

async function sendPingOnAlarm(alarmInfo) {
if (alarmInfo.name === "daily-ping") {
const { lastPing } = await storage.get({"lastPing": Date.now() - ONE_DAY});
if (((new Date) - lastPing) > ONE_DAY) {
const installedTxpAddons = await getInstalledTxpAddons();
let { clientUUID } = await storage.get("clientUUID");
if (!clientUUID) {
await storage.set({ clientUUID: clientUUID = uuidv4() });
}
submitPing(alarmInfo.name, "installed-addons", installedTxpAddons, clientUUID);
await storage.set({lastPing: Date.now()});
}
}
}

function setDailyPing() {
browser.alarms.create("daily-ping", {
delayInMinutes: 1,
periodInMinutes: 60 // check hourly
});

browser.alarms.onAlarm.addListener(sendPingOnAlarm);
}

function setupBrowserAction() {
log("setupBrowserAction");
browser.browserAction.setBadgeBackgroundColor({ color: "#0996f8" });
browser.browserAction.onClicked.addListener(() => {
// reset badge immediately
browser.browserAction.setBadgeText({ text: "" });
storage.set({ clicked: Date.now() });
browser.tabs.create({
url: `${currentEnvironment.baseUrl}${BROWSER_ACTION_LINK}`
});
});
}

function fetchResources() {
log("fetchResources");
return Promise.all(
Object.keys(resources).map(path =>
fetch(`${currentEnvironment.baseUrl}api/${path}.json`)
.then(res => res.json())
.then((data) => data.results ? data.results : data)
.then((data) => [path, data])
.catch(err => {
log("fetchResources error", path, err);
return [path, null];
}))
).then(results => {
log("fetchResources results", results);
results.forEach(([path, data]) => (resources[path] = data));
updateBadgeTextOnNew(resources.experiments, resources.news_updates);
log("fetchResources results parsed", resources);
});
}

async function updateBadgeTextOnNew(experiments, news_updates) {
let { clicked } = await storage.get("clicked");
if (!clicked) {
// Set initial button click timestamp if not found
clicked = Date.now();
await storage.set({ clicked });
}

const newExperiments = (experiments || []).filter(experiment => {
// don't include experiments which haven't launched yet
if (new Date() < new Date(experiment.launch_date)) return false;
const dt = new Date(experiment.modified || experiment.created).getTime();
return dt >= clicked;
});

// check for port number on local, we need to strip it off
// to properly fetch cookies.
const baseUrl = currentEnvironment.baseUrl;
const portIndex = baseUrl.indexOf(":8000");
const cookieUrl = (portIndex > -1) ? baseUrl.substring(0, portIndex) : baseUrl;

let lastViewed = 0;
const cookie = await browser.cookies.get({
url: cookieUrl,
name: "updates-last-viewed-date"
});

if (cookie) lastViewed = cookie.value;

/* only show badge for news update if:
* - has the "major" key
* - update has been published in the past two weeks
* - update has not been "seen" by the frontend (lastViewed)
* - update has not been "seen" by the addon (clicked)
*/
const twoWeeksAgo = Date.now() - TWO_WEEKS;
const newsUpdates = (news_updates || [])
.filter(u => u.major)
.filter(u => new Date(u.published).getTime() >= twoWeeksAgo)
.filter(u => new Date(u.published).getTime() >= lastViewed)
.filter(u => new Date(u.published).getTime() >= clicked);

BROWSER_ACTION_LINK = (newExperiments.length || newsUpdates.length) > 0
? BROWSER_ACTION_LINK_BADGED
: BROWSER_ACTION_LINK_NOT_BADGED;

browser.browserAction.setBadgeText({
text: (newExperiments.length || newsUpdates.length) > 0 ? "!" : ""
});
}

function submitPing(object, event, addons, clientUUID) {
return fetch("https://ssl.google-analytics.com/collect", {
method: "POST",
body: new URLSearchParams({
v: 1,
t: "event",
ec: "add-on Interactions",
ea: object,
el: event,
tid: "UA-49796218-47",
cid: clientUUID,
dimension14: addons
})
});
}

// Let's Go!
setup();
@@ -0,0 +1,41 @@
#!/usr/bin/env node

const fs = require("fs-extra");
const packageInfo = require("../package.json");
const manifestBase = require("../manifest-base.json");
const manifestFilename = __dirname + "/../manifest.json";

if (typeof process.env["ENVIRONMENT_TITLE"] === "undefined") {
process.env["ENVIRONMENT_TITLE"] = "local";
process.env["ENVIRONMENT_URL"] = "https://example.com:8000/";
}

const newName = (process.env["ENVIRONMENT_TITLE"] === "production") ?
"Test Pilot" : `Test Pilot ${process.env["ENVIRONMENT_TITLE"]} build`;
const newId = (process.env["ENVIRONMENT_TITLE"] === "production") ?
"@testpilot-addon" : `@testpilot-addon-${process.env["ENVIRONMENT_TITLE"]}`;

const currentEnvContent = `
/* This file is generated by "bin/process-with-env" */
// eslint-disable-next-line no-unused-vars
const currentEnvironment = {
name: "${process.env["ENVIRONMENT_TITLE"]}",
baseUrl: "${process.env["ENVIRONMENT_URL"]}"
};
`;

fs.writeJson(manifestFilename, Object.assign(manifestBase, {
name: newName,
version: packageInfo.version,
applications: {
gecko: Object.assign(manifestBase.applications.gecko, {id: newId})
},
permissions: manifestBase.permissions.concat([process.env["ENVIRONMENT_URL"]])
})).then(() => {
fs.writeFile("current-environment.js", currentEnvContent).then(() => {
console.log("it is complete");
process.exit();
});
}).catch(err => {
console.error(err)
});

This file was deleted.

60 addon/bin/update-version 100755 → 100644
@@ -1,29 +1,31 @@
#!/usr/bin/env node

var fs = require('fs');
var util = require('util');
var childProcess = require('child_process');

// Load the current manifest.json
var manifestFilename = __dirname + '/../package.json';
var manifest = require(manifestFilename);

// Come up with a version tag suffix based on CIRCLE_TAG, or fall back to
// looking up the current git commit hash.
var tag;
if (process.env['CIRCLE_TAG']) {
tag = 'tag-' + process.env['CIRCLE_TAG'];
} else {
// Note: Must use short git hash, because AMO rejects versions > 32 chars
tag = 'dev-' + childProcess.execSync('git rev-parse --short HEAD')
.toString('utf-8')
.trim();
}

// Update the version tag suffix.
var versionParts = manifest.version.split('-');
manifest.version = versionParts[0] + '-' + tag;

// Write the modified manifest.json
var manifestJSON = JSON.stringify(manifest, null, ' ');
fs.writeFileSync(manifestFilename, manifestJSON);
#!/usr/bin/env node

const fs = require('fs');
const util = require('util');
const childProcess = require('child_process');

// Load the current package.json
const packageFilename = __dirname + '/../package.json';
const package = require(packageFilename);


// Come up with a version tag suffix based on CIRCLE_TAG, or fall back to
// looking up the current git commit hash.
const tag = 'v' + childProcess
.execSync('git rev-parse --short HEAD').toString('utf-8').trim()
.split('').sort().reverse().join('');

const semver = /^(\d+\.?){1,3}/.exec(package.version)[0];

// Update the version tag suffix.
package.version = semver + tag;

// Write the modified package.json
var packageJSON = JSON.stringify(package, null, ' ');
fs.writeFileSync(packageFilename, packageJSON);

var manifestFilename = __dirname + '/../manifest.json';
var manifest = require(manifestFilename);
manifest.version = package.version;
var manifestJSON = JSON.stringify(manifest, null, ' ');
fs.writeFileSync(manifestFilename, manifestJSON);
@@ -0,0 +1,7 @@

/* This file is generated by "bin/process-with-env" */
// eslint-disable-next-line no-unused-vars
const currentEnvironment = {
name: "local",
baseUrl: "https://example.com:8000/"
};
Binary file not shown.
BIN -461 Bytes addon/data/back.png
Binary file not shown.

This file was deleted.

BIN -651 Bytes addon/data/check.png
Binary file not shown.
BIN -485 Bytes addon/data/check@2x.png
Binary file not shown.
BIN -28.3 KB addon/data/copter.png
Binary file not shown.
Binary file not shown.
BIN -203 Bytes addon/data/dh-arrow.png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="context-fill" fill-opacity="context-fill-opacity" d="M29.924 14.338a3.52 3.52 0 0 1 1.909 2.6c.183 1.66-.954 3.768-3.02 5.7.96 2.962 2.054 7.362.062 7.362-1.449 0-3.36-2.1-4.841-4.158a21.843 21.843 0 0 1-7.069 2.037A21.694 21.694 0 0 1 7.194 26.7C5.832 28.446 4.245 30 3 30c-1.664 0-1.173-3.076-.407-5.816a6.147 6.147 0 0 1-2.52-3.748c-.249-2.264 2.1-4.138 5.878-5.493C6.143 9.9 8.743 2 16 2a8.73 8.73 0 0 1 7.733 4.7A49.053 49.053 0 0 1 31 6c1 0 1 0 1 1a24.16 24.16 0 0 1-2.076 7.338zM16 4.9c-4.423 0-6 4.1-6.865 9.3C8.838 15.981 8 18 14 18c7 0 8.974-3.218 8.859-3.856C22 9.392 20.4 4.9 16 4.9z"/></svg>
Binary file not shown.
Binary file not shown.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -0,0 +1,38 @@
{
"manifest_version": 2,
"name": "Test Pilot",
"author": "Mozilla (https://mozilla.org/)",
"applications": {
"gecko": {
"id": "@testpilot-addon",
"update_url": "https://testpilot.firefox.com/files/@testpilot-addon/updates.json"
}
},
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"64": "icons/icon-64.png",
"96": "icons/icon-96.png"
},
"description": "Test Pilot is a privacy-sensitive user research program focused on getting new features into Firefox faster.",
"homepage_url": "https://testpilot.firefox.com",
"permissions": [
"alarms",
"management",
"notifications",
"storage",
"cookies",
"tabs"
],
"background": {
"scripts": [
"current-environment.js",
"background.js"
]
},
"browser_action": {
"default_title": "Firefox Test Pilot",
"browser_style": true,
"default_icon": "icons/icon.svg"
}
}

Large diffs are not rendered by default.

@@ -1,70 +1,34 @@
{
"title": "Test Pilot",
"name": "testpilot-addon",
"version": "0.6.1",
"id": "@testpilot-addon",
"version": "3.0.6",
"private": true,
"description": "Test Pilot is a privacy-sensitive user research program focused on getting new features into Firefox faster.",
"repository": "mozilla/testpilot",
"homepage": "https://testpilot.firefox.com/",
"icon": "resource://@testpilot-addon/data/icon-96.png",
"bugs": {
"url": "https://github.com/mozilla/testpilot/issues"
},
"main": "index.js",
"author": "Mozilla (https://mozilla.org/)",
"engines": {
"firefox": ">=38.0a1",
"fennec": ">=38.0a1"
},
"updateURL": "https://testpilot.firefox.com/static/addon/update.rdf",
"updateLink": "https://testpilot.firefox.com/static/addon/addon.xpi",
"scripts": {
"start": "npm run watch",
"once": "jpm run -b beta --prefs dev-prefs.json",
"watch": "jpm watchpost --post-url http://localhost:8888",
"watch-ui": "watchify-server ui-test.js --index ui-test.html",
"lint": "eslint .",
"sign": "./bin/update-version && ./bin/sign",
"package": "jpm xpi && mv @testpilot-addon-$npm_package_version.xpi ../testpilot/frontend/static-src/addon/addon.xpi && mv @testpilot-addon-$npm_package_version.update.rdf ../testpilot/frontend/static-src/addon/update.rdf"
},
"license": "MPL-2.0",
"devDependencies": {
"babel-eslint": "4.1.8",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^0.0.8",
"jpm": "1.0.6",
"jsonwebtoken": "5.7.0",
"request": "2.69.0",
"watchify-server": "1.0.2"
"babel-eslint": "9.0.0",
"cross-env": "5.2.0",
"eslint": "5.7.0",
"eslint-plugin-import": "2.14.0",
"fs-extra": "7.0.0",
"onchange": "4.1.0",
"shx": "0.3.2",
"web-ext": "2.9.1"
},
"preferences": [
{
"name": "SERVER_ENVIRONMENT",
"title": "Environment",
"description": "Which Test Pilot server environment to use?",
"type": "menulist",
"value": "production",
"options": [
{
"value": "local",
"label": "Local (testpilot.dev)"
},
{
"value": "dev",
"label": "Dev (testpilot.dev.mozaws.net)"
},
{
"value": "stage",
"label": "Staging (testpilot.stage.mozaws.net)"
},
{
"value": "production",
"label": "Production (testpilot.firefox.com)"
}
]
}
],
"dependencies": {
"mustache": "2.2.1"
"scripts": {
"start": "onchange -p -v \"*.js\" -- npm run package",
"clean": "shx rm -rf web-ext-artifacts && rm -f current-environment.js && rm -f manifest.json",
"prepackage": "npm run clean && node bin/process-with-env && npm run lint && npm run lint:extension && npm run update-version",
"package": "cross-env web-ext build --ignore-files manifest-base.json package.json package-lock.json bin/update-version bin/process-with-env README.md LICENSE --overwrite-dest && shx mv web-ext-artifacts/test_pilot*.zip ./addon.xpi",
"lint": "eslint .",
"lint:extension": "web-ext lint --self-hosted",
"update-version": "node bin/update-version"
}
}

This file was deleted.

This file was deleted.

This file was deleted.

@@ -0,0 +1,19 @@
#!/usr/bin/env node
const YAML = require("yamljs");
const ContentTransformerPlugin = require(__dirname + "/../frontend/lib/content-transformer-plugin");
const buildJSON = require(__dirname + "/../frontend/lib/content/json");

const contentTransformer = new ContentTransformerPlugin({
inputs: {
experiments: "content-src/experiments/**/*.yaml",
news_updates: "content-src/news_updates.yaml"
},
parser: ({ file }) => YAML.parse(file.content.toString("utf8")),
transforms: [ buildJSON ]
});

contentTransformer.applyOnce('frontend/build').then(result => {
console.log("Assets output:");
Object.keys(result).forEach(key => console.log("\t", key));
});

@@ -0,0 +1,14 @@
#!/bin/bash

echo "$(dirname $0)"
cd $(dirname $0)/..

HASH=$(git --no-pager log --format=format:"%H" -1)
TAG=$(git show-ref --tags | awk "/$HASH/ {print \$NF}" | sed 's/refs.tags.//')

printf '{"commit":"%s","version":"%s","source":"https://github.com/mozilla/testpilot"}\n' \
"$HASH" \
"$TAG" \
> version.json

cat version.json
@@ -0,0 +1,4 @@
#!/bin/bash
set -ex
cd addon/
npm run package
@@ -0,0 +1,4 @@
#!/bin/bash
set -ex
NODE_ENV=production ENABLE_PONTOON=1 ENABLE_DEV_CONTENT=1 ENABLE_DEV_LOCALES=1 npm run static
zip -r frontend.zip frontend/build dist
@@ -0,0 +1,10 @@
#!/bin/bash
set -ex
printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' \
"$CIRCLE_SHA1" \
"$CIRCLE_TAG" \
"$CIRCLE_PROJECT_USERNAME" \
"$CIRCLE_PROJECT_REPONAME" \
"$CIRCLE_BUILD_URL" \
> version.json
cat version.json
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# https://github.com/bellkev/circle-lock-test/blob/master/do-exclusively

# sets $branch, $tag, $rest
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-b|--branch) branch="$2" ;;
-t|--tag) tag="$2" ;;
*) break ;;
esac
shift 2
done
rest=("$@")
}

# reads $branch, $tag, $commit_message
should_skip() {
if [[ "$branch" && "$CIRCLE_BRANCH" != "$branch" ]]; then
echo "Not on branch $branch. Skipping..."
return 0
fi

if [[ "$tag" && "$commit_message" != *\[$tag\]* ]]; then
echo "No [$tag] commit tag found. Skipping..."
return 0
fi

return 1
}

# reads $branch, $tag
# sets $jq_prog
make_jq_prog() {
local jq_filters=""

if [[ $branch ]]; then
jq_filters+=" and .branch == \"$branch\""
fi

if [[ $tag ]]; then
jq_filters+=" and (.subject | contains(\"[$tag]\"))"
fi

jq_prog=".[] | select(.build_num < $CIRCLE_BUILD_NUM and (.status | test(\"running|pending|queued\")) $jq_filters) | .build_num"
}


if [[ "$0" != *bats* ]]; then
set -e
set -u
set -o pipefail

branch=""
tag=""
rest=()
api_url="https://circleci.com/api/v1/project/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME?circle-token=$CIRCLE_TOKEN&limit=100"

parse_args "$@"
commit_message=$(git log -1 --pretty=%B)
if should_skip; then exit 0; fi
make_jq_prog

echo "Checking for running builds..."

while true; do
builds=$(curl -s -H "Accept: application/json" "$api_url" | jq "$jq_prog")
if [[ $builds ]]; then
echo "Waiting on builds:"
echo "$builds"
else
break
fi
echo "Retrying in 5 seconds..."
sleep 5
done

echo "Acquired lock"

if [[ "${#rest[@]}" -ne 0 ]]; then
"${rest[@]}"
fi
fi
@@ -0,0 +1,11 @@
#!/bin/bash
set -ex

# Skip builds for Pontoon commits
IS_PONTOON=$(git show -s --format=%s | grep -q 'Pontoon:' && echo 'true' || echo '')
if [[ $IS_PONTOON ]]; then
echo "Skipping Integration Tests on Pontoon commit.";
exit 0;
fi

tox -e flake8
@@ -0,0 +1,5 @@
#!/bin/bash
set -ex

npm install
cd addon && npm install
@@ -0,0 +1,34 @@
#!/bin/bash
set -ex

IS_PONTOON=$(git show -s --format=%s | grep -q 'Pontoon:' && echo 'true' || echo '')
if [[ $IS_PONTOON ]]; then
echo "Skipping Integration Tests on Pontoon commit.";
exit 0;
fi

GECKODRIVER_URL=$(
curl -s 'https://api.github.com/repos/mozilla/geckodriver/releases/latest' |
python -c "import sys, json; r = json.load(sys.stdin); print([a for a in r['assets'] if 'linux64' in a['name']][0]['browser_download_url']);"
);


curl -L -o geckodriver.tar.gz $GECKODRIVER_URL
gunzip -c geckodriver.tar.gz | tar xopf -
chmod +x geckodriver
sudo mv geckodriver /bin
geckodriver --version
# Install pip
sudo apt-get install python-pip python-dev build-essential
sudo pip install --upgrade pip

sudo pip install tox mozdownload mozinstall==1.15

mkdir -p ~/project/firefox-downloads/
find ~/project/firefox-downloads/ -type f -mtime +90 -delete
mozdownload --version latest --destination ~/project/firefox-downloads/firefox/
mozdownload --version latest-beta --destination ~/project/firefox-downloads/firefox_dev/
mozdownload --version latest --type daily --destination ~/project/firefox-downloads/firefox_nightly/

# Dependencies for firefox
sudo apt-get update && sudo apt-get install -y libgtk3.0-cil-dev libasound2 libasound2 libdbus-glib-1-2 libdbus-1-3
@@ -0,0 +1,14 @@
#!/bin/bash
set -ex

DISTRIBUTION_ID=$1

aws configure set preview.cloudfront true

INVALIDATION_ID=$(aws cloudfront create-invalidation \
--distribution-id $DISTRIBUTION_ID \
--paths '/*' | jq -r '.Invalidation.Id');

aws cloudfront wait invalidation-completed \
--distribution-id $DISTRIBUTION_ID \
--id $INVALIDATION_ID
@@ -0,0 +1,5 @@
#!/bin/bash
set -ex
cd addon/
npm run lint
npm run test
@@ -0,0 +1,16 @@
#!/bin/bash
set -ex

# Skip builds for Pontoon commits
IS_PONTOON=$(git show -s --format=%s | grep -q 'Pontoon:' && echo 'true' || echo '')
if [[ $IS_PONTOON ]]; then
echo "Skipping Integration Tests on Pontoon commit.";
exit 0;
fi

mozinstall $(ls -t /home/ubuntu/firefox-downloads/firefox_dev/*.tar.bz2 | head -1)
firefox --version
export PYTEST_ADDOPTS=--html=integration-test-results/ui-test-dev.html
export SKIP_INSTALL_TEST=True
npm start &
tox -e ui-tests
@@ -0,0 +1,16 @@
#!/bin/bash
set -ex
export PATH=~/project/firefox:$PATH

# Skip builds for Pontoon commits
IS_PONTOON=$(git show -s --format=%s | grep -q 'Pontoon:' && echo 'true' || echo '')
if [[ $IS_PONTOON ]]; then
echo "Skipping Integration Tests on Pontoon commit.";
exit 0;
fi

mozinstall $(ls -t firefox-downloads/firefox_nightly/*.tar.bz2 | head -1)
firefox --version
export PYTEST_ADDOPTS=--html=integration-test-results/ui-test-nightly.html
npm start &
tox -e ui-tests
@@ -0,0 +1,17 @@
#!/bin/bash
set -ex
export PATH=~/project/firefox:$PATH

# Skip builds for Pontoon commits
IS_PONTOON=$(git show -s --format=%s | grep -q 'Pontoon:' && echo 'true' || echo '')
if [[ $IS_PONTOON ]]; then
echo "Skipping Integration Tests on Pontoon commit.";
exit 0;
fi

mozinstall $(ls -t firefox-downloads/firefox/*.tar.bz2 | head -1)
firefox --version
export PYTEST_ADDOPTS=--html=integration-test-results/ui-test-release.html
export SKIP_INSTALL_TEST=True
npm start &
tox -e ui-tests
@@ -0,0 +1,7 @@
#!/bin/bash
set -ex

npm run lint
npm run l10n:check
npm run flow
npm run test:ci

This file was deleted.

@@ -0,0 +1,76 @@
#!/bin/bash
set -ex

STORYBOOK_BUCKET=${STORYBOOK_BUCKET:-testpilot-storybook.dev.mozaws.net}
if [ -z "$STORYBOOK_BUCKET" ]; then
echo "The S3 bucket is not set. Failing."
exit 1;
fi

# Skip builds for Pontoon commits
IS_PONTOON=$(git show -s --format=%s | grep -q 'Pontoon:' && echo 'true' || echo '')
if [[ $IS_PONTOON ]]; then
echo "Skipping Storybook deploy on Pontoon commit.";
exit 0;
fi

# Skip builds for non-Mozilla users
if [ "$CIRCLE_PROJECT_USERNAME" != "mozilla" ]; then
echo "Skipping Storybook deploy for non-Mozilla CircleCI";
exit 0;
fi

# Skip builds without credentials
if [[ -z "$AWS_ACCESS_KEY_ID" || -z "$AWS_SECRET_ACCESS_KEY" ]]; then
echo "Skipping Storybook deploy for missing credentials";
exit 0;
fi

# HACK: Build static storybook with URL paths edited to include git hash
HASH=$(git --no-pager log --format=format:"%H" -1)
cp -r .storybook .storybook-$HASH
sed -i "s#href=\"/static#href=\"/$HASH/static#g" .storybook-$HASH/preview-head.html
# HACK: correct paths to experiment images in existing build
sed -i "s#url(/static/#url(../#g" ./frontend/build/static/styles/experiments.css
./node_modules/.bin/build-storybook -c .storybook-$HASH -o ./frontend/storybook/$HASH
cp -r ./frontend/build/static ./frontend/storybook/$HASH

# Deploy the files to bucket with git hash subdirectory
aws s3 cp \
--recursive \
--acl "public-read" \
./frontend/storybook/${HASH} s3://${STORYBOOK_BUCKET}/${HASH}/

STORYBOOK_URL="http://$STORYBOOK_BUCKET/$HASH/"
echo "Deployed Storybook to $STORYBOOK_URL"

# Deploy a client-side redirect page for a per-PR URL
if [[ ! -z $CI_PULL_REQUEST ]]; then
STORYBOOK_PR_PATH=$(echo $CI_PULL_REQUEST | cut -d/ -f6-7);
STORYBOOK_PR_NUMBER=$(echo $CI_PULL_REQUEST | cut -d/ -f7);
STORYBOOK_PR_URL=http://${STORYBOOK_BUCKET}/${STORYBOOK_PR_PATH}/index.html
REDIRECT_HTML=$(cat <<EOF
<!DOCTYPE html>
<html>
<head>
<title>Storybook for ${STORYBOOK_PR_PATH}</title>
<meta http-equiv="refresh" content="0;URL='${STORYBOOK_URL}'" />
</head>
<body>
<p><a href="${STORYBOOK_PR_URL}">${STORYBOOK_URL}</a></p>
</body>
</html>
EOF
)
echo $REDIRECT_HTML | \
aws s3 cp --content-type "text/html" --acl "public-read" - \
s3://${STORYBOOK_BUCKET}/${STORYBOOK_PR_PATH}/index.html

echo "Deployed Storybook PR redirect to ${STORYBOOK_PR_URL}"

curl \
-H'Content-Type: application/json' \
-H"Authorization: token $STORYBOOK_GITHUB_ACCESS_TOKEN" \
--data "{\"body\":\"# Storybook Deployment\\nPull Request: $STORYBOOK_PR_URL\\nCommit: $STORYBOOK_URL\"}" \
https://api.github.com/repos/mozilla/testpilot/issues/${STORYBOOK_PR_NUMBER}/comments
fi
@@ -0,0 +1,164 @@
#!/bin/bash

# This file is used to deploy Test Pilot to an S3 bucket. It expects to be run
# from the root of the Test Pilot directory and you'll need your S3 bucket name
# in an environment variable $TESTPILOT_BUCKET
#
# It takes a single argument: If you're deploying to a dev instance, pass in
# "dev" and it will tweak the rules slightly:
#
# ./deploy.sh dev
#
# Questions? Hit up #testpilot on IRC

if [ ! -d "dist" ]; then
echo "Can't find /dist/ directory. Are you running from the Test Pilot root?"
exit 1
fi

if [ -z "$TESTPILOT_BUCKET" ]; then
echo "The S3 bucket is not set. Failing."
exit 1
fi

if [ "$1" = "dev" ]; then
DEST="dev"
elif [ "$1" = "stage" ]; then
DEST="stage"
fi


# The basic strategy is to sync all the files that need special attention
# first, and then sync everything else which will get defaults


# For short-lived assets; in seconds
TEN_MINUTES="600"

# For long-lived assets; in seconds
ONE_YEAR="31536000"

HPKP="\"Public-Key-Pins\": \"max-age=300;pin-sha256=\\\"WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=\\\";pin-sha256=\\\"r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=\\\";pin-sha256=\\\"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=\\\";pin-sha256=\\\"sRHdihwgkaib1P1gxX8HFszlD+7/gTfNvuAybgLPNis=\\\";\""

# HACK: If this is changed, be sure to update the CSP constant in frontend/lib/dev-server.js
CSP="\"content-security-policy\": \"default-src 'self'; connect-src 'self' https://sentry.prod.mozaws.net https://www.google-analytics.com https://ssl.google-analytics.com https://location.services.mozilla.com https://basket.mozilla.org; font-src 'self' https://code.cdn.mozilla.net; form-action 'none'; frame-ancestors 'self'; img-src 'self' https://ssl.google-analytics.com https://www.google-analytics.com; object-src 'none'; script-src 'self' https://ssl.google-analytics.com; style-src 'self' https://code.cdn.mozilla.net; report-uri /__cspreport__; frame-src https://www.youtube.com;\""
HSTS="\"strict-transport-security\": \"max-age=${ONE_YEAR}; includeSubDomains; preload\""
TYPE="\"x-content-type-options\": \"nosniff\""
XSS="\"x-xss-protection\": \"1; mode=block\""
ACAO="\"Access-Control-Allow-Origin\": \"*\""

# Our dev server has a couple different rules to allow easier debugging and
# enable localization. Also expires more often.
if [ "$DEST" = "dev" ]; then
TEN_MINUTES="15"
ONE_YEAR="15"

# HACK: If this is changed, be sure to update the CSP constant in frontend/lib/dev-server.js
CSP="\"content-security-policy\": \"default-src 'self'; connect-src 'self' https://sentry.prod.mozaws.net https://www.google-analytics.com https://ssl.google-analytics.com https://location.services.mozilla.com https://basket.mozilla.org; font-src 'self' https://code.cdn.mozilla.net; form-action 'none'; frame-ancestors 'self' https://pontoon.mozilla.org; img-src 'self' https://pontoon.mozilla.org https://ssl.google-analytics.com https://www.google-analytics.com; object-src 'none'; script-src 'self' https://pontoon.mozilla.org https://ssl.google-analytics.com; style-src 'self' https://pontoon.mozilla.org https://code.cdn.mozilla.net; report-uri /__cspreport__; frame-src https://www.youtube.com;\""
fi

# build version.json if it isn't provided
[ -e version.json ] || $(dirname $0)/build-version-json.sh

if [ -e version.json ]; then
mv version.json dist/__version__
# __version__ JSON; short cache
aws s3 cp \
--cache-control "max-age=${TEN_MINUTES}" \
--content-type "application/json" \
--metadata "{${ACAO}, ${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/__version__ s3://${TESTPILOT_BUCKET}/__version__
fi

# HTML; short cache
aws s3 sync \
--cache-control "max-age=${TEN_MINUTES}" \
--content-type "text/html" \
--exclude "*" \
--include "*.html" \
--metadata "{${HPKP}, ${CSP}, ${HSTS}, ${TYPE}, ${XSS}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# JSON; short cache
aws s3 sync \
--cache-control "max-age=${TEN_MINUTES}" \
--content-type "application/json" \
--exclude "*" \
--include "*.json" \
--metadata "{${ACAO}, ${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# XPI; short cache; amazon won't detect the content-type correctly
aws s3 sync \
--cache-control "max-age=${TEN_MINUTES}" \
--content-type "application/x-xpinstall" \
--exclude "*" \
--include "*.xpi" \
--metadata "{${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# RDF; short cache; amazon won't detect the content-type correctly
aws s3 sync \
--cache-control "max-age=${TEN_MINUTES}" \
--content-type "text/rdf" \
--exclude "*" \
--include "*.rdf" \
--metadata "{${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# l10n files; short cache;
aws s3 sync \
--cache-control "max-age=${TEN_MINUTES}" \
--exclude "*" \
--include "*.ftl" \
--metadata "{${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# SVG; cache forever, assign correct content-type
aws s3 sync \
--cache-control "max-age=${ONE_YEAR}, immutable" \
--content-type "image/svg+xml" \
--exclude "*" \
--include "*.svg" \
--metadata "{${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# Everything else; cache forever, because it has hashes in the filenames
aws s3 sync \
--delete \
--exclude "*.rdf" \
--exclude "*.xpi" \
--cache-control "max-age=${ONE_YEAR}, immutable" \
--metadata "{${HPKP}, ${HSTS}, ${TYPE}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
dist/ s3://${TESTPILOT_BUCKET}/

# HTML - `path/index.html` to `path` resources; short cache
for fn in $(find dist -name 'index.html' -not -path 'dist/index.html'); do
s3path=${fn#dist/}
s3path=${s3path%/index.html}
aws s3 cp \
--cache-control "max-age=${TEN_MINUTES}" \
--content-type "text/html" \
--exclude "*" \
--include "*.html" \
--metadata "{${HPKP}, ${CSP}, ${HSTS}, ${TYPE}, ${XSS}}" \
--metadata-directive "REPLACE" \
--acl "public-read" \
$fn s3://${TESTPILOT_BUCKET}/${s3path}
done
@@ -0,0 +1,4 @@
#!/usr/bin/env node
const { DevServer } = require(__dirname + '/../frontend/lib/dev-server');
const { PORT = 8000, STATIC_ROOT = 'frontend/build' } = process.env;
DevServer({ PORT, STATIC_ROOT });

This file was deleted.

@@ -0,0 +1,18 @@
#!/usr/bin/env node
const YAML = require("yamljs");
const ContentTransformerPlugin = require(__dirname + "/../frontend/lib/content-transformer-plugin");
const buildL10N = require(__dirname + "/../frontend/lib/content/l10n");

const contentTransformer = new ContentTransformerPlugin({
inputs: {
experiments: "content-src/experiments/**/*.yaml",
news_updates: "content-src/news_updates.yaml"
},
parser: ({ file }) => YAML.parse(file.content.toString("utf8")),
transforms: [ buildL10N(["."]) ]
});

contentTransformer.applyOnce('.').then(result => {
console.log("Assets output:");
Object.keys(result).forEach(key => console.log("\t", key));
});