Skip to content

Commit cfefcd9

Browse files
authored
build: Automated release process (#297)
* Implemented semantic-release for automatic releases (release.yml and release.config.js) * Added test.yml action. Includes testing on multiple LTS node versions * Added deploy_s3.py based off internal deploy script * Replaced deprecated boto s3 library with AWS actively maintained boto3 * Updated CONTRIBUTING.md explaining commit conventions and release process * Added pull request template * Added semantic PR bot to check for PR titles (config is here) * Added secrets as env variables
1 parent 309dac3 commit cfefcd9

12 files changed

+4505
-50
lines changed

.github/pull_request_template.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!---
2+
Thanks for contributing to the Amplitude JavaScript SDK! 🎉
3+
4+
Please fill out the following sections to help us quickly review your pull request.
5+
--->
6+
### Summary
7+
8+
<!-- What does the PR do? -->
9+
10+
### Checklist
11+
12+
* [ ] Does your PR title have the correct [title format](../CONTRIBUTING.md#pr-commit-title-conventions)
13+
* [ ] Does your PR have a breaking change?

.github/semantic.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Validate the PR title, and ignore the commits
2+
titleOnly: true
3+
4+
# By default types specified in commitizen/conventional-commit-types is used.
5+
# See: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json
6+
# You can override the valid types
7+
8+
types:
9+
- feat
10+
- fix
11+
- perf
12+
- docs
13+
- test
14+
- refactor
15+
- style
16+
- build
17+
- ci
18+
- chore
19+
- revert

.github/workflows/release.yml

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
dryRun:
7+
description: 'Do a dry run instead of real release'
8+
required: true
9+
default: 'true'
10+
11+
jobs:
12+
authorize:
13+
name: Authorize
14+
runs-on: ubuntu-18.04
15+
steps:
16+
- name: ${{ github.actor }} permission check to do a release
17+
uses: octokit/[email protected]
18+
with:
19+
route: GET /repos/:repository/collaborators/${{ github.actor }}
20+
repository: ${{ github.repository }}
21+
env:
22+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
24+
release:
25+
name: Release
26+
runs-on: ubuntu-18.04
27+
needs: [authorize]
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@v1
31+
32+
- name: Configure AWS Credentials
33+
uses: aws-actions/configure-aws-credentials@v1
34+
with:
35+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
36+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
37+
aws-region: ${{ secrets.AWS_REGION }}
38+
39+
- name: Set up Python
40+
uses: actions/setup-python@v2
41+
with:
42+
python-version: '3.8.x'
43+
- name: Install boto3 for deployjs.python
44+
run: pip install boto3==1.14.63
45+
46+
- name: Setup Node.js
47+
uses: actions/setup-node@v1
48+
with:
49+
node-version: 10.x
50+
51+
- name: Install dependencies
52+
run: yarn install --frozen-lockfile
53+
54+
- name: Run tests
55+
run: make test
56+
57+
- name: Release --dry-run # Uses release.config.js
58+
if: ${{ github.event.inputs.dryRun == 'true'}}
59+
env:
60+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
62+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
63+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
64+
AWS_REGION: ${{ secrets.AWS_REGION }}
65+
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
66+
run: npx semantic-release --dry-run
67+
68+
- name: Release # Uses release.config.js
69+
if: ${{ github.event.inputs.dryRun == 'false'}}
70+
env:
71+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
73+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
74+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
75+
AWS_REGION: ${{ secrets.AWS_REGION }}
76+
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
77+
run: npx semantic-release

.github/workflows/test.yml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Test
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
strategy:
8+
fail-fast: false
9+
matrix:
10+
node-version: [10.x, 12.x, 14.x]
11+
# os: [macos-10.14, ubuntu-18.04] @TODO See if MacOS test can be fixed
12+
os: [ubuntu-18.04]
13+
runs-on: ${{ matrix.os }}
14+
15+
steps:
16+
- name: Check out Git repository
17+
uses: actions/checkout@v2
18+
19+
- name: Use Node.js ${{ matrix.node-version }}
20+
uses: actions/setup-node@v1
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
24+
- name: yarn install
25+
run: |
26+
yarn install --frozen-lockfile
27+
28+
- name: Build and run tests
29+
run: |
30+
make test

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
### As of September 21, 2020 CHANGELOG.md is no longer manually updated. Please check the [releases page](https://github.com/amplitude/Amplitude-JavaScript/releases) for up to date changes.
2+
13
### 7.1.1 (August 26, 2020)
24
* Fix an issue with detection of whether or not cookies are enabled on a device
35

CONTRIBUTING.md

+44
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Contributing to the Amplitude SDK for JavaScript
2+
23
🎉 Thanks for your interest in contributing! 🎉
34

45
## Ramping Up
6+
57
### Intro
8+
69
- There are three ways for SDK to be loaded
710
- Standard NPM package
811
- Snippet in `<script>` tag
@@ -11,6 +14,7 @@
1114
- Chek out the [Amplitude Instrumentation Explorer]((https://chrome.google.com/webstore/detail/amplitude-instrumentation/acehfjhnmhbmgkedjmjlobpgdicnhkbp)) to help logging events during development
1215

1316
### Architecture
17+
1418
- `index.js` is the main entrypoint of SDK
1519
- Stubbed methods are used when client imports via `<script>` snippet
1620
- Allows app to not be blocked while real JS SDK is loaded in
@@ -24,10 +28,50 @@
2428
- only applicable to snippet import
2529

2630
### Setting Up Development
31+
2732
- Cloning, installing, and building
2833
```
2934
git clone [email protected]:amplitude/Amplitude-JavaScript.git
3035
cd Amplitude-JavaScript
3136
make # Runs tests and generate builds
3237
yarn dev # Start development utility. Open localhost:9000 in your browser to access
3338
```
39+
40+
### PR Commit Title Conventions
41+
42+
PR titles should follow [conventional commit standards](https://www.conventionalcommits.org/en/v1.0.0/). A [probot app](https://github.com/zeke/semantic-pull-requests) checks for this when a PR is opened. This helps automate the [release](#release) process.
43+
44+
#### Commit Types ([related to release conditions](#release))
45+
46+
- **Special Case**: Any commit with `BREAKING CHANGES` in the body: Creates major release
47+
- `feat(<optional scope>)`: New features (minimum minor release)
48+
- `fix(<optional scope>)`: Bug fixes (minimum patch release)
49+
- `perf(<optional scope>)`: Performance improvement
50+
- `docs(<optional scope>)`: Documentation updates
51+
- `test(<optional scope>)`: Test updates
52+
- `refactor(<optional scope>)`: Code change that neither fixes a bug nor adds a feature
53+
- `style(<optional scope>)`: Code style changes (e.g. formatting, commas, semi-colons)
54+
- `build(<optional scope>)`: Changes that affect the build system or external dependencies (e.g. Yarn, Npm)
55+
- `ci(<optional scope>)`: Changes to our CI configuration files and scripts
56+
- `chore(<optional scope>)`: Other changes that don't modify src or test files
57+
- `revert(<optional scope>)`: Revert commit
58+
59+
### Release [Amplitude Internal]
60+
61+
Releases are managed by [semantic-release](https://github.com/semantic-release/semantic-release). It is a tool that will scan commits since the last release, determine the next [semantic version number](https://semver.org/), publish, and create changelogs.
62+
63+
#### Release Conditions [Amplitude Internal]
64+
65+
- `BREAKING CHANGES` in the body will do a major release
66+
```
67+
feat(cookies): Create new cookie format
68+
69+
BREAKING CHANGES: Breaks old cookie format
70+
```
71+
- Else `feat` in title will do a `minor` release
72+
`feat(cookies): some changes`
73+
- Else `fix` or `perf` in title will do a `patch` release
74+
`fix: null check bug`
75+
- Else no release
76+
`docs: update website`
77+

karma.conf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ module.exports = function(config) {
3737
'**/*.js': ['sourcemap']
3838
},
3939
frameworks: ['mocha', 'chai'],
40-
files: ['amplitude-snippet.min.js', 'build/snippet-tests.js', 'build/tests.js'],
40+
// files: ['amplitude-snippet.min.js', 'build/snippet-tests.js', 'build/tests.js'], @TODO: Fix flaky build/snippet-tests.js and re-enable
41+
files: ['amplitude-snippet.min.js', 'build/tests.js'],
4142
reporters: ['mocha', 'saucelabs'],
4243
port: 9876, // karma web server port
4344
colors: true,

package.json

+16-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
"analytics",
99
"amplitude"
1010
],
11-
"repository": "git://github.com/amplitude/amplitude-javascript.git",
11+
"repository": {
12+
"type": "git",
13+
"url": "https://github.com/amplitude/amplitude-javascript.git"
14+
},
1215
"main": "amplitude.umd.js",
1316
"react-native": "amplitude.native.js",
1417
"dependencies": {
@@ -23,6 +26,8 @@
2326
"@babel/plugin-transform-runtime": "^7.3.4",
2427
"@babel/preset-env": "^7.3.4",
2528
"@babel/runtime": "^7.3.4",
29+
"@semantic-release/exec": "^5.0.0",
30+
"@semantic-release/git": "^9.0.0",
2631
"chai": "^4.1.2",
2732
"date-fns": "^1.30.1",
2833
"express": "^4.16.2",
@@ -50,6 +55,7 @@
5055
"rollup-plugin-node-resolve": "^4.0.1",
5156
"rollup-plugin-replace": "^2.1.0",
5257
"rollup-plugin-uglify": "^6.0.2",
58+
"semantic-release": "^17.1.1",
5359
"sinon": "^7.0.0",
5460
"uglify-js": "^2.0.0"
5561
},
@@ -62,6 +68,14 @@
6268
"docs:build": "cd website/ && yarn build",
6369
"docs:serve": "cd website/ && yarn serve",
6470
"docs:deploy": "cd website/ && yarn deploy",
65-
"docs:swizzle": "cd website/ && yarn swizzle"
71+
"docs:swizzle": "cd website/ && yarn swizzle",
72+
"semantic-release": "semantic-release"
73+
},
74+
"bugs": {
75+
"url": "https://github.com/amplitude/amplitude-javascript/issues"
76+
},
77+
"homepage": "https://github.com/amplitude/amplitude-javascript#readme",
78+
"directories": {
79+
"test": "test"
6680
}
6781
}

release.config.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module.exports = {
2+
"branches": ["master"],
3+
"plugins": [
4+
["@semantic-release/commit-analyzer", {
5+
"preset": "angular",
6+
"parserOpts": {
7+
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES", "BREAKING"]
8+
}
9+
}],
10+
["@semantic-release/release-notes-generator", {
11+
"preset": "angular",
12+
}],
13+
["@semantic-release/npm", {
14+
"npmPublish": true,
15+
}],
16+
["@semantic-release/exec", {
17+
"prepareCmd": "make release",
18+
"publishCmd": "python scripts/deployjs.py --version ${nextRelease.version}",
19+
"failCmd": "npm unpublish amplitude-js@${nextRelease.version}"
20+
}],
21+
["@semantic-release/github", {
22+
"assets": "amplitude*.js"
23+
}],
24+
["@semantic-release/git", {
25+
"assets": ["package.json", "src/amplitude-snippet.js"],
26+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
27+
}],
28+
],
29+
}

scripts/deploy_s3.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import argparse
2+
import os
3+
import sys
4+
from boto3 import Session
5+
from botocore.exceptions import ClientError
6+
7+
unzipped_args = {
8+
'ContentType': 'application/javascript',
9+
'CacheControl': 'max-age=31536000',
10+
'ACL': 'public-read',
11+
}
12+
zipped_args = {
13+
'ContentType': 'application/javascript',
14+
'CacheControl': 'max-age=31536000',
15+
'ContentEncoding': 'gzip',
16+
'ACL': 'public-read',
17+
}
18+
19+
def check_exists(key):
20+
try:
21+
key.load()
22+
except ClientError as e:
23+
if e.response['Error']['Code'] == '404':
24+
return False
25+
else:
26+
return True
27+
28+
def upload(bucket, file, args):
29+
bucket.upload_file(
30+
os.path.join('dist', file),
31+
os.path.join('libs', file),
32+
ExtraArgs=args,
33+
)
34+
35+
def main():
36+
parser = argparse.ArgumentParser()
37+
parser.add_argument('--version', '-v', required=True,
38+
help='Version to deploy')
39+
args = parser.parse_args()
40+
s3 = Session(
41+
aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
42+
aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY'),
43+
region_name=os.environ.get('AWS_REGION'),
44+
).resource('s3')
45+
bucket = s3.Bucket(os.environ.get('S3_BUCKET_NAME'))
46+
47+
files = [
48+
f'amplitude-{args.version}.js',
49+
f'amplitude-{args.version}-min.js',
50+
f'amplitude-{args.version}.umd.js',
51+
f'amplitude-{args.version}-min.umd.js'
52+
]
53+
for file in files:
54+
if check_exists(s3.Object(os.environ.get('S3_BUCKET_NAME'), os.path.join('libs', file))):
55+
sys.exit(f'ERROR: {file} already exists and shouldn\'t be republished. Consider releasing a new version')
56+
print(f'Uploading {file}')
57+
upload(bucket, file, unzipped_args)
58+
59+
gz_files = [
60+
f'amplitude-{args.version}-min.gz.js',
61+
f'amplitude-{args.version}-min.umd.gz.js'
62+
]
63+
for file in gz_files:
64+
if check_exists(s3.Object(os.environ.get('S3_BUCKET_NAME'), file)):
65+
sys.exit(f'{file} already exists!')
66+
print(f'Uploading {file}')
67+
upload(bucket, file, zipped_args)
68+
69+
print(f'Success: S3 upload completed. Example: https://cdn.amplitude.com/libs/amplitude-{args.version}.js')
70+
return 0
71+
72+
if __name__ == '__main__':
73+
sys.exit(main())

src/amplitude-snippet.js

+2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
var amplitude = window.amplitude || {'_q':[],'_iq':{}};
99
var as = document.createElement('script');
1010
as.type = 'text/javascript';
11+
// Don't edit as.integrity, it is tracked by semantic-release-bot during releases
1112
as.integrity = 'sha384-+EOJUyXoWkQo2G0XTc+u2DOlZkrMUcc5yOqCuE2XHRnytSyqpFQSbgFZAlGmjpLI';
1213
as.crossOrigin = 'anonymous';
1314
as.async = true;
15+
// Don't edit as.src, it is tracked by semantic-release-bot during releases
1416
as.src = 'https://cdn.amplitude.com/libs/amplitude-7.1.1-min.gz.js';
1517
as.onload = function() {if(!window.amplitude.runQueuedFunctions) {console.log('[Amplitude] Error: could not load SDK');}};
1618
var s = document.getElementsByTagName('script')[0];

0 commit comments

Comments
 (0)