Merge pull request #5 from getsentry/feat/validate
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
# Sentry Release GitHub Action
|
||||
A release is a version of your code that is deployed to an environment. When you give Sentry information about your releases, you unlock a number of new features:
|
||||
Automatically create a Sentry Release in a workflow.
|
||||
|
||||
## Why Releases?
|
||||
A release is a version of your code that is deployed to an environment.
|
||||
When you give Sentry information about your releases, you unlock a number of new features:
|
||||
- Determine the issues and regressions introduced in a new release
|
||||
- Predict which commit caused an issue and who is likely responsible
|
||||
- Resolve issues by including the issue number in your commit message
|
||||
@@ -9,19 +12,34 @@ A release is a version of your code that is deployed to an environment. When you
|
||||
Additionally, releases are used for applying source maps to minified JavaScript to view original, untransformed source code.
|
||||
|
||||
## Usage
|
||||
|
||||
Adding the following to your workflow will create a new Sentry release for the
|
||||
`production` environment of your project and upload JavaScript source maps from
|
||||
the `./lib` directory.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
...
|
||||
- name: Sentry Release
|
||||
uses: getsentry/[email protected]
|
||||
with:
|
||||
environment: 'production'
|
||||
sourcemaps: './lib'
|
||||
uses: getsentry/action-release@v1
|
||||
with:
|
||||
environment: 'production'
|
||||
sourcemaps: './lib'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
Don’t forget to include the following environment variables:
|
||||
```yaml
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
```
|
||||
|
||||
See [action.yml](https://github.com/getsentry/action-release/blob/master/action.yml) for more workflow options.
|
||||
|
||||
## Troubleshooting
|
||||
Building and running this action locally on an unsupported environment yields an error that looks like:
|
||||
```
|
||||
Syntax error: end of file unexpected (expecting ")")
|
||||
```
|
||||
|
||||
Suggestions and issues can be posted on the repository's [issues page](https://github.com/getsentry/action-release/issues).
|
||||
|
||||
## Contributing
|
||||
See the [Contributing Guide](https://github.com/getsentry/action-release/blob/master/CONTRIBUTING).
|
||||
|
||||
+54
-4
@@ -1,19 +1,18 @@
|
||||
import {execSync} from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as process from 'process';
|
||||
import {getShouldFinalize} from '../src/validate';
|
||||
import {getShouldFinalize, getSourcemaps, getStartedAt} from '../src/validate'
|
||||
|
||||
describe('validate', () => {
|
||||
describe('getShouldFinalize', () => {
|
||||
const errorMessage = 'finalize is not a boolean';
|
||||
afterEach(() => {
|
||||
delete process.env['INPUT_FINALIZE'];
|
||||
});
|
||||
|
||||
test('should throw an error when finalize is invalid', async () => {
|
||||
process.env['INPUT_FINALIZE'] = 'error';
|
||||
expect(() => getShouldFinalize()).toThrow(
|
||||
'finalize is not a boolean'
|
||||
);
|
||||
expect(() => getShouldFinalize()).toThrow(errorMessage);
|
||||
});
|
||||
|
||||
test('should return true when finalize is omitted', async () => {
|
||||
@@ -25,6 +24,57 @@ describe('validate', () => {
|
||||
expect(getShouldFinalize()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSourcemaps', () => {
|
||||
afterEach(() => {
|
||||
delete process.env['INPUT_SOURCEMAPS'];
|
||||
});
|
||||
|
||||
test('should return empty list when sourcemaps is omitted', async () => {
|
||||
expect(getSourcemaps()).toEqual([]);
|
||||
});
|
||||
|
||||
test('should return array when sourcemaps is false', () => {
|
||||
process.env['INPUT_SOURCEMAPS'] = './lib';
|
||||
expect(getSourcemaps()).toEqual(['./lib']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStartedAt', () => {
|
||||
const errorMessage = 'started_at not in valid format. Unix timestamp or ISO 8601 date expected';
|
||||
afterEach(() => {
|
||||
delete process.env['INPUT_STARTED_AT'];
|
||||
});
|
||||
|
||||
test('should throw an error when started_at is negative', async () => {
|
||||
process.env['INPUT_STARTED_AT'] = '-1';
|
||||
expect(() => getStartedAt()).toThrow(errorMessage);
|
||||
});
|
||||
|
||||
test('should throw an error when started_at is invalid', async () => {
|
||||
process.env['INPUT_STARTED_AT'] = 'error';
|
||||
expect(() => getStartedAt()).toThrow(errorMessage);
|
||||
});
|
||||
|
||||
test('should return null when started_at is omitted', async () => {
|
||||
expect(getStartedAt()).toBeNull();
|
||||
});
|
||||
|
||||
test('should return an integer when started_at is an ISO8601 string.', async () => {
|
||||
process.env['INPUT_STARTED_AT'] = '2017-07-13T19:40:00';
|
||||
expect(getStartedAt()).toEqual(1500000000);
|
||||
});
|
||||
|
||||
test('should return an integer when started_at is an truncated ISO8601 string.', async () => {
|
||||
process.env['INPUT_STARTED_AT'] = '2017-07-13';
|
||||
expect(getStartedAt()).toEqual(1499904000);
|
||||
});
|
||||
|
||||
test('should return an integer when started_at is an integer.', async () => {
|
||||
process.env['INPUT_STARTED_AT'] = '1500000000';
|
||||
expect(getStartedAt()).toEqual(1500000000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// shows how the runner will run a javascript action with env / stdout protocol
|
||||
|
||||
+4
-4
@@ -3,16 +3,16 @@ description: 'GitHub Action for creating a release on Sentry'
|
||||
author: 'Sentry'
|
||||
inputs:
|
||||
environment:
|
||||
description: 'The name of the environment. E.g. production. TODO'
|
||||
description: 'Set the environment for this release. E.g. "production" or "staging".'
|
||||
required: true
|
||||
sourcemaps:
|
||||
description: 'Space-separated list of paths to JavaScript sourcemaps. Omit to skip _sending_ sourcemaps.'
|
||||
description: 'Space-separated list of paths to JavaScript sourcemaps. Omit to skip uploading sourcemaps.'
|
||||
required: false
|
||||
finalize:
|
||||
description: 'When false, omit setting the _TODO_ timestamp on the release.'
|
||||
description: 'When false, omit marking the release as finalized and released.'
|
||||
default: true
|
||||
started_at:
|
||||
description: 'Unix timestamp '
|
||||
description: 'Unix timestamp of the release start date.'
|
||||
required: false
|
||||
runs:
|
||||
using: 'docker'
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "action-release",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "GitHub Action for creating a release on Sentry",
|
||||
"main": "dist/index.js",
|
||||
|
||||
+7
-5
@@ -4,6 +4,8 @@ import * as validate from './validate';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const cli = getCLI();
|
||||
|
||||
// Validate parameters first so we can fail early.
|
||||
validate.checkEnvironmentVariables();
|
||||
const environment = validate.getEnvironment();
|
||||
@@ -14,25 +16,25 @@ import * as validate from './validate';
|
||||
const version = await validate.getVersion();
|
||||
|
||||
core.debug(`Version is ${version}`);
|
||||
await getCLI().new(version);
|
||||
await cli.new(version);
|
||||
|
||||
core.debug(`Setting commits`);
|
||||
await getCLI().setCommits(version, {auto: true});
|
||||
await cli.setCommits(version, {auto: true});
|
||||
|
||||
if (sourcemaps) {
|
||||
core.debug(`Adding sourcemaps`);
|
||||
await getCLI().uploadSourceMaps(version, {include: sourcemaps});
|
||||
await cli.uploadSourceMaps(version, {include: sourcemaps});
|
||||
}
|
||||
|
||||
core.debug(`Adding deploy to release`);
|
||||
await getCLI().newDeploy(version, {
|
||||
await cli.newDeploy(version, {
|
||||
env: environment,
|
||||
...(deployStartedAtOption && {started: deployStartedAtOption}),
|
||||
});
|
||||
|
||||
core.debug(`Finalizing the release`);
|
||||
if (shouldFinalize) {
|
||||
await getCLI().finalize(version);
|
||||
await cli.finalize(version);
|
||||
}
|
||||
|
||||
core.debug(`Done`);
|
||||
|
||||
+22
-11
@@ -17,7 +17,7 @@ export const getVersion = async (): Promise<string> => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Environment is required.
|
||||
* Get `environment`, a required parameter.
|
||||
* @throws
|
||||
* @returns string
|
||||
*/
|
||||
@@ -26,10 +26,9 @@ export const getEnvironment = (): string => {
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO I don't want to duplicate logic here. How should I validate the timestamp?
|
||||
* TODO must be a UNIX timestamp
|
||||
* TODO what if this is in the future?
|
||||
* TODO FIRST it could also be a datetime.
|
||||
* Optionally get a UNIX timestamp of when the deployment started.
|
||||
* Input timestamp may also be ISO 8601.
|
||||
*
|
||||
* @throws
|
||||
* @returns number
|
||||
*/
|
||||
@@ -39,16 +38,28 @@ export const getStartedAt = (): number | null => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startedAt = parseInt(startedAtOption);
|
||||
if (isNaN(startedAt)) {
|
||||
throw new Error('started_at is not a number');
|
||||
// In sentry-cli, we parse integer first.
|
||||
const startedAtTimestamp = parseInt(startedAtOption);
|
||||
const startedAt8601 = Math.floor(Date.parse(startedAtOption) / 1000);
|
||||
|
||||
let outputTimestamp;
|
||||
if (!isNaN(startedAtTimestamp)) {
|
||||
outputTimestamp = startedAtTimestamp;
|
||||
} else if (!isNaN(startedAt8601)) {
|
||||
outputTimestamp = startedAt8601;
|
||||
}
|
||||
return startedAt;
|
||||
|
||||
if (!outputTimestamp || outputTimestamp < 0) {
|
||||
throw new Error(
|
||||
'started_at not in valid format. Unix timestamp or ISO 8601 date expected'
|
||||
);
|
||||
}
|
||||
|
||||
return outputTimestamp;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO EXPLAIN FORMAT IN THE README
|
||||
* TODO handle failure
|
||||
* Source maps are optional, but there may be several as a space-separated list.
|
||||
* @returns string[]
|
||||
*/
|
||||
export const getSourcemaps = (): string[] => {
|
||||
|
||||
Reference in New Issue
Block a user