Skip to content

Commit

Permalink
ci(release): add dev release handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ckohen committed Jun 21, 2024
1 parent d526ed9 commit 637266d
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 88 deletions.
60 changes: 11 additions & 49 deletions .github/workflows/publish-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,13 @@ on:
schedule:
- cron: '0 */12 * * *'
workflow_dispatch:
inputs:
dry_run:
type: boolean
default: false
jobs:
npm-publish:
name: npm publish
strategy:
fail-fast: false
matrix:
include:
- package: '@discordjs/brokers'
folder: 'brokers'
- package: '@discordjs/builders'
folder: 'builders'
- package: '@discordjs/collection'
folder: 'collection'
- package: '@discordjs/core'
folder: 'core'
- package: '@discordjs/formatters'
folder: 'formatters'
- package: 'discord.js'
folder: 'discord.js'
- package: '@discordjs/next'
folder: 'next'
- package: '@discordjs/proxy'
folder: 'proxy'
- package: '@discordjs/rest'
folder: 'rest'
- package: '@discordjs/util'
folder: 'util'
- package: '@discordjs/voice'
folder: 'voice'
- package: '@discordjs/ws'
folder: 'ws'
runs-on: ubuntu-latest
permissions:
id-token: write
Expand All @@ -53,32 +29,18 @@ jobs:
node-version: 20
registry-url: https://registry.npmjs.org/

- name: Check the current development version
id: release-check
run: |
if [[ $(npm view ${{ matrix.package }}@dev version | grep -e "$(git rev-parse --short HEAD)") ]]; \
then echo "RELEASE=0" >> "$GITHUB_OUTPUT"; \
else echo "RELEASE=1" >> "$GITHUB_OUTPUT"; \
fi
- name: Install dependencies
if: steps.release-check.outputs.release == '1'
uses: ./packages/actions/src/pnpmCache

- name: Build dependencies
if: steps.release-check.outputs.release == '1'
run: pnpm run build

- name: Publish package
if: steps.release-check.outputs.release == '1'
run: |
pnpm --filter=${{ matrix.package }} run release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)"
pnpm --filter=${{ matrix.package }} publish --provenance --no-git-checks --tag dev || true
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}

- name: Deprecate prior development releases
if: steps.release-check.outputs.release == '1'
run: pnpm exec npm-deprecate --name "*dev*" --message "This version is deprecated. Please use a newer version." --package ${{ matrix.package }}
- name: Pubish packages
uses: ./packages/actions/src/releasePackages
with:
exclude: 'create-discord-bot,@discordjs/docgen'
dry: ${{ inputs.dry_run }}
dev: true
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ jobs:
- name: Install dependencies
uses: ./packages/actions/src/pnpmCache

- name: Build dependencies
run: pnpm run build

