Skip to content

Commit

Permalink
chore: add publish-docs workflow (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson authored Aug 17, 2024
1 parent 999ec10 commit 6ef7141
Show file tree
Hide file tree
Showing 4 changed files with 1,120 additions and 0 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/publish-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Publish Docs

on:
push:
tags:
- '*'

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version: '22.x'
cache: pnpm

- name: Install dependencies
run: |
pnpm install -C scripts/docs
pnpm install -C scripts/radashi-db
- name: Publish
env:
SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }}
run: |
./scripts/docs/node_modules/.bin/tsx ./scripts/docs/ci-publish-docs.ts ${{ github.event.ref }}
315 changes: 315 additions & 0 deletions scripts/docs/ci-publish-docs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
/**
* # Publishing A Version
*
* - Clone the "main" branch of https://github.com/radashi-org/radashi-org.github.io.git to ./www
* - Clone the "gh-pages" branch of https://github.com/radashi-org/radashi-org.github.io.git to ./www/dist
* - Run `git submodule update --init --recursive` in ./www
* - Symlink ./ to ./www/radashi
* - Add version to ./www/versions.json
* - Run `pnpm build --outDir ./dist/{version}` in ./www
* - If a stable version (or ./www/versions.json is empty), recursive copy ./www/dist/{version} to ./www/dist
*
* # Determining The Version
*
* - If triggered by a stable release or a workflow dispatch, the version is found with "$(git describe --abbrev=0 --tags)". If nothing in ./docs/ was changed between the last stable release and HEAD, skip the build.
* - If triggered by a beta release, the version is "beta". If nothing in ./docs/ was changed between the last release and HEAD, skip the build.
*/
import { execa } from 'execa'
import glob from 'fast-glob'
import { green } from 'kleur/colors'
import mri from 'mri'
import fs from 'node:fs/promises'
import { supabase } from 'radashi-db/supabase.js'

main()

async function main() {
const argv = mri(process.argv.slice(2), {
boolean: ['dry-run', 'force'],
alias: {
'dry-run': ['dryRun', 'n'],
force: ['f'],
},
})

let [newVersion] = argv._
if (!newVersion) {
console.error('Version is required')
process.exit(1)
}

let metaId: string
if (newVersion.startsWith('refs/tags/')) {
const tag = newVersion.slice('refs/tags/'.length)
if (tag[0] === 'v') {
newVersion = tag.slice(1)
metaId = 'stable_version'
} else if (tag === 'beta' || tag === 'next') {
newVersion = await execa('git-cliff', ['--bumped-version']).then(r =>
r.stdout.replace(/^v/, ''),
)
if (tag === 'next') {
newVersion += '-alpha'
// Compare against the last alpha version.
metaId = 'alpha_version'
} else {
newVersion += '-beta'
// Compare against the last stable version or the last beta
// version, whichever is more recent.
metaId = 'latest_version'
}
} else {
console.error('Invalid tag: "%s"', tag)
process.exit(1)
}
if (!/^\d+\.\d+\.\d+(-\w+)?$/.test(newVersion)) {
console.error(
'Expected version to be like "1.0.0", but got "%s" instead.',
newVersion,
)
process.exit(1)
}
} else {
console.error(
'Expected a tag ref like "refs/tags/v1.0.0", but got "%s" instead.',
newVersion,
)
process.exit(1)
}

log('Getting release info')
const metaResult = await supabase
.from('meta')
.select('value')
.eq('id', metaId)
.limit(1)
.single()

if (metaResult.error) {
console.error('Failed to get release info:', metaResult.error)
process.exit(1)
}

const lastRelease = metaResult.data?.value as {
version: string
ref: string
}

log('Comparing to this release:', lastRelease)

const lastReleaseParts = lastRelease.version.split('.')
const lastReleaseMajor = +lastReleaseParts[0]
const lastReleaseMinor = +lastReleaseParts[1]

const newReleaseParts = newVersion.split(/[-.]/)
const newReleaseMajor = +newReleaseParts[0]
const newReleaseMinor = +newReleaseParts[1]

// When the new release has a greater major.minor version, we'll
// always continue. Otherwise, check to see if there are any changes
// before continuing.
if (
newReleaseMajor === lastReleaseMajor &&
newReleaseMinor === lastReleaseMinor
) {
log('Checking for changes in docs')
const diffResult = await execa('git', [
'diff',
`${lastRelease.ref}..HEAD`,
'--',
'docs',
])

if (!diffResult.stdout.trim()) {
log('No changes in docs since last release. Skipping build.')
process.exit(0)
}
}

/** This is the identifier used by URLs. */
let newReleaseId = 'v' + String(newReleaseMajor)
if (newReleaseParts.length > 3) {
if (newReleaseMajor === lastReleaseMajor) {
newReleaseId += '.' + newReleaseMinor
if (newReleaseMinor === lastReleaseMinor) {
newReleaseId += '.' + newReleaseParts[2]
}
}
newReleaseId += '-' + newReleaseParts[3]
}

log('Publishing docs for version:', newReleaseId)

log('Cloning main branch of radashi-org.github.io')
await execa(
'git',
[
'clone',
'--depth',
'1',
'--branch',
'main',
'https://github.com/radashi-org/radashi-org.github.io.git',
'./www',
],
{ stdio: 'inherit' },
)

log('Cloning gh-pages branch of radashi-org.github.io')
await execa(
'git',
[
'clone',
'--depth',
'1',
'--branch',
'gh-pages',
'https://github.com/radashi-org/radashi-org.github.io.git',
'./www/dist',
],
{ stdio: 'inherit' },
)

log('Cloning submodules in ./www directory')
await execa('git', ['submodule', 'update', '--init', '--recursive'], {
cwd: './www',
stdio: 'inherit',
}).catch(exit)

log('Installing dependencies in ./www')
await execa('pnpm', ['install'], {
cwd: './www',
stdio: 'inherit',
}).catch(exit)

log('Installing dependencies in ./www/starlight')
await execa('pnpm', ['install'], {
cwd: './www/starlight',
stdio: 'inherit',
}).catch(exit)

log('Symlinking radashi to ./www/radashi')
await fs.symlink('..', './www/radashi')

const removedVersions = new Set<string>()
const cleanVersions = (oldVersions: string[]) =>
oldVersions.filter(oldVersion => {
if (newVersion === oldVersion) {
return true
}
const oldRawVersion = toRawVersion(oldVersion)
if (newVersion === oldRawVersion) {
// Avoid having a beta, RC, or stable with the same raw version.
removedVersions.add(oldVersion)
return false
}
// Beta versions are reserved for non-breaking changes that have
// been merged into the "main" branch and don't have a stable
// version yet.
if (oldVersion.includes('beta')) {
if (newVersion.includes('beta')) {
// Only one beta version is made available at a time.
removedVersions.add(oldVersion)
return false
}
}
// Alpha versions are reserved for breaking changes that have
// been merged into the "next" branch.
else if (oldVersion.includes('alpha')) {
if (newVersion.includes('alpha')) {
// Only one RC version is made available at a time.
removedVersions.add(oldVersion)
return false
}
}
// Only a stable version should be left to handle.
else {
const oldMajorVersion = +oldRawVersion.split('.')[0]
if (newReleaseMajor - oldMajorVersion > 2) {
// Only 3 major versions are made available at a time.
removedVersions.add(oldVersion)
return false
}
}
return true
})

log('Updating ./www/public/versions.json')
const versions = new Set(
cleanVersions(
JSON.parse(await fs.readFile('./www/public/versions.json', 'utf8')),
),
)
versions.add(newReleaseId)
await fs.writeFile(
'./www/public/versions.json',
JSON.stringify([...versions]),
)

if (removedVersions.size > 0) {
log('Removing docs for unmaintained versions')
for (const removedVersion of removedVersions) {
await fs.rm(`./www/dist/${removedVersion}`, { recursive: true })
}
}

log('Removing docs that will be overwritten')
await fs.rm(`./www/dist/${newReleaseId}`, { recursive: true }).catch(() => {})

log('Building docs with version-specific --outDir')
await execa('pnpm', ['build', '--outDir', `./dist/${newReleaseId}`], {
cwd: './www',
stdio: 'inherit',
env: {
...process.env,
NODE_ENV: 'production',
},
}).catch(exit)

if (newReleaseParts.length === 3) {
log('Copying build into root folder')
const cwd = `www/dist/${newReleaseId}`
const files = await glob('**/*', { cwd })
for (const file of files) {
await fs.cp(`${cwd}/${file}`, `www/dist/${file}`)
}
}

log('Pushing to gh-pages branch')
await execa('git', ['add', '-A'], { cwd: './www/dist' })
await execa('git', ['commit', '-m', `chore: ${newReleaseId}`], {
cwd: './www/dist',
stdio: 'inherit',
})
await execa('git', ['push'], {
cwd: './www/dist',
stdio: 'inherit',
})
}

function log(msg: string, ...args: any[]) {
console.log(green('• ' + msg), ...args)
}

function exit() {
process.exit(1)
}

/**
* Extracts the raw version from a version string that may not have
* its minor or patch version set. It also removes any suffix like
* "-rc" or "-beta".
*
* @example
* ```ts
* toRawVersion('1') // '1.0.0'
* toRawVersion('1.1.0-rc') // '1.1.0'
* ```
*/
function toRawVersion(version: string) {
let [, rawVersion] = version.match(/^v?(\d+(?:\.\d+(?:\.\d+)?)?)/)!
for (let i = rawVersion.split('.').length; i < 3; i++) {
rawVersion += '.0'
}
return rawVersion
}
14 changes: 14 additions & 0 deletions scripts/docs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": true,
"type": "module",
"dependencies": {
"@types/node": "^22.3.0",
"execa": "^9.3.1",
"fast-glob": "^3.3.2",
"git-cliff": "2.4.0",
"kleur": "^4.1.5",
"mri": "^1.2.0",
"radashi-db": "link:../radashi-db",
"tsx": "^4.17.0"
}
}
Loading

0 comments on commit 6ef7141

Please sign in to comment.