Skip to content

Commit

Permalink
feat: allow use of custom hostname
Browse files Browse the repository at this point in the history
fix #149
  • Loading branch information
Simon authored and lgaticaq committed May 14, 2020
1 parent e66c509 commit 057d5b0
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 51 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,20 @@ The plugin can be configured in the [**semantic-release** configuration file](ht

| Variable | Description |
| -------------------- | ----------------------------------------------------------------- |
| `SENTRY_AUTH_TOKEN` | Sentry token created in [profile](https://docs.sentry.io/api/auth/#id1) |
| `SENTRY_ORG` | Sentry organization name |
| `SENTRY_PROJECT` | Sentry project name |
| `SENTRY_AUTH_TOKEN` | The authentication token created in [profile](https://docs.sentry.io/api/auth/#id1) |
| `SENTRY_ORG` | The slug of the organization. |
| `SENTRY_PROJECT` | The slug of the project. |
| `SENTRY_URL` | The URL to use to connect to sentry. This defaults to https://sentry.io/. |
| `DEPLOY_START` | Sentry deploy start timestamp. Optional for deploy |
| `DEPLOY_END` | Sentry deploy end timestamp. Optional for deploy |

### Options

| Variable | Description |
| --------- | ----------------------------------------------------------------- |
| `project` | Sentry project name. Optional. Required if not present in environment variables |
| `org` | Sentry organization name. Optional. Required if not present in environment variables |
| `project` | The slug of the project. Optional. Required if not present in environment variables |
| `org` | The slug of the organization. Optional. Required if not present in environment variables |
| `url` | The URL to use to connect to sentry. Optional to set an on-premise instance |
| `repositoryUrl` | A valid url for add link to commits of new release. Optional. Ex: https://github.com/owner/repo |
| `tagsUrl` | A valid url for add link to new release. Optional. Ex: https://github.com/owner/repo/releases/tag/vx.y.z |
| `environment` | Sentry environment. Optional for deploy. Default production |
Expand Down
13 changes: 13 additions & 0 deletions src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ Please make sure to create an [sentry token](https://docs.sentry.io/api/auth/#id
)}), if defined, must be a valid url.`
})
],
[
'EINVALIDSENTRYURL',
/**
* @param {Context} ctx -
* @returns {SemanticReleaseError} -
*/
ctx => ({
message: 'Invalid sentry url.',
details: `The [url option](${linkify(
'README.md#options'
)}), if defined, must be a valid url.`
})
],
[
'EINVALIDSENTRYTOKEN',
/**
Expand Down
56 changes: 37 additions & 19 deletions src/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ const { createRelease, createDeploy } = require('./request')
* @property {SentryReleaseSuccessResponse} release
* @property {SentryDeploySuccessResponse} deploy
*/

/**
* @param {Config} pluginConfig -
* @param {Context} ctx -
* @returns {SentryDeployParams} -
* @example
* getDeployData(pluginConfig, ctx)
*/
const getDeployData = (pluginConfig, ctx) => {
/** @type {SentryDeployParams} */
const deployData = {
environment: pluginConfig.environment || 'production'
}
if (pluginConfig.deployName) {
deployData.name = pluginConfig.deployName
}
if (pluginConfig.deployUrl) {
deployData.url = pluginConfig.deployUrl
}
if (ctx.env.DEPLOY_START) {
deployData.dateStarted = ctx.env.DEPLOY_START
}
if (ctx.env.DEPLOY_END) {
deployData.dateStarted = ctx.env.DEPLOY_END
}
return deployData
}
/**
* @param {Config} pluginConfig -
* @param {Context} ctx -
Expand All @@ -26,7 +53,7 @@ const { createRelease, createDeploy } = require('./request')
*/
module.exports = async (pluginConfig, ctx) => {
try {
const url = pluginConfig.tagsUrl || ''
const tagsUrl = pluginConfig.tagsUrl || ''
const project = ctx.env.SENTRY_PROJECT || pluginConfig.project
/** @type {SentryReleaseParams} */
const releaseDate = {
Expand All @@ -46,16 +73,21 @@ module.exports = async (pluginConfig, ctx) => {
version: ctx.nextRelease.version,
projects: [project]
}
if (url !== '') releaseDate.url = `${url}/v${ctx.nextRelease.version}`
if (tagsUrl !== '') {
releaseDate.url = `${tagsUrl}/v${ctx.nextRelease.version}`
}
const org = ctx.env.SENTRY_ORG || pluginConfig.org
const url = ctx.env.SENTRY_URL || pluginConfig.url || 'https://sentry.io/'
ctx.logger.log('Creating release %s', ctx.nextRelease.version)
const release = await createRelease(
releaseDate,
ctx.env.SENTRY_AUTH_TOKEN,
org
org,
url
)
ctx.logger.log('Release created')
process.env.SENTRY_ORG = org
process.env.SENTRY_URL = url
process.env.SENTRY_PROJECT = project
const cli = new SentryCli()
if (pluginConfig.sourcemaps && pluginConfig.urlPrefix) {
Expand All @@ -68,27 +100,13 @@ module.exports = async (pluginConfig, ctx) => {
})
ctx.logger.log('Sourcemaps uploaded')
}
/** @type {SentryDeployParams} */
const deployData = {
environment: pluginConfig.environment || 'production'
}
if (pluginConfig.deployName) {
deployData.name = pluginConfig.deployName
}
if (pluginConfig.deployUrl) {
deployData.url = pluginConfig.deployUrl
}
if (ctx.env.DEPLOY_START) {
deployData.dateStarted = ctx.env.DEPLOY_START
}
if (ctx.env.DEPLOY_END) {
deployData.dateStarted = ctx.env.DEPLOY_END
}
const deployData = getDeployData(pluginConfig, ctx)
ctx.logger.log('Creating deploy for release %s', ctx.nextRelease.version)
const deploy = await createDeploy(
deployData,
ctx.env.SENTRY_AUTH_TOKEN,
org,
url,
ctx.nextRelease.version
)
ctx.logger.log('Deploy created')
Expand Down
37 changes: 24 additions & 13 deletions src/request.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const https = require('https')
const http = require('http')
const { URL } = require('url')

/** @typedef {string} PatchSetType */
/** @enum {PatchSetType} */
Expand Down Expand Up @@ -80,15 +82,17 @@ const TYPES = {
* @param {string} path -
* @param {*} data -
* @param {string} token -
* @param {string} url -
* @returns {Promise<*>} -
* @example
* await request(path, data, token)
*/
const request = (path, data, token) =>
const request = (path, data, token, url) =>
new Promise((resolve, reject) => {
const { hostname, protocol } = new URL(url)
const postData = JSON.stringify(data)
const options = {
hostname: 'sentry.io',
hostname,
path,
method: 'POST',
headers: {
Expand All @@ -97,7 +101,8 @@ const request = (path, data, token) =>
'Content-Length': Buffer.byteLength(postData)
}
}
const req = https.request(options, res => {
const client = protocol === 'http:' ? http : https
const req = client.request(options, res => {
if (res.statusCode !== 201) {
return reject(new Error(`Invalid status code: ${res.statusCode}`))
}
Expand Down Expand Up @@ -125,50 +130,56 @@ const request = (path, data, token) =>
* @param {SentryReleaseParams} data -
* @param {string} token -
* @param {string} org -
* @param {string} url -
* @returns {Promise<SentryReleaseSuccessResponse>} -
* @example
* await createRelease(data, token, org)
* await createRelease(data, token, org, url)
*/
const createRelease = (data, token, org) => {
return request(`/api/0/organizations/${org}/releases/`, data, token)
const createRelease = (data, token, org, url) => {
return request(`/api/0/organizations/${org}/releases/`, data, token, url)
}

/**
* @param {SentryDeployParams} data -
* @param {string} token -
* @param {string} org -
* @param {string} url -
* @param {string} version -
* @returns {Promise<SentryDeploySuccessResponse>} -
* @example
* await createDeploy(data, token, org, version)
* await createDeploy(data, token, org, url, version)
*/
const createDeploy = (data, token, org, version) => {
const createDeploy = (data, token, org, url, version) => {
return request(
`/api/0/organizations/${org}/releases/${version}/deploys/`,
data,
token
token,
url
)
}

/**
* @param {string} token -
* @param {string} org -
* @param {string} url -
* @returns {Promise<*>} -
* @example
* await verify(data, token, org)
* await verify(token, org, url)
*/
const verify = (token, org) =>
const verify = (token, org, url) =>
new Promise((resolve, reject) => {
const { hostname, protocol } = new URL(url)
const options = {
hostname: 'sentry.io',
hostname,
path: `/api/0/organizations/${org}/`,
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
const req = https.request(options, res => {
const client = protocol === 'http:' ? http : https
const req = client.request(options, res => {
if (res.statusCode !== 200) {
return reject(new Error(`Invalid status code: ${res.statusCode}`))
}
Expand Down
1 change: 1 addition & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Config {
deployName?: string
deployUrl?: string
org?: string
url?: string
project?: string
sourcemaps?: string
urlPrefix?: string
Expand Down
63 changes: 49 additions & 14 deletions src/verify.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,53 @@
const { URL } = require('url')
const AggregateError = require('aggregate-error')
const getError = require('./get-error')
const { verify } = require('./request')

/**
* @param {string} url -
* @returns {boolean} -
* @example
* isValidUrl(url)
*/
const isValidUrl = url => {
try {
// eslint-disable-next-line no-unused-vars
const { href } = new URL(url)
return true
} catch (err) {
return false
}
}

/**
* @param {Config} pluginConfig -
* @param {Context} ctx -
* @param {string} org -
* @param {string} url -
* @returns {Array<Error>} -
* @example
* getErrors(pluginConfig, ctx, org, url)
*/
const getErrors = (pluginConfig, ctx, org, url) => {
const errors = []
if (!ctx.env.SENTRY_AUTH_TOKEN) {
errors.push(getError('ENOSENTRYTOKEN', ctx))
}
if (!org) {
errors.push(getError('ENOSENTRYORG', ctx))
}
if (!isValidUrl(url)) {
errors.push(getError('EINVALIDSENTRYURL', ctx))
}
if (!ctx.env.SENTRY_PROJECT && !pluginConfig.project) {
errors.push(getError('ENOSENTRYPROJECT', ctx))
}
if (pluginConfig.tagsUrl && !isValidUrl(pluginConfig.tagsUrl)) {
errors.push(getError('EINVALIDTAGSURL', ctx))
}
return errors
}

/**
* @typedef {import('./types').Context} Context
* @typedef {import('./types').Config} Config
Expand All @@ -15,24 +61,13 @@ const { verify } = require('./request')
*/
module.exports = async (pluginConfig, ctx) => {
try {
const errors = []
if (!ctx.env.SENTRY_AUTH_TOKEN) {
errors.push(getError('ENOSENTRYTOKEN', ctx))
}
const org = ctx.env.SENTRY_ORG || pluginConfig.org
if (!org) {
errors.push(getError('ENOSENTRYORG', ctx))
}
if (!ctx.env.SENTRY_PROJECT && !pluginConfig.project) {
errors.push(getError('ENOSENTRYPROJECT', ctx))
}
if (pluginConfig.tagsUrl && !/http/.test(pluginConfig.tagsUrl)) {
errors.push(getError('EINVALIDTAGSURL', ctx))
}
const url = ctx.env.SENTRY_URL || pluginConfig.url || 'https://sentry.io/'
const errors = getErrors(pluginConfig, ctx, org, url)
if (errors.length > 0) {
throw new AggregateError(errors)
}
return await verify(ctx.env.SENTRY_AUTH_TOKEN, org)
return await verify(ctx.env.SENTRY_AUTH_TOKEN, org, url)
} catch (err) {
if (err instanceof AggregateError) {
throw err
Expand Down
16 changes: 16 additions & 0 deletions test/verify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,26 @@ describe('Verify', () => {
}
})

it('Return SemanticReleaseError if a SENTRY_URL environment variable is invalid', async () => {
try {
env.SENTRY_AUTH_TOKEN = 'valid'
env.SENTRY_ORG = 'valid'
env.SENTRY_PROJECT = 'project'
env.SENTRY_URL = 'invalid'
// @ts-ignore
await verify({}, { env })
} catch (errs) {
const err = errs._errors[0]
expect(err.name).to.equal('SemanticReleaseError')
expect(err.code).to.equal('EINVALIDSENTRYURL')
}
})

it('Verify alias from a custom environmen variable', async () => {
env.SENTRY_AUTH_TOKEN = 'valid'
env.SENTRY_ORG = 'valid'
env.SENTRY_PROJECT = 'project'
env.SENTRY_URL = SENTRY_HOST
// @ts-ignore
expect(await verify({}, { env })).to.be.a('undefined')
})
Expand Down

0 comments on commit 057d5b0

Please sign in to comment.