Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DE-6746: Add a GH workflow to publish a release #18

Merged
merged 11 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/actions/apply-version/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: 'Apply version'
description: 'Apply version to package.json, readme and docs, also update versioned links'

inputs:
version:
description: The SDK version
required: true

runs:
using: 'node20'
main: './main.js'
73 changes: 73 additions & 0 deletions .github/actions/apply-version/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const fs = require('fs')
const execSync = require('child_process').execSync
const core = require('@actions/core')
const utils = require('../utils')

/**
* @fileoverview Replace version and links to API docs with the current/fresh
* value where needed (package.json, readme, storybook intro).
*/

const TOKENS = {
LINK_DOCS: 'CI_REPLACE_LINK_DOCS',
VERSION: 'CI_REPLACE_VERSION',
}

const FILES = {
README: utils.file('README.md'),
STORYBOOK_INTRO: utils.file('story/stories/Intro.mdx'),
}

const version = core.getInput('version', {required: true})

// Note: The trailing slash is very important here, it won't work without it.
const homepage = `https://players.castlabs.com/react-dom/${version}/docs/`

core.info(`API docs link is: ${homepage}`)

/**
* Set the "version" attribute in package.json
* @param {string} version
*/
function updatePackageJson(version) {
core.info(`Set package.json version to ${version}`)
execSync(`npm pkg set version=${version};`, { stdio: 'inherit' })
}

/**
* Replace token by link in the README.md file
* @param {string} link
*/
function addLinkToReadme(link) {
core.info(`Adding link to ${FILES.README}`)
replaceText(FILES.README, TOKENS.LINK_DOCS, link)
}

/**
* Replace token by version in Storybook Intro.mdx file
* @param {string} version
*/
function addVersionToStorybook(version) {
core.info(`Adding version to ${FILES.STORYBOOK_INTRO}`)
replaceText(FILES.STORYBOOK_INTRO, TOKENS.VERSION, version)
}

/**
* @param {string} filename file
* @param {string} token token to replace
* @param {string} text replacement text
*/
function replaceText(filename, token, text) {
const content = fs.readFileSync(filename, 'utf8')
fs.writeFileSync(filename, content.replace(token, text))
}

try {
addLinkToReadme(homepage)
addVersionToStorybook(version)
updatePackageJson(version)
core.info('Success')
} catch (err) {
core.error('Failed to generated links to API docs' + err)
process.exit(1)
}
17 changes: 17 additions & 0 deletions .github/actions/publish/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: 'Publish'
description: 'Publish to NPM, if it is a beta version publish to beta channel'

inputs:
version:
description: The SDK version
required: true
npm-token:
description: The NPM token
required: true
dry-run:
description: If true, run the commands but don't publish to NPM
required: false

runs:
using: 'node20'
main: './main.js'
75 changes: 75 additions & 0 deletions .github/actions/publish/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const execSync = require('child_process').execSync
const core = require('@actions/core')
const fs = require('fs')
const utils = require('../utils')

/**
* @fileoverview Publish to NPM, if the version is in the beta format
* (e.g. 1.2.3-beta.4 or 1.2.3-alpha.1 etc.) then publish to a beta channel.
*/

/**
* @typedef {'normal'|'beta'} VersionType
* @typedef {{ type: VersionType, label?: string }} VersionInfo
*/

/**
* @param {string} version version string
* @return {VersionInfo|null} version info or null if invalid
*/
function parseVersion(version) {
const regexBetaVersion = /^\d+\.\d+\.\d+-(\w+)\.\d+$/;
const regexVersion = /^\d+\.\d+\.\d+$/;

const betaMatch = version.match(regexBetaVersion);
if (betaMatch) {
const label = betaMatch[1];
return { type: 'beta', label };
}

const isNormal = regexVersion.test(version);
if (isNormal) {
return { type: 'normal' };
}

return null;
}


const version = core.getInput('version', {required: true})
const npmToken = core.getInput('npm-token', {required: true})
const dryRun = core.getBooleanInput('dry-run')

const versionInfo = parseVersion(version)

if (versionInfo === null) {
core.error(`Invalid version format. Version: ${version}`)
process.exit(1)
}

const { type, label } = versionInfo;
const args = ['--provenance']
if (dryRun) {
args.push('--dry-run')
}
if (label) {
args.push(`--tag ${label}`)
}

core.info(`Publishing ${type === 'beta' ? 'Beta version' : 'version'} ${version}.`)

const npmRcContent = `
@castlabs:registry=https://registry.npmjs.org
//registry.npmjs.org/:_authToken=\${NPM_TOKEN}
`

try {
fs.writeFileSync(utils.file('.npmrc'), npmRcContent)
execSync(
`npm publish ${args.join(' ')}`,
{ stdio: 'inherit', env: { ...process.env, NPM_TOKEN: npmToken }},
)
} catch (error) {
core.error('Failed to publish to NPM' + error)
process.exit(1)
}
17 changes: 17 additions & 0 deletions .github/actions/upload-to-s3/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: 'Upload to S3'
description: 'Upload the contents of a directory to an S3 bucket'

inputs:
directory:
description: Directory to upload
required: true
destination:
description: S3 destination path URI
required: true
dry-run:
description: If true, run the commands but don't actually upload
required: false

runs:
using: 'node20'
main: './main.js'
21 changes: 21 additions & 0 deletions .github/actions/upload-to-s3/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const execSync = require('child_process').execSync
const core = require('@actions/core')

/**
* @fileoverview Upload a directory to S3.
*/

const directory = core.getInput('directory', {required: true})
const destination = core.getInput('destination', {required: true})
const dryRun = core.getBooleanInput('dry-run')

const args = ['--recursive']
if (dryRun) {
args.push('--dryrun')
}

core.info(`Uploading ${directory} to S3.`)
execSync(`aws s3 cp ${args.join(' ')} "${directory}" "${destination}"`, { stdio: 'inherit' })
// Note: the default cache policy is 1 day, I think that's OK for now.
// In the future if traffic increases we can increase the expiration
// as much as we want.
13 changes: 13 additions & 0 deletions .github/actions/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const path = require('path')

/**
* @param {string} filepath relative to project root
* @return {string} absolute path
*/
function file(filepath) {
return path.resolve(__dirname, `../../${filepath}`)
}

module.exports = {
file,
}
15 changes: 8 additions & 7 deletions .github/workflows/ci-build.yml → .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI Build
name: Build

on:
push:
Expand All @@ -7,21 +7,22 @@ on:
pull_request:
branches:
- main

jobs:
build:
name: Build
runs-on: [ubuntu-latest]

steps:
- uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v3

- uses: actions/setup-node@v3
name: Setup Node
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm

- name: NPM Install
run: npm ci

Expand Down
73 changes: 73 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Publish

on:
release:
types: [released]

env:
VERSION: ${{ github.event.release.tag_name }}
WEB_PATH: "react-dom/${{ github.event.release.tag_name }}/docs/"
DRY_RUN: false

permissions:
contents: read # Permission for actions/checkout
id-token: write # Permission for aws-actions/configure-aws-credentials

jobs:
publish:
name: Publish
runs-on: [ubuntu-latest]
steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20
cache: npm

- name: Install Dependencies
run: npm ci

- name: Apply version to package.json, readme and docs
uses: ./.github/actions/apply-version
with:
version: ${{ env.VERSION }}

## Publish the API docs

- name: Build API Docs
run: npm run build-storybook

- name: Assume AWS role
uses: aws-actions/configure-aws-credentials@v3
with:
role-to-assume: ${{ secrets.AWS_UPLOADER_ROLE_PLAYERS }}
aws-region: us-east-1

- name: Upload API Docs
uses: ./.github/actions/upload-to-s3
with:
directory: "./dist/storybook"
destination: "${{ secrets.AWS_WEB_BUCKET }}/${{ env.WEB_PATH }}"
dry-run: ${{ env.DRY_RUN }}

## Publish the NPM package

- name: Build
run: npm run build

- name: Publish to NPM
uses: ./.github/actions/publish
with:
version: ${{ env.VERSION }}
npm-token: ${{ secrets.CLPLAYERS_NPM_TOKEN_REACT_COMPONENTS }}
dry-run: ${{ env.DRY_RUN }}

- name: Add Job summary
run: |
echo '### NPM Release' >> $GITHUB_STEP_SUMMARY
echo "Released version ${{ env.VERSION }} of https://www.npmjs.com/package/@castlabs/prestoplay-react-components" >> $GITHUB_STEP_SUMMARY
echo '### Docs' >> $GITHUB_STEP_SUMMARY
echo "Published docs to https://players.castlabs.com/${{ env.WEB_PATH }}" >> $GITHUB_STEP_SUMMARY
Loading