Compare commits

..

33 Commits

Author SHA1 Message Date
Jess Bees
44d4246912 Merge pull request #62 from actions/pages/get-build-conclusion
Add optional 'conclusion' argument to emitTelemetry
2022-09-16 09:33:36 -04:00
Jess Bees
5de668770e Gotta update npm deps to accurately compile it 2022-09-15 13:05:09 -04:00
Jess Bees
1c52af2b34 Add optional 'conclusion' argument to emitTelemetry
If present, will include the value of `conclusion` with the telemetry
POST body (gh/gh needs a separate PR to recognize this)
2022-09-15 12:14:23 -04:00
James M. Greene
c2379ec5e7 Merge pull request #61 from actions/upstream-preview-alpha
Add support for preview deployments internal alpha experiment 🧪
2022-09-13 19:58:43 -05:00
James M. Greene
e7d22decca Remove internal debugging from @actions/core 2022-09-09 18:54:00 -05:00
James M. Greene
9b78820d58 Update distributables 2022-09-09 18:51:52 -05:00
James M. Greene
dd23652c5c Adhere to Prettier formatting 2022-09-09 18:51:35 -05:00
James M. Greene
ec456f786c Correct outdated reference 2022-09-09 18:50:08 -05:00
James M. Greene
b68f047ef0 Merge branch 'main' into upstream-preview-alpha 2022-09-09 18:49:17 -05:00
James M. Greene
791c72a9c0 Merge pull request #60 from actions/yaml-consistency
Use single quotes in Action metadata for consistency
2022-09-09 18:48:39 -05:00
James M. Greene
b45768bc0f Merge branch 'main' into yaml-consistency 2022-09-09 18:47:11 -05:00
James M. Greene
3c12eff351 Merge pull request #59 from actions/fix-org-refs
Correct organization references
2022-09-09 18:46:55 -05:00
James M. Greene
f5fafd81c2 Remove '.only' from the new unit test 2022-09-09 18:46:36 -05:00
James M. Greene
f8578825e2 Update distributables 2022-09-09 18:44:20 -05:00
James M. Greene
d2d7f752d5 Add support for preview deployments 2022-09-09 18:44:03 -05:00
James M. Greene
ad109d8dbd Add failing test 2022-09-09 18:29:17 -05:00
James M. Greene
3ed1d6d9a8 Use single quotes in Action metadata for consistency 2022-09-09 17:09:19 -05:00
James M. Greene
71efac92ad Add 'preview' input parameter to Action metadata 2022-09-09 17:08:16 -05:00
James M. Greene
b742641b90 Correct organization references 2022-09-09 16:59:57 -05:00
Yoann Chaudet
e4de7aa8e4 Merge pull request #57 from actions/release-instructions
Add release instructions
2022-08-24 11:24:11 -07:00
Yoann Chaudet
84346747a3 v1 2022-08-24 10:55:10 -07:00
Yoann Chaudet
9113cc7efc fix lint 2022-08-24 10:54:29 -07:00
Yoann Chaudet
858592354e Add release instructions 2022-08-24 10:53:23 -07:00
Yoann Chaudet
114f9cc1f8 Merge pull request #56 from actions/cancel
Explicitly handle cancellation on errors
2022-08-24 10:46:21 -07:00
Yoann Chaudet
0dfe0f0668 Merge pull request #55 from actions/formatter
Bring back the formatter
2022-08-24 08:59:01 -07:00
Yoann Chaudet
c7c77bb21c Explicitly handle cancellation on errors 2022-08-23 21:32:40 -07:00
Yoann Chaudet
369d1aed92 Add linter 2022-08-23 17:28:44 -07:00
Yoann Chaudet
4865e84b02 Bring back the formatter 2022-08-23 17:19:21 -07:00
James M. Greene
b627026553 Merge pull request #54 from actions/codeowners-file
Add CODEOWNERS file
2022-08-19 13:29:24 -05:00
James M. Greene
9bd912c0ca Add CODEOWNERS file 2022-08-19 13:28:07 -05:00
James M. Greene
ed034ea1c3 Merge pull request #53 from actions/consistent-release-workflow
Make the major version release workflow consistent with other Actions
2022-08-19 13:27:01 -05:00
James M. Greene
8f9a38d5e7 Update major version only for full releases 2022-08-19 12:33:44 -05:00
James M. Greene
71b1669225 Make the major version release workflow consistent with other Actions
Based on https://github.com/paper-spa/configure-pages/edit/main/.github/workflows/release.yml
2022-08-19 12:26:27 -05:00
21 changed files with 875 additions and 623 deletions

View File

