Commit 7d678196 authored by Moody Salem's avatar Moody Salem

chore(release): Add an action to replace Vercel DNS records

parent 7b9b332c
......@@ -9,7 +9,8 @@
}
},
"ignorePatterns": [
"node_modules/**/*"
"node_modules/**/*",
"actions/**/*"
],
"settings": {
"react": {
......
......@@ -46,7 +46,14 @@ jobs:
- name: Update DNS with new IPFS hash
id: update_dns
run: npx vercel --token ${{ secrets.VERCEL_TOKEN }} --scope uniswap dns add uniswap.org _dnslink.app TXT "dnslink=/ipfs/${{ steps.upload.outputs.hash }}"
uses: ./actions/replace-vercel-dns-record
with:
domain: 'uniswap.org'
subdomain: '_dnslink.app'
record-type: 'TXT'
value: /ipfs/${{ steps.upload.outputs.hash }}
token: ${{ secrets.VERCEL_TOKEN }}
team-name: 'uniswap'
- name: Create GitHub Release
id: create_release
......
name: 'Replace Vercel DNS Record'
description: 'Replace a Vercel DNS record by adding one record and deleting all others'
inputs:
domain:
description: 'The vercel domain'
required: true
subdomain:
description: 'The vercel subdomain to replace records for'
required: true
record-type:
description: 'The record type to replace'
required: true
value:
description: 'The new value for the subdomain/record type combo'
required: true
token:
description: 'The Vercel authentication token'
required: true
team-name:
description: 'The name of the team that controls the domain, e.g. Uniswap'
required: true
runs:
using: 'node12'
main: 'index.js'
\ No newline at end of file
const core = require('@actions/core')
const replaceDomain = require('./replace-domain')
replaceDomain({
teamName: core.getInput('team-name'),
token: core.getInput('token'),
recordType: core.getInput('record-type'),
domain: core.getInput('domain'),
subdomain: core.getInput('subdomain'),
value: core.getInput('value'),
})
.catch(error => {
core.setFailed(error)
})
\ No newline at end of file
{
"main": "index.js",
"dependencies": {
"@actions/core": "^1.2.4",
"node-fetch": "^2.6.0"
}
}
const fetch = require('node-fetch')
function checkStatusAndParseResponse(res) {
if (res.status < 200 || res.status >= 300) {
return res.json()
.then(
({ error: { code, message } }) => {
throw new Error(`Response error code ${code}: ${message}`)
},
)
.catch(error => {
throw new Error(`Response status received without JSON error body: ${res.status}; ${error.message}`)
})
}
if (res.status === 204) return ''
return res.text()
.then(text => {
try {
return JSON.parse(text)
} catch (error) {
console.warn(`Invalid json response: ${error.message}`)
return ''
}
})
}
function authorizedFetchAndParse(urlPath, token, options) {
return fetch(`https://api.vercel.com${urlPath}`, {
...options,
headers: {
...(options && options.headers),
Authorization: `Bearer ${token}`,
},
}).then(checkStatusAndParseResponse)
}
function getTeamId({ teamName, token }) {
return authorizedFetchAndParse('/v1/teams', token)
.then(({ teams }) => teams.filter(({ name, slug }) => slug.toLowerCase() === teamName || name.toLowerCase() === teamName))
.then(teams => {
if (teams.length === 1) return teams[ 0 ]
if (teams.length > 1) {
throw new Error('team name matched more than 1 team')
} else {
throw new Error('team name did not match any team')
}
})
.then(team => team.id)
}
function getExistingRecords({ domain, subdomain, recordType, teamId, token }) {
// GET /v4/domains/:domain/records
return authorizedFetchAndParse(`/v4/domains/${domain}/records?limit=10000&teamId=${teamId}`, token)
.then(
({ records }) => records.filter(({ type, name }) => type.toLowerCase() === recordType.toLowerCase() && name.toLowerCase() === subdomain.toLowerCase()),
)
.then(records => records.map(record => record.id))
}
function createRecord({ domain, subdomain, recordType, value, teamId, token }) {
return authorizedFetchAndParse(`/v2/domains/${domain}/records?teamId=${teamId}`, token, {
method: 'POST',
body: JSON.stringify({
name: subdomain,
type: recordType,
value,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(({ uid }) => uid)
}
function deleteRecord({ domain, recordId, teamId, token }) {
return authorizedFetchAndParse(`/v2/domains/${domain}/records/${recordId}?teamId=${teamId}`, token, {
method: 'DELETE',
})
}
module.exports = async function replaceDomain({ domain, subdomain, recordType, token, teamName, value }) {
const teamId = await getTeamId({ teamName, token })
const recordsToDelete = await getExistingRecords({ domain, subdomain, recordType, teamId, token })
const createdUid = await createRecord({ domain, subdomain, recordType, teamId, token, value })
if (recordsToDelete.indexOf(createdUid) !== -1) {
console.log('Record already exists, create was idempotent')
} else {
console.log(`Created record with uid ${createdUid}, deleting others`)
}
const filteredToDelete = recordsToDelete.filter(recordId => recordId !== createdUid)
if (filteredToDelete.length > 0) {
console.log(`Deleting records: ${filteredToDelete.join(', ')}`)
await Promise.all(
filteredToDelete.map(recordId => deleteRecord({ domain, recordId, token, teamId })),
)
console.log('Deleted records. Updated completed.')
} else {
console.log('No records to delete. Update completed.')
}
}
\ No newline at end of file
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@actions/core@^1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.4.tgz#96179dbf9f8d951dd74b40a0dbd5c22555d186ab"
integrity sha512-YJCEq8BE3CdN8+7HPZ/4DxJjk/OkZV2FFIf+DlZTC/4iBlzYCD5yjRR6eiOS5llO11zbRltIRuKAjMKaWTE6cg==
node-fetch@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment