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

chore: add publish-docs workflow #181

Merged
merged 2 commits into from
Aug 17, 2024
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
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
Loading