- name: Release packages
uses: ./packages/actions/src/releasePackages
with:
Expand Down
1 change: 1 addition & 0 deletions packages/actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"undici": "6.18.2"
},
"devDependencies": {
"@npm/types": "^1.0.2",
"@types/bun": "^1.1.4",
"@types/node": "18.18.8",
"@vitest/coverage-v8": "^1.6.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/actions/src/releasePackages/action.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: 'Release Packages'
description: 'Tags and releases any unreleased packages'
inputs:
dev:
description: 'Releases development versions of packages (skips tagging and github releases)'
default: false
dry:
descrption: 'Perform a dry run that skips publishing and outputs logs indicating what would have happened'
default: false
Expand Down
92 changes: 66 additions & 26 deletions packages/actions/src/releasePackages/generateReleaseTree.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { warning } from '@actions/core';
import { $, file } from 'bun';
import { info, warning } from '@actions/core';
import type { PackageJson, PackumentVersion } from '@npm/types';
import { $, file, write } from 'bun';

const nonNodePackages = new Set(['@discordjs/proxy-container']);

Expand All @@ -25,11 +26,24 @@ export interface ReleaseEntry {
version: string;
}

async function getReleaseEntries() {
async function fetchDevVersion(pkg: string) {
try {
const res = await fetch(`https://registry.npmjs.org/${pkg}/dev`);
if (!res.ok) return null;
const packument = (await res.json()) as PackumentVersion;
return packument.version;
} catch {
return null;
}
}

async function getReleaseEntries(dev: boolean, dry: boolean) {
const releaseEntries: ReleaseEntry[] = [];
const packageList: pnpmTree[] =
await $`pnpm list --recursive --only-projects --filter {packages/\*} --prod --json`.json();

const commitHash = (await $`git rev-parse --short HEAD`.text()).trim();

for (const pkg of packageList) {
// Don't release private packages ever (npm will error anyways)
if (pkg.private) continue;
Expand All @@ -42,35 +56,61 @@ async function getReleaseEntries() {
version: pkg.version,
};

try {
// Find and parse changelog to post in github release
const changelogFile = await file(`${pkg.path}/CHANGELOG.md`).text();

let changelogLines: string[] = [];
let foundChangelog = false;
if (dev) {
const devVersion = await fetchDevVersion(pkg.name);
if (devVersion?.endsWith(commitHash)) {
// Write the currently released dev version so when pnpm publish runs on dependents they depend on the dev versions
if (dry) {
info(`[DRY] ${pkg.name}@${devVersion} already released. Editing package.json version.`);
} else {
const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJson;
pkgJson.version = devVersion;
await write(`${pkg.path}/package.json`, JSON.stringify(pkgJson, null, '\t'));
}

for (const line of changelogFile.split('\n')) {
if (line.startsWith('# [')) {
if (foundChangelog) {
if (changelogLines.at(-1) === '') {
changelogLines = changelogLines.slice(2, -1);
release.version = devVersion;
} else if (dry) {
info(`[DRY] Bumping ${pkg.name} via git-cliff.`);
release.version = `${pkg.version}.DRY-dev.${Math.round(Date.now() / 1_000)}-${commitHash}`;
} else {
await $`pnpm --filter=${pkg.name} run release --preid "dev.${Math.round(Date.now() / 1_000)}-${commitHash}"`;
// Read again instead of parsing the output to be sure we're matching when checking against npm
const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJson;
release.version = pkgJson.version;
}
}
// Only need changelog for releases published to github
else {
try {
// Find and parse changelog to post in github release
const changelogFile = await file(`${pkg.path}/CHANGELOG.md`).text();

let changelogLines: string[] = [];
let foundChangelog = false;

for (const line of changelogFile.split('\n')) {
if (line.startsWith('# [')) {
if (foundChangelog) {
if (changelogLines.at(-1) === '') {
changelogLines = changelogLines.slice(2, -1);
}

break;
}

break;
foundChangelog = true;
}

foundChangelog = true;
if (foundChangelog) {
changelogLines.push(line);
}
}

if (foundChangelog) {
changelogLines.push(line);
}
release.changelog = changelogLines.join('\n');
} catch (error) {
// Probably just no changelog file but log just in case
warning(`Error parsing changelog for ${pkg.name}, will use auto generated: ${error}`);
}

release.changelog = changelogLines.join('\n');
} catch (error) {
// Probably just no changelog file but log just in case
warning(`Error parsing changelog for ${pkg.name}, will use auto generated: ${error}`);
}

if (pkg.dependencies) {
Expand All @@ -83,8 +123,8 @@ async function getReleaseEntries() {
return releaseEntries;
}

export async function generateReleaseTree(packageName?: string, exclude?: string[]) {
let releaseEntries = await getReleaseEntries();
export async function generateReleaseTree(dev: boolean, dry: boolean, packageName?: string, exclude?: string[]) {
let releaseEntries = await getReleaseEntries(dev, dry);
// Try to early return if the package doesn't have deps
if (packageName) {
const releaseEntry = releaseEntries.find((entry) => entry.name === packageName);
Expand Down
9 changes: 6 additions & 3 deletions packages/actions/src/releasePackages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { releasePackage } from './releasePackage.js';

const excludeInput = getInput('exclude');
let dryInput = false;
let devInput = false;
try {
dryInput = getBooleanInput('dry');
devInput = getBooleanInput('dev');
} catch {
// We're not running in actions
}
Expand All @@ -21,15 +23,16 @@ program
excludeInput ? excludeInput.split(',') : [],
)
.option('--dry', 'skips actual publishing and outputs logs instead', dryInput)
.option('--dev', 'publishes development versions and skips tagging / github releases', devInput)
.parse();

const { exclude, dry } = program.opts<{ dry: boolean; exclude: string[] }>();
const { exclude, dry, dev } = program.opts<{ dev: boolean; dry: boolean; exclude: string[] }>();
const packageName = program.args[0]!;

const tree = await generateReleaseTree(packageName, exclude);
const tree = await generateReleaseTree(dev, dry, packageName, exclude);
for (const branch of tree) {
startGroup(`Releasing ${branch.map((entry) => `${entry.name}@${entry.version}`).join(', ')}`);
await Promise.all(branch.map(async (release) => releasePackage(release, dry)));
await Promise.all(branch.map(async (release) => releasePackage(release, dev, dry)));
endGroup();
}

Expand Down
11 changes: 8 additions & 3 deletions packages/actions/src/releasePackages/releasePackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async function gitTagAndRelease(release: ReleaseEntry, dry: boolean) {
}
}

export async function releasePackage(release: ReleaseEntry, dry: boolean) {
export async function releasePackage(release: ReleaseEntry, dev: boolean, dry: boolean) {
// Sanity check against the registry first
if (await checkRegistry(release)) {
info(`${release.name}@${release.version} already published, skipping.`);
Expand All @@ -52,10 +52,10 @@ export async function releasePackage(release: ReleaseEntry, dry: boolean) {
if (dry) {
info(`[DRY] Releasing ${release.name}@${release.version}`);
} else {
await $`pnpm --filter=${release.name} publish --provenance --no-git-checks`;
await $`pnpm --filter=${release.name} publish --provenance --no-git-checks ${dev ? '--tag=dev' : ''}`;
}

await gitTagAndRelease(release, dry);
if (!dev) await gitTagAndRelease(release, dry);

if (dry) return;

Expand All @@ -76,4 +76,9 @@ export async function releasePackage(release: ReleaseEntry, dry: boolean) {
}
}, 15_000);
});

if (dev) {
// Send and forget, deprecations are less important than releasing other dev versions and can be done manually
void $`pnpm exec npm-deprecate --name "*dev*" --message "This version is deprecated. Please use a newer version." --package ${release.name}`.nothrow();
}
}
22 changes: 15 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 637266d

Please sign in to comment.