@@ -11,7 +11,7 @@
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
"ecmaVersion": 2020
},
"rules": {
"semi": ["error", "never"]

2
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,2 @@
# Default PR reviewers
* @actions/pages

View File

@@ -1,38 +1,40 @@
---
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
name-template: "v$RESOLVED_VERSION"
tag-template: "v$RESOLVED_VERSION"
template: |
# Changelog
$CHANGES
See details of [all code changes](https://github.com/actions/deploy-pages/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION) since previous release
See details of [all code changes](https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION) since previous release.
categories:
- title: '🚀 Features'
- title: "🚀 Features"
labels:
- 'feature'
- 'enhancement'
- title: '🐛 Bug Fixes'
- "feature"
- "enhancement"
- title: "🐛 Bug Fixes"
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 Maintenance'
- "fix"
- "bugfix"
- "bug"
- title: "🧰 Maintenance"
labels:
- 'infrastructure'
- 'automation'
- 'documentation'
- title: '🏎 Performance'
label: 'performance'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
- "infrastructure"
- "automation"
- "documentation"
- title: "🏎 Performance"
label: "performance"
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
version-resolver:
major:
labels:
- 'type: breaking'
- "type: breaking"
minor:
labels:
- 'type: enhancement'
- "type: enhancement"
patch:
labels:
- 'type: bug'
- 'type: maintenance'
- 'type: documentation'
- "type: bug"
- "type: maintenance"
- "type: documentation"
default: patch

34
.github/workflows/check-formatting.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Checking formatting
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.JS
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Verify formatting
run: npm run format:check

34
.github/workflows/check-linter.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Checking linter
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.JS
uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Verify linter
run: npm run lint

View File

@@ -1,28 +1,30 @@
name: Release new action version
name: Release
on:
release:
types: [edited]
types: [released]
workflow_dispatch:
inputs:
TAG_NAME:
description: 'Tag name that the major tag will point to'
description: "Tag name that the major tag will point to"
required: true
env:
TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
permissions:
contents: write
jobs:
update_tag:
name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes
environment:
name: releaseNewActionVersion
runs-on: ubuntu-latest
environment:
# Note: this environment is protected
name: Release
steps:
- name: Update the ${{ env.TAG_NAME }} tag
id: update-major-tag
uses: actions/publish-action@v0.1.0
with:
source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
- name: Update the ${{ env.TAG_NAME }} tag
id: update-major-tag
uses: actions/publish-action@v0.1.0
with:
source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -1,10 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid"
}

10
.prettierrc.yml Normal file
View File

@@ -0,0 +1,10 @@
# Prettier (formatter) configuration
---
printWidth: 120
tabWidth: 2
useTabs: false
semi: false
singleQuote: true
trailingComma: none
bracketSpacing: true
arrowParens: avoid

View File

@@ -62,6 +62,16 @@ There are a few important considerations to be aware of:
5. If your Pages site is using GitHub Actions as the source, while not required we highly recommend you also [protect your environment][environment-protection] (we do it by default for you)
# Release instructions
In order to release a new version of this Action:
1. Publish the draft release from the `main` branch with semantic version as the tag name, _with_ the checkbox to publish to the GitHub Marketplace checked. :ballot_box_with_check:
2. After publishing the release, the [`release` workflow][release] will automatically run to create/update the corresponding the major version tag such as `v1`.
⚠️ Environment approval is required. Check the [Release workflow run list][release-workflow-runs].
## License
The scripts and documentation in this project are released under the [MIT License](LICENSE).
@@ -71,3 +81,5 @@ The scripts and documentation in this project are released under the [MIT Licens
[upload-pages-artifact]: https://github.com/actions/upload-pages-artifact
[artifacts]: https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts
[environment-protection]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-protection-rules
[release]: .github/workflows/release.yml
[release-workflow-runs]: https://github.com/actions/deploy-pages/actions/workflows/release.yml

View File

@@ -8,7 +8,10 @@ inputs:
emit_telemetry:
description: 'Should this action only emit build telemetry instead of deploying the build artifact?'
required: false
default: "false"
default: 'false'
conclusion:
description: 'The status of the previous build.'
required: false
token:
description: 'GitHub token'
default: ${{ github.token }}
@@ -16,19 +19,23 @@ inputs:
timeout:
description: 'Time in milliseconds after which to timeout and cancel the deployment (default: 10 minutes)'
required: false
default: "600000"
default: '600000'
error_count:
description: 'Maximum number of status report errors before cancelling a deployment (default: 10)'
required: false
default: "10"
default: '10'
reporting_interval:
description: 'Time in milliseconds between two deployment status report (default: 5 seconds)'
required: false
default: "5000"
default: '5000'
artifact_name:
description: 'Name of the artifact to deploy'
required: false
default: "github-pages"
default: 'github-pages'
preview:
description: 'Is this attempting to deploy a pull request as a GitHub Pages preview site? (NOTE: This feature is only in alpha currently and is not available to the public!)'
required: false
default: 'false'
outputs:
page_url:
description: 'URL to deployed GitHub Pages'

409
dist/index.js vendored
View File

@@ -8177,7 +8177,8 @@ function getRequiredVars() {
actionsId: process.env.GITHUB_ACTION,
githubToken: core.getInput('token'),
githubApiUrl: process.env.GITHUB_API_URL ?? 'https://api.github.com',
artifactName: core.getInput('artifact_name') ?? 'github-pages'
artifactName: core.getInput('artifact_name') ?? 'github-pages',
isPreview: core.getInput('preview') === 'true'
}
}
@@ -8207,184 +8208,231 @@ const axios = __nccwpck_require__(6545)
const getContext = __nccwpck_require__(1319)
const errorStatus = {
'unknown_status' : 'Unable to get deployment status.',
'not_found' : 'Deployment not found.',
'deployment_attempt_error' : 'Deployment temporarily failed, a retry will be automatically scheduled...'
unknown_status: 'Unable to get deployment status.',
not_found: 'Deployment not found.',
deployment_attempt_error: 'Deployment temporarily failed, a retry will be automatically scheduled...'
}
class Deployment {
constructor() {
const context = getContext()
this.runTimeUrl = context.runTimeUrl
this.repositoryNwo = context.repositoryNwo
this.runTimeToken = context.runTimeToken
this.buildVersion = context.buildVersion
this.buildActor = context.buildActor
this.actionsId = context.workflowRun
this.githubToken = context.githubToken
this.workflowRun = context.workflowRun
this.requestedDeployment = false
this.deploymentInfo = null
this.githubApiUrl = context.githubApiUrl
this.artifactName = context.artifactName
}
constructor() {
const context = getContext()
this.runTimeUrl = context.runTimeUrl
this.repositoryNwo = context.repositoryNwo
this.runTimeToken = context.runTimeToken
this.buildVersion = context.buildVersion
this.buildActor = context.buildActor
this.actionsId = context.workflowRun
this.githubToken = context.githubToken
this.workflowRun = context.workflowRun
this.requestedDeployment = false
this.deploymentInfo = null
this.githubApiUrl = context.githubApiUrl
this.artifactName = context.artifactName
this.isPreview = context.isPreview === true
}
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
try {
core.info(`Actor: ${this.buildActor}`)
core.info(`Action ID: ${this.actionsId}`)
const pagesDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment`
const artifactExgUrl = `${this.runTimeUrl}_apis/pipelines/workflows/${this.workflowRun}/artifacts?api-version=6.0-preview`
core.info(`Artifact URL: ${artifactExgUrl}`)
const {data} = await axios.get(artifactExgUrl, {
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
try {
core.info(`Actor: ${this.buildActor}`)
core.info(`Action ID: ${this.actionsId}`)
const pagesDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment`
const artifactExgUrl = `${this.runTimeUrl}_apis/pipelines/workflows/${this.workflowRun}/artifacts?api-version=6.0-preview`
core.info(`Artifact URL: ${artifactExgUrl}`)
const { data } = await axios.get(artifactExgUrl, {
headers: {
Authorization: `Bearer ${this.runTimeToken}`,
'Content-Type': 'application/json'
}
})
core.info(JSON.stringify(data))
const artifactRawUrl = data?.value?.find(artifact => artifact.name === this.artifactName)?.url
if (!artifactRawUrl) {
throw new Error(
'No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.'
)
}
const artifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
const payload = {
artifact_url: artifactUrl,
pages_build_version: this.buildVersion,
oidc_token: idToken
}
if (this.isPreview === true) {
payload.preview = true
}
core.info(`Creating deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
const response = await axios.post(pagesDeployEndpoint, payload, {
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${this.githubToken}`,
'Content-type': 'application/json'
}
})
this.requestedDeployment = true
core.info(`Created deployment for ${this.buildVersion}`)
if (response && response.data) {
core.info(JSON.stringify(response.data))
this.deploymentInfo = response.data
}
} catch (error) {
core.info(error.stack)
// output raw error in debug mode.
core.debug(JSON.stringify(error))
// build customized error message based on server response
if (error.response) {
let errorMessage = `Failed to create deployment (status: ${error.response.status}) with build version ${this.buildVersion}. `
if (error.response.status == 400) {
let message = ''
if (error.response.data && error.response.data.message) {
message = error.response.data.message
} else {
message = error.response.data
}
errorMessage += `Responded with: ${message}`
} else if (error.response.status == 403) {
errorMessage += `Ensure GITHUB_TOKEN has permission "pages: write".`
} else if (error.response.status == 404) {
errorMessage += `Ensure GitHub Pages has been enabled.`
} else if (error.response.status >= 500) {
errorMessage += `Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
}
throw errorMessage
} else {
throw error
}
}
}
// Poll the deployment endpoint for status
async check() {
try {
const statusUrl =
this.deploymentInfo != null
? this.deploymentInfo['status_url']
: `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/status/${this.buildVersion}`
let pageUrl = this.deploymentInfo != null ? this.deploymentInfo['page_url'] : ''
const previewUrl = this.deploymentInfo != null ? this.deploymentInfo['preview_url'] : ''
if (this.isPreview && previewUrl) {
pageUrl = previewUrl
}
core.setOutput('page_url', pageUrl)
const timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
var startTime = Date.now()
var errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
var errorReportingInterval = 0
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// Handle reporting interval
await new Promise(r => setTimeout(r, reportingInterval + errorReportingInterval))
// Check status
var res = await axios.get(statusUrl, {
headers: {
Authorization: `Bearer ${this.runTimeToken}`,
'Content-Type': 'application/json'
Authorization: `token ${this.githubToken}`
}
})
core.info(JSON.stringify(data))
const artifactRawUrl = data?.value?.find(artifact => artifact.name === this.artifactName)?.url
if (!artifactRawUrl) {
throw new Error('No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.')
if (res.data.status == 'succeed') {
core.info('Reported success!')
core.setOutput('status', 'succeed')
break
} else if (res.data.status == 'deployment_failed') {
// Fall into permanent error, it may be caused by ongoing incident or malicious deployment content or exhausted automatic retry times.
core.setFailed('Deployment failed, try again later.')
break
} else if (res.data.status == 'deployment_content_failed') {
// The uploaded artifact is invalid.
core.setFailed(
'Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.'
)
break
} else if (errorStatus[res.data.status]) {
// A temporary error happened, will query the status again
core.info(errorStatus[res.data.status])
} else {
core.info('Current status: ' + res.data.status)
}
const artifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
const payload = {
artifact_url: artifactUrl,
pages_build_version: this.buildVersion,
oidc_token: idToken
if (res.status != 200 || !!errorStatus[res.data.status]) {
errorCount++
// set the Maximum error reporting interval greater than 15 sec but below 30 sec.
if (errorReportingInterval < 1000 * 15) {
errorReportingInterval = (errorReportingInterval << 1) | 1
}
} else {
// reset the error reporting interval once get the proper status back.
errorReportingInterval = 0
}
core.info(`Creating deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
const response = await axios.post(pagesDeployEndpoint, payload, {
if (errorCount >= maxErrorCount) {
core.info('Too many errors, aborting!')
core.setFailed('Failed with status code: ' + res.status)
// Explicitly cancel the deployment
await this.cancel()
return
}
// Handle timeout
if (Date.now() - startTime >= timeout) {
core.info('Timeout reached, aborting!')
core.setFailed('Timeout reached, aborting!')
// Explicitly cancel the deployment
await this.cancel()
return
}
}
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
}
}
async cancel() {
// Don't attemp to cancel if no deployment was created
if (!this.requestedDeployment) {
return
}
// Cancel the deployment
try {
const pagesCancelDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/cancel/${this.buildVersion}`
await axios.put(
pagesCancelDeployEndpoint,
{},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${this.githubToken}`,
'Content-type': 'application/json'
}
})
this.requestedDeployment = true
core.info(`Created deployment for ${this.buildVersion}`)
if (response && response.data) {
core.info(JSON.stringify(response.data))
this.deploymentInfo = response.data
}
} catch (error) {
core.info(error.stack)
// output raw error in debug mode.
core.debug(JSON.stringify(error))
// build customized error message based on server response
if (error.response) {
let errorMessage = `Failed to create deployment (status: ${error.response.status}) with build version ${this.buildVersion}. `
if (error.response.status == 400) {
let message = ""
if (error.response.data && error.response.data.message) {
message = error.response.data.message
} else {
message = error.response.data
}
errorMessage += `Responded with: ${message}`
}
else if (error.response.status == 403) {
errorMessage += `Ensure GITHUB_TOKEN has permission "pages: write".`
} else if (error.response.status == 404) {
errorMessage += `Ensure GitHub Pages has been enabled.`
}
else if (error.response.status >= 500) {
errorMessage += `Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
}
throw errorMessage
} else {
throw error
}
}
}
// Poll the deployment endpoint for status
async check() {
try {
const statusUrl = this.deploymentInfo != null ?
this.deploymentInfo["status_url"] :
`${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/status/${process.env['GITHUB_SHA']}`
core.setOutput('page_url', this.deploymentInfo != null ? this.deploymentInfo["page_url"] : "")
const timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
var startTime = Date.now()
var errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
var errorReportingInterval = 0
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// Handle reporting interval
await new Promise(r => setTimeout(r, reportingInterval + errorReportingInterval))
// Check status
var res = await axios.get(statusUrl, {
headers: {
Authorization: `token ${this.githubToken}`
}
})
if (res.data.status == 'succeed') {
core.info('Reported success!')
core.setOutput('status', 'succeed')
break
} else if (res.data.status == 'deployment_failed') {
// Fall into permanent error, it may be caused by ongoing incident or malicious deployment content or exhausted automatic retry times.
core.setFailed('Deployment failed, try again later.')
break
} else if(res.data.status == 'deployment_content_failed') {
// The uploaded artifact is invalid.
core.setFailed('Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.')
break
} else if (errorStatus[res.data.status]) {
// A temporary error happened, will query the status again
core.info(errorStatus[res.data.status])
} else {
core.info('Current status: ' + res.data.status)
}
if (res.status != 200 || !!errorStatus[res.data.status]) {
errorCount++
// set the Maximum error reporting interval greater than 15 sec but below 30 sec.
if (errorReportingInterval < 1000 * 15) {
errorReportingInterval = errorReportingInterval << 1 | 1
}
} else {
// reset the error reporting interval once get the proper status back.
errorReportingInterval = 0
}
if (errorCount >= maxErrorCount) {
core.info('Too many errors, aborting!')
core.setFailed('Failed with status code: ' + res.status)
break
}
// Handle timeout
if (Date.now() - startTime >= timeout) {
core.info('Timeout reached, aborting!')
core.setFailed('Timeout reached, aborting!')
return
}
}
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
)
core.info(`Deployment cancelled with ${pagesCancelDeployEndpoint}`)
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
}
}
module.exports = {Deployment}
}
module.exports = { Deployment }
/***/ }),
@@ -8409,17 +8457,18 @@ axiosRetry(axios, {
}
})
const {Deployment} = __nccwpck_require__(2877)
const { Deployment } = __nccwpck_require__(2877)
async function emitTelemetry() {
// All variables we need from the runtime are set in the Deployment constructor
const deployment = new Deployment()
const telemetryUrl = `${deployment.githubApiUrl}/repos/${deployment.repositoryNwo}/pages/telemetry`
core.info(`Sending telemetry for run id ${deployment.workflowRun}`)
const conclusion = core.getInput('conclusion') || null
core.info(`Sending telemetry for run id ${deployment.workflowRun}: ${conclusion}`)
await axios
.post(
telemetryUrl,
{github_run_id: deployment.workflowRun},
{ github_run_id: deployment.workflowRun, conclusion },
{
headers: {
Accept: 'application/vnd.github.v3+json',
@@ -8447,7 +8496,7 @@ async function main() {
main()
module.exports = {emitTelemetry}
module.exports = { emitTelemetry }
/***/ }),
@@ -8621,39 +8670,17 @@ __nccwpck_require__(4307)
const core = __nccwpck_require__(2186)
// const github = require('@actions/github'); // TODO: Not used until we publish API endpoint to the @action/github package
const axios = __nccwpck_require__(6545)
const {Deployment} = __nccwpck_require__(2877)
const { Deployment } = __nccwpck_require__(2877)
const deployment = new Deployment()
// TODO: If the artifact hasn't been created, we can create it and upload to artifact storage ourselves
// const tar = require('tar')
async function cancelHandler(evtOrExitCodeOrError) {
try {
if (deployment.requestedDeployment) {
const pagesCancelDeployEndpoint = `${deployment.githubApiUrl}/repos/${process.env.GITHUB_REPOSITORY}/pages/deployment/cancel/${process.env.GITHUB_SHA}`
await axios.put(
pagesCancelDeployEndpoint,
{},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${deployment.githubToken}`,
'Content-type': 'application/json'
}
}
)
core.info(`Deployment cancelled with ${pagesCancelDeployEndpoint}`)
}
} catch (e) {
console.log('Deployment cancellation failed', e)
}
await deployment.cancel()
process.exit(isNaN(+evtOrExitCodeOrError) ? 1 : +evtOrExitCodeOrError)
}
async function main() {
let idToken = ""
let idToken = ''
try {
idToken = await core.getIDToken()
} catch (error) {
@@ -8674,8 +8701,8 @@ process.on('SIGINT', cancelHandler)
process.on('SIGTERM', cancelHandler)
// Main
const emitTelemetry = core.getInput("emit_telemetry")
if (emitTelemetry === "true") {
const emitTelemetry = core.getInput('emit_telemetry')
if (emitTelemetry === 'true') {
__nccwpck_require__(9557)
} else {
main()

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@@ -27,11 +27,13 @@
"lint": "eslint src",
"prepare": "ncc build src/index.js -o dist --source-map --license licenses.txt && ncc build src/pre.js -o pre --source-map --license licenses.txt",
"test": "jest",
"all": "npm run lint && npm run prepare && npm run test"
"format": "prettier --write 'src/**/*.js'",
"format:check": "prettier --check 'src/**/*.js'",
"all": "npm run format && npm run lint && npm run prepare && npm run test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/paper-spa/deploy-pages.git"
"url": "git+https://github.com/actions/deploy-pages.git"
},
"keywords": [
"GitHub",
@@ -40,7 +42,7 @@
"author": "GitHub",
"license": "MIT",
"bugs": {
"url": "https://github.com/paper-spa/deploy-pages/issues"
"url": "https://github.com/actions/deploy-pages/issues"
},
"homepage": "https://github.com/paper-spa/deploy-pages#readme"
"homepage": "https://github.com/actions/deploy-pages#readme"
}

View File

@@ -8177,7 +8177,8 @@ function getRequiredVars() {
actionsId: process.env.GITHUB_ACTION,
githubToken: core.getInput('token'),
githubApiUrl: process.env.GITHUB_API_URL ?? 'https://api.github.com',
artifactName: core.getInput('artifact_name') ?? 'github-pages'
artifactName: core.getInput('artifact_name') ?? 'github-pages',
isPreview: core.getInput('preview') === 'true'
}
}
@@ -8207,184 +8208,231 @@ const axios = __nccwpck_require__(6545)
const getContext = __nccwpck_require__(1319)
const errorStatus = {
'unknown_status' : 'Unable to get deployment status.',
'not_found' : 'Deployment not found.',
'deployment_attempt_error' : 'Deployment temporarily failed, a retry will be automatically scheduled...'
unknown_status: 'Unable to get deployment status.',
not_found: 'Deployment not found.',
deployment_attempt_error: 'Deployment temporarily failed, a retry will be automatically scheduled...'
}
class Deployment {
constructor() {
const context = getContext()
this.runTimeUrl = context.runTimeUrl
this.repositoryNwo = context.repositoryNwo
this.runTimeToken = context.runTimeToken
this.buildVersion = context.buildVersion
this.buildActor = context.buildActor
this.actionsId = context.workflowRun
this.githubToken = context.githubToken
this.workflowRun = context.workflowRun
this.requestedDeployment = false
this.deploymentInfo = null
this.githubApiUrl = context.githubApiUrl
this.artifactName = context.artifactName
}
constructor() {
const context = getContext()
this.runTimeUrl = context.runTimeUrl
this.repositoryNwo = context.repositoryNwo
this.runTimeToken = context.runTimeToken
this.buildVersion = context.buildVersion
this.buildActor = context.buildActor
this.actionsId = context.workflowRun
this.githubToken = context.githubToken
this.workflowRun = context.workflowRun
this.requestedDeployment = false
this.deploymentInfo = null
this.githubApiUrl = context.githubApiUrl
this.artifactName = context.artifactName
this.isPreview = context.isPreview === true
}
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
try {
core.info(`Actor: ${this.buildActor}`)
core.info(`Action ID: ${this.actionsId}`)
const pagesDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment`
const artifactExgUrl = `${this.runTimeUrl}_apis/pipelines/workflows/${this.workflowRun}/artifacts?api-version=6.0-preview`
core.info(`Artifact URL: ${artifactExgUrl}`)
const {data} = await axios.get(artifactExgUrl, {
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
try {
core.info(`Actor: ${this.buildActor}`)
core.info(`Action ID: ${this.actionsId}`)
const pagesDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment`
const artifactExgUrl = `${this.runTimeUrl}_apis/pipelines/workflows/${this.workflowRun}/artifacts?api-version=6.0-preview`
core.info(`Artifact URL: ${artifactExgUrl}`)
const { data } = await axios.get(artifactExgUrl, {
headers: {
Authorization: `Bearer ${this.runTimeToken}`,
'Content-Type': 'application/json'
}
})
core.info(JSON.stringify(data))
const artifactRawUrl = data?.value?.find(artifact => artifact.name === this.artifactName)?.url
if (!artifactRawUrl) {
throw new Error(
'No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.'
)
}
const artifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
const payload = {
artifact_url: artifactUrl,
pages_build_version: this.buildVersion,
oidc_token: idToken
}
if (this.isPreview === true) {
payload.preview = true
}
core.info(`Creating deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
const response = await axios.post(pagesDeployEndpoint, payload, {
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${this.githubToken}`,
'Content-type': 'application/json'
}
})
this.requestedDeployment = true
core.info(`Created deployment for ${this.buildVersion}`)
if (response && response.data) {
core.info(JSON.stringify(response.data))
this.deploymentInfo = response.data
}
} catch (error) {
core.info(error.stack)
// output raw error in debug mode.
core.debug(JSON.stringify(error))
// build customized error message based on server response
if (error.response) {
let errorMessage = `Failed to create deployment (status: ${error.response.status}) with build version ${this.buildVersion}. `
if (error.response.status == 400) {
let message = ''
if (error.response.data && error.response.data.message) {
message = error.response.data.message
} else {
message = error.response.data
}
errorMessage += `Responded with: ${message}`
} else if (error.response.status == 403) {
errorMessage += `Ensure GITHUB_TOKEN has permission "pages: write".`
} else if (error.response.status == 404) {
errorMessage += `Ensure GitHub Pages has been enabled.`
} else if (error.response.status >= 500) {
errorMessage += `Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
}
throw errorMessage
} else {
throw error
}
}
}
// Poll the deployment endpoint for status
async check() {
try {
const statusUrl =
this.deploymentInfo != null
? this.deploymentInfo['status_url']
: `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/status/${this.buildVersion}`
let pageUrl = this.deploymentInfo != null ? this.deploymentInfo['page_url'] : ''
const previewUrl = this.deploymentInfo != null ? this.deploymentInfo['preview_url'] : ''
if (this.isPreview && previewUrl) {
pageUrl = previewUrl
}
core.setOutput('page_url', pageUrl)
const timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
var startTime = Date.now()
var errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
var errorReportingInterval = 0
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// Handle reporting interval
await new Promise(r => setTimeout(r, reportingInterval + errorReportingInterval))
// Check status
var res = await axios.get(statusUrl, {
headers: {
Authorization: `Bearer ${this.runTimeToken}`,
'Content-Type': 'application/json'
Authorization: `token ${this.githubToken}`
}
})
core.info(JSON.stringify(data))
const artifactRawUrl = data?.value?.find(artifact => artifact.name === this.artifactName)?.url
if (!artifactRawUrl) {
throw new Error('No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.')
if (res.data.status == 'succeed') {
core.info('Reported success!')
core.setOutput('status', 'succeed')
break
} else if (res.data.status == 'deployment_failed') {
// Fall into permanent error, it may be caused by ongoing incident or malicious deployment content or exhausted automatic retry times.
core.setFailed('Deployment failed, try again later.')
break
} else if (res.data.status == 'deployment_content_failed') {
// The uploaded artifact is invalid.
core.setFailed(
'Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.'
)
break
} else if (errorStatus[res.data.status]) {
// A temporary error happened, will query the status again
core.info(errorStatus[res.data.status])
} else {
core.info('Current status: ' + res.data.status)
}
const artifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
const payload = {
artifact_url: artifactUrl,
pages_build_version: this.buildVersion,
oidc_token: idToken
if (res.status != 200 || !!errorStatus[res.data.status]) {
errorCount++
// set the Maximum error reporting interval greater than 15 sec but below 30 sec.
if (errorReportingInterval < 1000 * 15) {
errorReportingInterval = (errorReportingInterval << 1) | 1
}
} else {
// reset the error reporting interval once get the proper status back.
errorReportingInterval = 0
}
core.info(`Creating deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
const response = await axios.post(pagesDeployEndpoint, payload, {
if (errorCount >= maxErrorCount) {
core.info('Too many errors, aborting!')
core.setFailed('Failed with status code: ' + res.status)
// Explicitly cancel the deployment
await this.cancel()
return
}
// Handle timeout
if (Date.now() - startTime >= timeout) {
core.info('Timeout reached, aborting!')
core.setFailed('Timeout reached, aborting!')
// Explicitly cancel the deployment
await this.cancel()
return
}
}
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
}
}
async cancel() {
// Don't attemp to cancel if no deployment was created
if (!this.requestedDeployment) {
return
}
// Cancel the deployment
try {
const pagesCancelDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/cancel/${this.buildVersion}`
await axios.put(
pagesCancelDeployEndpoint,
{},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${this.githubToken}`,
'Content-type': 'application/json'
}
})
this.requestedDeployment = true
core.info(`Created deployment for ${this.buildVersion}`)
if (response && response.data) {
core.info(JSON.stringify(response.data))
this.deploymentInfo = response.data
}
} catch (error) {
core.info(error.stack)
// output raw error in debug mode.
core.debug(JSON.stringify(error))
// build customized error message based on server response
if (error.response) {
let errorMessage = `Failed to create deployment (status: ${error.response.status}) with build version ${this.buildVersion}. `
if (error.response.status == 400) {
let message = ""
if (error.response.data && error.response.data.message) {
message = error.response.data.message
} else {
message = error.response.data
}
errorMessage += `Responded with: ${message}`
}
else if (error.response.status == 403) {
errorMessage += `Ensure GITHUB_TOKEN has permission "pages: write".`
} else if (error.response.status == 404) {
errorMessage += `Ensure GitHub Pages has been enabled.`
}
else if (error.response.status >= 500) {
errorMessage += `Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
}
throw errorMessage
} else {
throw error
}
}
}
// Poll the deployment endpoint for status
async check() {
try {
const statusUrl = this.deploymentInfo != null ?
this.deploymentInfo["status_url"] :
`${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/status/${process.env['GITHUB_SHA']}`
core.setOutput('page_url', this.deploymentInfo != null ? this.deploymentInfo["page_url"] : "")
const timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
var startTime = Date.now()
var errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
var errorReportingInterval = 0
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// Handle reporting interval
await new Promise(r => setTimeout(r, reportingInterval + errorReportingInterval))
// Check status
var res = await axios.get(statusUrl, {
headers: {
Authorization: `token ${this.githubToken}`
}
})
if (res.data.status == 'succeed') {
core.info('Reported success!')
core.setOutput('status', 'succeed')
break
} else if (res.data.status == 'deployment_failed') {
// Fall into permanent error, it may be caused by ongoing incident or malicious deployment content or exhausted automatic retry times.
core.setFailed('Deployment failed, try again later.')
break
} else if(res.data.status == 'deployment_content_failed') {
// The uploaded artifact is invalid.
core.setFailed('Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.')
break
} else if (errorStatus[res.data.status]) {
// A temporary error happened, will query the status again
core.info(errorStatus[res.data.status])
} else {
core.info('Current status: ' + res.data.status)
}
if (res.status != 200 || !!errorStatus[res.data.status]) {
errorCount++
// set the Maximum error reporting interval greater than 15 sec but below 30 sec.
if (errorReportingInterval < 1000 * 15) {
errorReportingInterval = errorReportingInterval << 1 | 1
}
} else {
// reset the error reporting interval once get the proper status back.
errorReportingInterval = 0
}
if (errorCount >= maxErrorCount) {
core.info('Too many errors, aborting!')
core.setFailed('Failed with status code: ' + res.status)
break
}
// Handle timeout
if (Date.now() - startTime >= timeout) {
core.info('Timeout reached, aborting!')
core.setFailed('Timeout reached, aborting!')
return
}
}
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
)
core.info(`Deployment cancelled with ${pagesCancelDeployEndpoint}`)
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
}
}
module.exports = {Deployment}
}
module.exports = { Deployment }
/***/ }),
@@ -8409,17 +8457,18 @@ axiosRetry(axios, {
}
})
const {Deployment} = __nccwpck_require__(2877)
const { Deployment } = __nccwpck_require__(2877)
async function emitTelemetry() {
// All variables we need from the runtime are set in the Deployment constructor
const deployment = new Deployment()
const telemetryUrl = `${deployment.githubApiUrl}/repos/${deployment.repositoryNwo}/pages/telemetry`
core.info(`Sending telemetry for run id ${deployment.workflowRun}`)
const conclusion = core.getInput('conclusion') || null
core.info(`Sending telemetry for run id ${deployment.workflowRun}: ${conclusion}`)
await axios
.post(
telemetryUrl,
{github_run_id: deployment.workflowRun},
{ github_run_id: deployment.workflowRun, conclusion },
{
headers: {
Accept: 'application/vnd.github.v3+json',
@@ -8447,7 +8496,7 @@ async function main() {
main()
module.exports = {emitTelemetry}
module.exports = { emitTelemetry }
/***/ }),

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,8 @@ function getRequiredVars() {
actionsId: process.env.GITHUB_ACTION,
githubToken: core.getInput('token'),
githubApiUrl: process.env.GITHUB_API_URL ?? 'https://api.github.com',
artifactName: core.getInput('artifact_name') ?? 'github-pages'
artifactName: core.getInput('artifact_name') ?? 'github-pages',
isPreview: core.getInput('preview') === 'true'
}
}

View File

@@ -7,181 +7,227 @@ const axios = require('axios')
const getContext = require('./context')
const errorStatus = {
'unknown_status' : 'Unable to get deployment status.',
'not_found' : 'Deployment not found.',
'deployment_attempt_error' : 'Deployment temporarily failed, a retry will be automatically scheduled...'
unknown_status: 'Unable to get deployment status.',
not_found: 'Deployment not found.',
deployment_attempt_error: 'Deployment temporarily failed, a retry will be automatically scheduled...'
}
class Deployment {
constructor() {
const context = getContext()
this.runTimeUrl = context.runTimeUrl
this.repositoryNwo = context.repositoryNwo
this.runTimeToken = context.runTimeToken
this.buildVersion = context.buildVersion
this.buildActor = context.buildActor
this.actionsId = context.workflowRun
this.githubToken = context.githubToken
this.workflowRun = context.workflowRun
this.requestedDeployment = false
this.deploymentInfo = null
this.githubApiUrl = context.githubApiUrl
this.artifactName = context.artifactName
}
constructor() {
const context = getContext()
this.runTimeUrl = context.runTimeUrl
this.repositoryNwo = context.repositoryNwo
this.runTimeToken = context.runTimeToken
this.buildVersion = context.buildVersion
this.buildActor = context.buildActor
this.actionsId = context.workflowRun
this.githubToken = context.githubToken
this.workflowRun = context.workflowRun
this.requestedDeployment = false
this.deploymentInfo = null
this.githubApiUrl = context.githubApiUrl
this.artifactName = context.artifactName
this.isPreview = context.isPreview === true
}
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
try {
core.info(`Actor: ${this.buildActor}`)
core.info(`Action ID: ${this.actionsId}`)
const pagesDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment`
const artifactExgUrl = `${this.runTimeUrl}_apis/pipelines/workflows/${this.workflowRun}/artifacts?api-version=6.0-preview`
core.info(`Artifact URL: ${artifactExgUrl}`)
const {data} = await axios.get(artifactExgUrl, {
// Ask the runtime for the unsigned artifact URL and deploy to GitHub Pages
// by creating a deployment with that artifact
async create(idToken) {
try {
core.info(`Actor: ${this.buildActor}`)
core.info(`Action ID: ${this.actionsId}`)
const pagesDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment`
const artifactExgUrl = `${this.runTimeUrl}_apis/pipelines/workflows/${this.workflowRun}/artifacts?api-version=6.0-preview`
core.info(`Artifact URL: ${artifactExgUrl}`)
const { data } = await axios.get(artifactExgUrl, {
headers: {
Authorization: `Bearer ${this.runTimeToken}`,
'Content-Type': 'application/json'
}
})
core.info(JSON.stringify(data))
const artifactRawUrl = data?.value?.find(artifact => artifact.name === this.artifactName)?.url
if (!artifactRawUrl) {
throw new Error(
'No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.'
)
}
const artifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
const payload = {
artifact_url: artifactUrl,
pages_build_version: this.buildVersion,
oidc_token: idToken
}
if (this.isPreview === true) {
payload.preview = true
}
core.info(`Creating deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
const response = await axios.post(pagesDeployEndpoint, payload, {
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${this.githubToken}`,
'Content-type': 'application/json'
}
})
this.requestedDeployment = true
core.info(`Created deployment for ${this.buildVersion}`)
if (response && response.data) {
core.info(JSON.stringify(response.data))
this.deploymentInfo = response.data
}
} catch (error) {
core.info(error.stack)
// output raw error in debug mode.
core.debug(JSON.stringify(error))
// build customized error message based on server response
if (error.response) {
let errorMessage = `Failed to create deployment (status: ${error.response.status}) with build version ${this.buildVersion}. `
if (error.response.status == 400) {
let message = ''
if (error.response.data && error.response.data.message) {
message = error.response.data.message
} else {
message = error.response.data
}
errorMessage += `Responded with: ${message}`
} else if (error.response.status == 403) {
errorMessage += `Ensure GITHUB_TOKEN has permission "pages: write".`
} else if (error.response.status == 404) {
errorMessage += `Ensure GitHub Pages has been enabled.`
} else if (error.response.status >= 500) {
errorMessage += `Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
}
throw errorMessage
} else {
throw error
}
}
}
// Poll the deployment endpoint for status
async check() {
try {
const statusUrl =
this.deploymentInfo != null
? this.deploymentInfo['status_url']
: `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/status/${this.buildVersion}`
let pageUrl = this.deploymentInfo != null ? this.deploymentInfo['page_url'] : ''
const previewUrl = this.deploymentInfo != null ? this.deploymentInfo['preview_url'] : ''
if (this.isPreview && previewUrl) {
pageUrl = previewUrl
}
core.setOutput('page_url', pageUrl)
const timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
var startTime = Date.now()
var errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
var errorReportingInterval = 0
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// Handle reporting interval
await new Promise(r => setTimeout(r, reportingInterval + errorReportingInterval))
// Check status
var res = await axios.get(statusUrl, {
headers: {
Authorization: `Bearer ${this.runTimeToken}`,
'Content-Type': 'application/json'
Authorization: `token ${this.githubToken}`
}
})
core.info(JSON.stringify(data))
const artifactRawUrl = data?.value?.find(artifact => artifact.name === this.artifactName)?.url
if (!artifactRawUrl) {
throw new Error('No uploaded artifact was found! Please check if there are any errors at build step, or uploaded artifact name is correct.')
if (res.data.status == 'succeed') {
core.info('Reported success!')
core.setOutput('status', 'succeed')
break
} else if (res.data.status == 'deployment_failed') {
// Fall into permanent error, it may be caused by ongoing incident or malicious deployment content or exhausted automatic retry times.
core.setFailed('Deployment failed, try again later.')
break
} else if (res.data.status == 'deployment_content_failed') {
// The uploaded artifact is invalid.
core.setFailed(
'Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.'
)
break
} else if (errorStatus[res.data.status]) {
// A temporary error happened, will query the status again
core.info(errorStatus[res.data.status])
} else {
core.info('Current status: ' + res.data.status)
}
const artifactUrl = `${artifactRawUrl}&%24expand=SignedContent`
const payload = {
artifact_url: artifactUrl,
pages_build_version: this.buildVersion,
oidc_token: idToken
if (res.status != 200 || !!errorStatus[res.data.status]) {
errorCount++
// set the Maximum error reporting interval greater than 15 sec but below 30 sec.
if (errorReportingInterval < 1000 * 15) {
errorReportingInterval = (errorReportingInterval << 1) | 1
}
} else {
// reset the error reporting interval once get the proper status back.
errorReportingInterval = 0
}
core.info(`Creating deployment with payload:\n${JSON.stringify(payload, null, '\t')}`)
const response = await axios.post(pagesDeployEndpoint, payload, {
if (errorCount >= maxErrorCount) {
core.info('Too many errors, aborting!')
core.setFailed('Failed with status code: ' + res.status)
// Explicitly cancel the deployment
await this.cancel()
return
}
// Handle timeout
if (Date.now() - startTime >= timeout) {
core.info('Timeout reached, aborting!')
core.setFailed('Timeout reached, aborting!')
// Explicitly cancel the deployment
await this.cancel()
return
}
}
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
}
}
async cancel() {
// Don't attemp to cancel if no deployment was created
if (!this.requestedDeployment) {
return
}
// Cancel the deployment
try {
const pagesCancelDeployEndpoint = `${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/cancel/${this.buildVersion}`
await axios.put(
pagesCancelDeployEndpoint,
{},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${this.githubToken}`,
'Content-type': 'application/json'
}
})
this.requestedDeployment = true
core.info(`Created deployment for ${this.buildVersion}`)
if (response && response.data) {
core.info(JSON.stringify(response.data))
this.deploymentInfo = response.data
}
} catch (error) {
core.info(error.stack)
// output raw error in debug mode.
core.debug(JSON.stringify(error))
// build customized error message based on server response
if (error.response) {
let errorMessage = `Failed to create deployment (status: ${error.response.status}) with build version ${this.buildVersion}. `
if (error.response.status == 400) {
let message = ""
if (error.response.data && error.response.data.message) {
message = error.response.data.message
} else {
message = error.response.data
}
errorMessage += `Responded with: ${message}`
}
else if (error.response.status == 403) {
errorMessage += `Ensure GITHUB_TOKEN has permission "pages: write".`
} else if (error.response.status == 404) {
errorMessage += `Ensure GitHub Pages has been enabled.`
}
else if (error.response.status >= 500) {
errorMessage += `Server error, is githubstatus.com reporting a Pages outage? Please re-run the deployment at a later time.`
}
throw errorMessage
} else {
throw error
}
}
}
// Poll the deployment endpoint for status
async check() {
try {
const statusUrl = this.deploymentInfo != null ?
this.deploymentInfo["status_url"] :
`${this.githubApiUrl}/repos/${this.repositoryNwo}/pages/deployment/status/${process.env['GITHUB_SHA']}`
core.setOutput('page_url', this.deploymentInfo != null ? this.deploymentInfo["page_url"] : "")
const timeout = Number(core.getInput('timeout'))
const reportingInterval = Number(core.getInput('reporting_interval'))
const maxErrorCount = Number(core.getInput('error_count'))
var startTime = Date.now()
var errorCount = 0
// Time in milliseconds between two deployment status report when status errored, default 0.
var errorReportingInterval = 0
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
// Handle reporting interval
await new Promise(r => setTimeout(r, reportingInterval + errorReportingInterval))
// Check status
var res = await axios.get(statusUrl, {
headers: {
Authorization: `token ${this.githubToken}`
}
})
if (res.data.status == 'succeed') {
core.info('Reported success!')
core.setOutput('status', 'succeed')
break
} else if (res.data.status == 'deployment_failed') {
// Fall into permanent error, it may be caused by ongoing incident or malicious deployment content or exhausted automatic retry times.
core.setFailed('Deployment failed, try again later.')
break
} else if(res.data.status == 'deployment_content_failed') {
// The uploaded artifact is invalid.
core.setFailed('Artifact could not be deployed. Please ensure the content does not contain any hard links, symlinks and total size is less than 10GB.')
break
} else if (errorStatus[res.data.status]) {
// A temporary error happened, will query the status again
core.info(errorStatus[res.data.status])
} else {
core.info('Current status: ' + res.data.status)
}
if (res.status != 200 || !!errorStatus[res.data.status]) {
errorCount++
// set the Maximum error reporting interval greater than 15 sec but below 30 sec.
if (errorReportingInterval < 1000 * 15) {
errorReportingInterval = errorReportingInterval << 1 | 1
}
} else {
// reset the error reporting interval once get the proper status back.
errorReportingInterval = 0
}
if (errorCount >= maxErrorCount) {
core.info('Too many errors, aborting!')
core.setFailed('Failed with status code: ' + res.status)
break
}
// Handle timeout
if (Date.now() - startTime >= timeout) {
core.info('Timeout reached, aborting!')
core.setFailed('Timeout reached, aborting!')
return
}
}
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
)
core.info(`Deployment cancelled with ${pagesCancelDeployEndpoint}`)
} catch (error) {
core.setFailed(error)
if (error.response && error.response.data) {
core.info(JSON.stringify(error.response.data))
}
}
}
module.exports = {Deployment}
}
module.exports = { Deployment }

View File

@@ -6,39 +6,17 @@ require('regenerator-runtime/runtime')
const core = require('@actions/core')
// const github = require('@actions/github'); // TODO: Not used until we publish API endpoint to the @action/github package
const axios = require('axios')
const {Deployment} = require('./deployment')
const { Deployment } = require('./deployment')
const deployment = new Deployment()
// TODO: If the artifact hasn't been created, we can create it and upload to artifact storage ourselves
// const tar = require('tar')
async function cancelHandler(evtOrExitCodeOrError) {
try {
if (deployment.requestedDeployment) {
const pagesCancelDeployEndpoint = `${deployment.githubApiUrl}/repos/${process.env.GITHUB_REPOSITORY}/pages/deployment/cancel/${process.env.GITHUB_SHA}`
await axios.put(
pagesCancelDeployEndpoint,
{},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${deployment.githubToken}`,
'Content-type': 'application/json'
}
}
)
core.info(`Deployment cancelled with ${pagesCancelDeployEndpoint}`)
}
} catch (e) {
console.log('Deployment cancellation failed', e)
}
await deployment.cancel()
process.exit(isNaN(+evtOrExitCodeOrError) ? 1 : +evtOrExitCodeOrError)
}
async function main() {
let idToken = ""
let idToken = ''
try {
idToken = await core.getIDToken()
} catch (error) {
@@ -59,8 +37,8 @@ process.on('SIGINT', cancelHandler)
process.on('SIGTERM', cancelHandler)
// Main
const emitTelemetry = core.getInput("emit_telemetry")
if (emitTelemetry === "true") {
const emitTelemetry = core.getInput('emit_telemetry')
if (emitTelemetry === 'true') {
require('./pre')
} else {
main()

View File

@@ -7,14 +7,14 @@ const axios = require('axios')
const { expect, jest } = require('@jest/globals')
const {Deployment} = require('./deployment')
const { Deployment } = require('./deployment')
describe('with all environment variables set', () => {
beforeEach(() => {
process.env.ACTIONS_RUNTIME_URL = 'my-url'
process.env.GITHUB_RUN_ID = '123'
process.env.ACTIONS_RUNTIME_TOKEN = 'a-token'
process.env.GITHUB_REPOSITORY = 'paper-spa/is-awesome'
process.env.GITHUB_REPOSITORY = 'actions/is-awesome'
process.env.GITHUB_TOKEN = 'gha-token'
process.env.GITHUB_SHA = '123abc'
process.env.GITHUB_ACTOR = 'monalisa'
@@ -22,6 +22,11 @@ describe('with all environment variables set', () => {
process.env.GITHUB_ACTION_PATH = 'something'
})
afterEach(() => {
// Remove mock for `core.getInput('preview')`
delete process.env.INPUT_PREVIEW
})
it('Executes cleanly', done => {
const ip = path.join(__dirname, './index.js')
cp.exec(`node ${ip}`, { env: process.env }, (err, stdout) => {
@@ -35,8 +40,8 @@ describe('with variables missing', () => {
it('execution fails if there are missing variables', done => {
delete process.env.ACTIONS_RUNTIME_URL
const ip = path.join(__dirname, './index.js')
cp.exec(`node ${ip}`, {env: process.env}, (err, stdout) => {
expect(stdout).toBe("")
cp.exec(`node ${ip}`, { env: process.env }, (err, stdout) => {
expect(stdout).toBe('')
expect(err).toBeTruthy()
expect(err.code).toBe(1)
done()
@@ -49,7 +54,7 @@ describe('create', () => {
process.env.ACTIONS_RUNTIME_URL = 'http://my-url/'
process.env.GITHUB_RUN_ID = '123'
process.env.ACTIONS_RUNTIME_TOKEN = 'a-token'
process.env.GITHUB_REPOSITORY = 'paper-spa/is-awesome'
process.env.GITHUB_REPOSITORY = 'actions/is-awesome'
process.env.GITHUB_TOKEN = 'gha-token'
process.env.GITHUB_SHA = '123abc'
process.env.GITHUB_ACTOR = 'monalisa'
@@ -61,6 +66,8 @@ describe('create', () => {
return 'github-pages'
case 'token':
return process.env.GITHUB_TOKEN
default:
return process.env[`INPUT_${param.toUpperCase()}`] || ''
}
})
@@ -80,10 +87,16 @@ describe('create', () => {
it('can successfully create a deployment', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const fakeJwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNjllMWIxOC1jOGFiLTRhZGQtOGYxOC03MzVlMzVjZGJhZjAiLCJzdWIiOiJyZXBvOnBhcGVyLXNwYS9taW55aTplbnZpcm9ubWVudDpQcm9kdWN0aW9uIiwiYXVkIjoiaHR0cHM6Ly9naXRodWIuY29tL3BhcGVyLXNwYSIsInJlZiI6InJlZnMvaGVhZHMvbWFpbiIsInNoYSI6ImEyODU1MWJmODdiZDk3NTFiMzdiMmM0YjM3M2MxZjU3NjFmYWM2MjYiLCJyZXBvc2l0b3J5IjoicGFwZXItc3BhL21pbnlpIiwicmVwb3NpdG9yeV9vd25lciI6InBhcGVyLXNwYSIsInJ1bl9pZCI6IjE1NDY0NTkzNjQiLCJydW5fbnVtYmVyIjoiMzQiLCJydW5fYXR0ZW1wdCI6IjIiLCJhY3RvciI6IllpTXlzdHkiLCJ3b3JrZmxvdyI6IkNJIiwiaGVhZF9yZWYiOiIiLCJiYXNlX3JlZiI6IiIsImV2ZW50X25hbWUiOiJwdXNoIiwicmVmX3R5cGUiOiJicmFuY2giLCJlbnZpcm9ubWVudCI6IlByb2R1Y3Rpb24iLCJqb2Jfd29ya2Zsb3dfcmVmIjoicGFwZXItc3BhL21pbnlpLy5naXRodWIvd29ya2Zsb3dzL2JsYW5rLnltbEByZWZzL2hlYWRzL21haW4iLCJpc3MiOiJodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwibmJmIjoxNjM4ODI4MDI4LCJleHAiOjE2Mzg4Mjg5MjgsImlhdCI6MTYzODgyODYyOH0.1wyupfxu1HGoTyIqatYg0hIxy2-0bMO-yVlmLSMuu2w'
const fakeJwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNjllMWIxOC1jOGFiLTRhZGQtOGYxOC03MzVlMzVjZGJhZjAiLCJzdWIiOiJyZXBvOnBhcGVyLXNwYS9taW55aTplbnZpcm9ubWVudDpQcm9kdWN0aW9uIiwiYXVkIjoiaHR0cHM6Ly9naXRodWIuY29tL3BhcGVyLXNwYSIsInJlZiI6InJlZnMvaGVhZHMvbWFpbiIsInNoYSI6ImEyODU1MWJmODdiZDk3NTFiMzdiMmM0YjM3M2MxZjU3NjFmYWM2MjYiLCJyZXBvc2l0b3J5IjoicGFwZXItc3BhL21pbnlpIiwicmVwb3NpdG9yeV9vd25lciI6InBhcGVyLXNwYSIsInJ1bl9pZCI6IjE1NDY0NTkzNjQiLCJydW5fbnVtYmVyIjoiMzQiLCJydW5fYXR0ZW1wdCI6IjIiLCJhY3RvciI6IllpTXlzdHkiLCJ3b3JrZmxvdyI6IkNJIiwiaGVhZF9yZWYiOiIiLCJiYXNlX3JlZiI6IiIsImV2ZW50X25hbWUiOiJwdXNoIiwicmVmX3R5cGUiOiJicmFuY2giLCJlbnZpcm9ubWVudCI6IlByb2R1Y3Rpb24iLCJqb2Jfd29ya2Zsb3dfcmVmIjoicGFwZXItc3BhL21pbnlpLy5naXRodWIvd29ya2Zsb3dzL2JsYW5rLnltbEByZWZzL2hlYWRzL21haW4iLCJpc3MiOiJodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwibmJmIjoxNjM4ODI4MDI4LCJleHAiOjE2Mzg4Mjg5MjgsImlhdCI6MTYzODgyODYyOH0.1wyupfxu1HGoTyIqatYg0hIxy2-0bMO-yVlmLSMuu2w'
const scope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, { value: [ {url: 'https://another-artifact.com', name: 'another-artifact'}, { url: 'https://fake-artifact.com', name: 'github-pages' }] })
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
})
core.getIDToken = jest.fn().mockResolvedValue(fakeJwt)
axios.post = jest.fn().mockResolvedValue('test')
@@ -93,7 +106,7 @@ describe('create', () => {
await deployment.create(fakeJwt)
expect(axios.post).toBeCalledWith(
'https://api.github.com/repos/paper-spa/is-awesome/pages/deployment',
'https://api.github.com/repos/actions/is-awesome/pages/deployment',
{
artifact_url: 'https://fake-artifact.com&%24expand=SignedContent',
pages_build_version: 'valid-build-version',
@@ -109,10 +122,54 @@ describe('create', () => {
)
expect(core.setFailed).not.toHaveBeenCalled()
expect(core.info).toHaveBeenCalledWith(
'Created deployment for valid-build-version'
expect(core.info).toHaveBeenCalledWith('Created deployment for valid-build-version')
scope.done()
})
it('can successfully create a preview deployment', async () => {
process.env.GITHUB_SHA = 'valid-build-version'
const fakeJwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJiNjllMWIxOC1jOGFiLTRhZGQtOGYxOC03MzVlMzVjZGJhZjAiLCJzdWIiOiJyZXBvOnBhcGVyLXNwYS9taW55aTplbnZpcm9ubWVudDpQcm9kdWN0aW9uIiwiYXVkIjoiaHR0cHM6Ly9naXRodWIuY29tL3BhcGVyLXNwYSIsInJlZiI6InJlZnMvaGVhZHMvbWFpbiIsInNoYSI6ImEyODU1MWJmODdiZDk3NTFiMzdiMmM0YjM3M2MxZjU3NjFmYWM2MjYiLCJyZXBvc2l0b3J5IjoicGFwZXItc3BhL21pbnlpIiwicmVwb3NpdG9yeV9vd25lciI6InBhcGVyLXNwYSIsInJ1bl9pZCI6IjE1NDY0NTkzNjQiLCJydW5fbnVtYmVyIjoiMzQiLCJydW5fYXR0ZW1wdCI6IjIiLCJhY3RvciI6IllpTXlzdHkiLCJ3b3JrZmxvdyI6IkNJIiwiaGVhZF9yZWYiOiIiLCJiYXNlX3JlZiI6IiIsImV2ZW50X25hbWUiOiJwdXNoIiwicmVmX3R5cGUiOiJicmFuY2giLCJlbnZpcm9ubWVudCI6IlByb2R1Y3Rpb24iLCJqb2Jfd29ya2Zsb3dfcmVmIjoicGFwZXItc3BhL21pbnlpLy5naXRodWIvd29ya2Zsb3dzL2JsYW5rLnltbEByZWZzL2hlYWRzL21haW4iLCJpc3MiOiJodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tIiwibmJmIjoxNjM4ODI4MDI4LCJleHAiOjE2Mzg4Mjg5MjgsImlhdCI6MTYzODgyODYyOH0.1wyupfxu1HGoTyIqatYg0hIxy2-0bMO-yVlmLSMuu2w'
const scope = nock(`http://my-url`)
.get('/_apis/pipelines/workflows/123/artifacts?api-version=6.0-preview')
.reply(200, {
value: [
{ url: 'https://another-artifact.com', name: 'another-artifact' },
{ url: 'https://fake-artifact.com', name: 'github-pages' }
]
})
core.getIDToken = jest.fn().mockResolvedValue(fakeJwt)
axios.post = jest.fn().mockResolvedValue('test')
// Return `"true"` for `core.getInput("preview")`
process.env.INPUT_PREVIEW = 'true'
// Create the deployment
const deployment = new Deployment()
await deployment.create(fakeJwt)
expect(axios.post).toBeCalledWith(
'https://api.github.com/repos/actions/is-awesome/pages/deployment',
{
artifact_url: 'https://fake-artifact.com&%24expand=SignedContent',
pages_build_version: 'valid-build-version',
oidc_token: fakeJwt,
preview: true
},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer gha-token`,
'Content-type': 'application/json'
}
}
)
expect(core.setFailed).not.toHaveBeenCalled()
expect(core.info).toHaveBeenCalledWith('Created deployment for valid-build-version')
scope.done()
})
@@ -131,9 +188,8 @@ describe('create', () => {
try {
deployment.create()
} catch (err) {
expect(axios.post).toBeCalledWith(
'https://api.github.com/repos/paper-spa/is-awesome/pages/deployment',
'https://api.github.com/repos/actions/is-awesome/pages/deployment',
{
artifact_url: 'https://invalid-artifact.com&%24expand=SignedContent',
pages_build_version: 'invalid-build-version'
@@ -147,15 +203,12 @@ describe('create', () => {
}
)
expect(core.info).toHaveBeenLastCalledWith(
'Failed to create deployment for invalid-build-version.'
)
expect(core.info).toHaveBeenLastCalledWith('Failed to create deployment for invalid-build-version.')
expect(core.setFailed).toHaveBeenCalledWith({ status: 400 })
scope.done()
}
})
})
describe('check', () => {
@@ -163,7 +216,7 @@ describe('check', () => {
process.env.ACTIONS_RUNTIME_URL = 'http://my-url/'
process.env.GITHUB_RUN_ID = '123'
process.env.ACTIONS_RUNTIME_TOKEN = 'a-token'
process.env.GITHUB_REPOSITORY = 'paper-spa/is-awesome'
process.env.GITHUB_REPOSITORY = 'actions/is-awesome'
process.env.GITHUB_TOKEN = 'gha-token'
process.env.GITHUB_SHA = '123abc'
process.env.GITHUB_ACTOR = 'monalisa'

View File

@@ -16,17 +16,18 @@ axiosRetry(axios, {
}
})
const {Deployment} = require('./deployment')
const { Deployment } = require('./deployment')
async function emitTelemetry() {
// All variables we need from the runtime are set in the Deployment constructor
const deployment = new Deployment()
const telemetryUrl = `${deployment.githubApiUrl}/repos/${deployment.repositoryNwo}/pages/telemetry`
core.info(`Sending telemetry for run id ${deployment.workflowRun}`)
const conclusion = core.getInput('conclusion') || null
core.info(`Sending telemetry for run id ${deployment.workflowRun}: ${conclusion}`)
await axios
.post(
telemetryUrl,
{github_run_id: deployment.workflowRun},
{ github_run_id: deployment.workflowRun, conclusion },
{
headers: {
Accept: 'application/vnd.github.v3+json',
@@ -54,4 +55,4 @@ async function main() {
main()
module.exports = {emitTelemetry}
module.exports = { emitTelemetry }

View File

@@ -2,21 +2,22 @@ const core = require('@actions/core')
const process = require('process')
const axios = require('axios')
const {expect, jest} = require('@jest/globals')
const { expect, jest } = require('@jest/globals')
const {emitTelemetry} = require('./pre')
const { emitTelemetry } = require('./pre')
describe('emitTelemetry', () => {
beforeAll(() => {
process.env.ACTIONS_RUNTIME_URL = 'my-url'
process.env.GITHUB_RUN_ID = '123'
process.env.ACTIONS_RUNTIME_TOKEN = 'a-token'
process.env.GITHUB_REPOSITORY = 'paper-spa/is-awesome'
process.env.GITHUB_REPOSITORY = 'actions/is-awesome'
process.env.GITHUB_TOKEN = 'gha-token'
process.env.GITHUB_SHA = '123abc'
process.env.GITHUB_ACTOR = 'monalisa'
process.env.GITHUB_ACTION = '__monalisa/octocat'
process.env.GITHUB_ACTION_PATH = 'something'
process.env.INPUT_CONCLUSION = 'success'
jest.spyOn(core, 'setOutput').mockImplementation(param => {
return param
@@ -45,9 +46,10 @@ describe('emitTelemetry', () => {
emitTelemetry()
expect(axios.post).toBeCalledWith(
'https://api.github.com/repos/paper-spa/is-awesome/pages/telemetry',
'https://api.github.com/repos/actions/is-awesome/pages/telemetry',
{
github_run_id: process.env.GITHUB_RUN_ID
github_run_id: process.env.GITHUB_RUN_ID,
conclusion: 'success'
},
{
headers: {