From ad157cf1ad1f9a980a674a2a70e4351fe287e219 Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Mon, 13 Jul 2020 12:03:45 -0700 Subject: [PATCH 1/7] Update documentation --- README.md | 34 +++++++++++++++++++++++++--------- action.yml | 8 ++++---- src/validate.ts | 5 ++--- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0835ce3..f0ac0c2 100644 --- a/README.md +++ b/README.md @@ -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,32 @@ 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/action-release@v0.0.1 - with: - environment: 'production' - sourcemaps: './lib' +uses: getsentry/action-release@v1 +with: + environment: 'production' + sourcemaps: './lib' +``` + +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 }} ``` ## Troubleshooting +Building and running this action locally on unsupported environment yields an error that looks like: +``` +Syntax error: end of file unexpected (expecting ")") +``` +Suggestions and issues can be posted on the repositories [issues page](https://github.com/getsentry/action-release/issues). ## Contributing See the [Contributing Guide](https://github.com/getsentry/action-release/blob/master/CONTRIBUTING). diff --git a/action.yml b/action.yml index e0d2a4a..37f281b 100644 --- a/action.yml +++ b/action.yml @@ -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' diff --git a/src/validate.ts b/src/validate.ts index da3b0c0..ec5969e 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -17,7 +17,7 @@ export const getVersion = async (): Promise => { }; /** - * Environment is required. + * Get `environment`, a required parameter. * @throws * @returns string */ @@ -47,8 +47,7 @@ export const getStartedAt = (): number | null => { }; /** - * 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[] => { From 971c27cd9a458fb47e3c988fbb301560443f7fb4 Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Mon, 13 Jul 2020 12:04:09 -0700 Subject: [PATCH 2/7] Use a single call to getCLI --- src/main.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main.ts b/src/main.ts index 0841d75..a91bcaf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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`); From b6d28ec47a35117f6562f55104ca89edd38144af Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Mon, 13 Jul 2020 12:04:27 -0700 Subject: [PATCH 3/7] Validate started_at timestamp --- src/validate.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/validate.ts b/src/validate.ts index ec5969e..4d8ab41 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -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,11 +38,24 @@ 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; }; /** From 575b63a38a0eb4c65e95ce4326565aea42bfcb49 Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Mon, 13 Jul 2020 13:25:08 -0700 Subject: [PATCH 4/7] Fix typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f0ac0c2..b3d3535 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,12 @@ env: ``` ## Troubleshooting -Building and running this action locally on unsupported environment yields an error that looks like: +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 repositories [issues page](https://github.com/getsentry/action-release/issues). +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). From 89bc0c0b2c74c9d129399c7e7ebd825febcedaab Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Mon, 13 Jul 2020 14:51:07 -0700 Subject: [PATCH 5/7] Add tests --- __tests__/main.test.ts | 58 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index b10736d..f27a278 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -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 From 4501c2e316e4213afbf987f2caed480609cb2cab Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Tue, 14 Jul 2020 12:22:58 -0700 Subject: [PATCH 6/7] Bump version to 1.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed687f4..2d3b77e 100644 --- a/package.json +++ b/package.json @@ -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", From 1a8243b728492c9128adb0b64025e9630fd46c62 Mon Sep 17 00:00:00 2001 From: Marcos Gaeta Date: Tue, 14 Jul 2020 13:11:42 -0700 Subject: [PATCH 7/7] Add README note --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b3d3535..586d555 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ env: 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: ```