Skip to content

Commit da71777

Browse files
committed
ci(release): add dev release handling
1 parent d526ed9 commit da71777

File tree

8 files changed

+114
-88
lines changed

8 files changed

+114
-88
lines changed

.github/workflows/publish-dev.yml

+11-49
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,13 @@ on:
33
schedule:
44
- cron: '0 */12 * * *'
55
workflow_dispatch:
6+
inputs:
7+
dry_run:
8+
type: boolean
9+
default: false
610
jobs:
711
npm-publish:
812
name: npm publish
9-
strategy:
10-
fail-fast: false
11-
matrix:
12-
include:
13-
- package: '@discordjs/brokers'
14-
folder: 'brokers'
15-
- package: '@discordjs/builders'
16-
folder: 'builders'
17-
- package: '@discordjs/collection'
18-
folder: 'collection'
19-
- package: '@discordjs/core'
20-
folder: 'core'
21-
- package: '@discordjs/formatters'
22-
folder: 'formatters'
23-
- package: 'discord.js'
24-
folder: 'discord.js'
25-
- package: '@discordjs/next'
26-
folder: 'next'
27-
- package: '@discordjs/proxy'
28-
folder: 'proxy'
29-
- package: '@discordjs/rest'
30-
folder: 'rest'
31-
- package: '@discordjs/util'
32-
folder: 'util'
33-
- package: '@discordjs/voice'
34-
folder: 'voice'
35-
- package: '@discordjs/ws'
36-
folder: 'ws'
3713
runs-on: ubuntu-latest
3814
permissions:
3915
id-token: write
@@ -53,32 +29,18 @@ jobs:
5329
node-version: 20
5430
registry-url: https://registry.npmjs.org/
5531

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

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

72-
- name: Publish package
73-
if: steps.release-check.outputs.release == '1'
74-
run: |
75-
pnpm --filter=${{ matrix.package }} run release --preid "dev.$(date +%s)-$(git rev-parse --short HEAD)"
76-
pnpm --filter=${{ matrix.package }} publish --provenance --no-git-checks --tag dev || true
77-
env:
78-
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
79-
80-
- name: Deprecate prior development releases
81-
if: steps.release-check.outputs.release == '1'
82-
run: pnpm exec npm-deprecate --name "*dev*" --message "This version is deprecated. Please use a newer version." --package ${{ matrix.package }}
38+
- name: Pubish packages
39+
uses: ./packages/actions/src/releasePackages
40+
with:
41+
exclude: 'create-discord-bot,@discordjs/docgen'
42+
dry: ${{ inputs.dry_run }}
43+
dev: true
8344
env:
8445
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
46+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/release.yml

+4
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,16 @@ jobs:
5353
- name: Install dependencies
5454
uses: ./packages/actions/src/pnpmCache
5555

56+
- name: Build dependencies
57+
run: pnpm run build
58+
5659
- name: Release packages
5760
uses: ./packages/actions/src/releasePackages
5861
with:
5962
package: ${{ inputs.package }}
6063
exclude: ${{ inputs.exclude }}
6164
dry: ${{ inputs.dry_run }}
65+
dev: ${{ inputs.dev }}
6266
env:
6367
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
6468
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

packages/actions/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"undici": "6.18.2"
5555
},
5656
"devDependencies": {
57+
"@npm/types": "^1.0.2",
5758
"@types/bun": "^1.1.4",
5859
"@types/node": "18.18.8",
5960
"@vitest/coverage-v8": "^1.6.0",

packages/actions/src/releasePackages/action.yml

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: 'Release Packages'
22
description: 'Tags and releases any unreleased packages'
33
inputs:
4+
dev:
5+
description: 'Releases development versions of packages (skips tagging and github releases)'
6+
default: false
47
dry:
58
descrption: 'Perform a dry run that skips publishing and outputs logs indicating what would have happened'
69
default: false

packages/actions/src/releasePackages/generateReleaseTree.ts

+66-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { warning } from '@actions/core';
2-
import { $, file } from 'bun';
1+
import { info, warning } from '@actions/core';
2+
import type { PackageJson, PackumentVersion } from '@npm/types';
3+
import { $, file, write } from 'bun';
34

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

@@ -25,11 +26,24 @@ export interface ReleaseEntry {
2526
version: string;
2627
}
2728

28-
async function getReleaseEntries() {
29+
async function fetchDevVersion(pkg: string) {
30+
try {
31+
const res = await fetch(`https://registry.npmjs.org/${pkg}/dev`);
32+
if (!res.ok) return null;
33+
const packument = (await res.json()) as PackumentVersion;
34+
return packument.version;
35+
} catch {
36+
return null;
37+
}
38+
}
39+
40+
async function getReleaseEntries(dev: boolean, dry: boolean) {
2941
const releaseEntries: ReleaseEntry[] = [];
3042
const packageList: pnpmTree[] =
3143
await $`pnpm list --recursive --only-projects --filter {packages/\*} --prod --json`.json();
3244

45+
const commitHash = (await $`git rev-parse --short HEAD`.text()).trim();
46+
3347
for (const pkg of packageList) {
3448
// Don't release private packages ever (npm will error anyways)
3549
if (pkg.private) continue;
@@ -42,35 +56,61 @@ async function getReleaseEntries() {
4256
version: pkg.version,
4357
};
4458

45-
try {
46-
// Find and parse changelog to post in github release
47-
const changelogFile = await file(`${pkg.path}/CHANGELOG.md`).text();
48-
49-
let changelogLines: string[] = [];
50-
let foundChangelog = false;
59+
if (dev) {
60+
const devVersion = await fetchDevVersion(pkg.name);
61+
if (devVersion?.endsWith(commitHash)) {
62+
// Write the currently released dev version so when pnpm publish runs on dependents they depend on the dev versions
63+
if (dry) {
64+
info(`[DRY] ${pkg.name}@${devVersion} already released. Editing package.json version.`);
65+
} else {
66+
const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJson;
67+
pkgJson.version = devVersion;
68+
await write(`${pkg.path}/package.json`, JSON.stringify(pkgJson, null, '\t'));
69+
}
5170

52-
for (const line of changelogFile.split('\n')) {
53-
if (line.startsWith('# [')) {
54-
if (foundChangelog) {
55-
if (changelogLines.at(-1) === '') {
56-
changelogLines = changelogLines.slice(2, -1);
71+
release.version = devVersion;
72+
} else if (dry) {
73+
info(`[DRY] Bumping ${pkg.name} via git-cliff.`);
74+
release.version = `${pkg.version}.DRY-dev.${Math.round(Date.now() / 1_000)}-${commitHash}`;
75+
} else {
76+
await $`pnpm --filter=${pkg.name} run release --preid "dev.${Math.round(Date.now() / 1_000)}-${commitHash}"`;
77+
// Read again instead of parsing the output to be sure we're matching when checking against npm
78+
const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJson;
79+
release.version = pkgJson.version;
80+
}
81+
}
82+
// Only need changelog for releases published to github
83+
else {
84+
try {
85+
// Find and parse changelog to post in github release
86+
const changelogFile = await file(`${pkg.path}/CHANGELOG.md`).text();
87+
88+
let changelogLines: string[] = [];
89+
let foundChangelog = false;
90+
91+
for (const line of changelogFile.split('\n')) {
92+
if (line.startsWith('# [')) {
93+
if (foundChangelog) {
94+
if (changelogLines.at(-1) === '') {
95+
changelogLines = changelogLines.slice(2, -1);
96+
}
97+
98+
break;
5799
}
58100

59-
break;
101+
foundChangelog = true;
60102
}
61103

62-
foundChangelog = true;
104+
if (foundChangelog) {
105+
changelogLines.push(line);
106+
}
63107
}
64108

65-
if (foundChangelog) {
66-
changelogLines.push(line);
67-
}
109+
release.changelog = changelogLines.join('\n');
110+
} catch (error) {
111+
// Probably just no changelog file but log just in case
112+
warning(`Error parsing changelog for ${pkg.name}, will use auto generated: ${error}`);
68113
}
69-
70-
release.changelog = changelogLines.join('\n');
71-
} catch (error) {
72-
// Probably just no changelog file but log just in case
73-
warning(`Error parsing changelog for ${pkg.name}, will use auto generated: ${error}`);
74114
}
75115

76116
if (pkg.dependencies) {
@@ -83,8 +123,8 @@ async function getReleaseEntries() {
83123
return releaseEntries;
84124
}
85125

86-
export async function generateReleaseTree(packageName?: string, exclude?: string[]) {
87-
let releaseEntries = await getReleaseEntries();
126+
export async function generateReleaseTree(dev: boolean, dry: boolean, packageName?: string, exclude?: string[]) {
127+
let releaseEntries = await getReleaseEntries(dev, dry);
88128
// Try to early return if the package doesn't have deps
89129
if (packageName) {
90130
const releaseEntry = releaseEntries.find((entry) => entry.name === packageName);

packages/actions/src/releasePackages/index.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { releasePackage } from './releasePackage.js';
55

66
const excludeInput = getInput('exclude');
77
let dryInput = false;
8+
let devInput = false;
89
try {
910
dryInput = getBooleanInput('dry');
11+
devInput = getBooleanInput('dev');
1012
} catch {
1113
// We're not running in actions
1214
}
@@ -21,15 +23,16 @@ program
2123
excludeInput ? excludeInput.split(',') : [],
2224
)
2325
.option('--dry', 'skips actual publishing and outputs logs instead', dryInput)
26+
.option('--dev', 'publishes development versions and skips tagging / github releases', devInput)
2427
.parse();
2528

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

29-
const tree = await generateReleaseTree(packageName, exclude);
32+
const tree = await generateReleaseTree(dev, dry, packageName, exclude);
3033
for (const branch of tree) {
3134
startGroup(`Releasing ${branch.map((entry) => `${entry.name}@${entry.version}`).join(', ')}`);
32-
await Promise.all(branch.map(async (release) => releasePackage(release, dry)));
35+
await Promise.all(branch.map(async (release) => releasePackage(release, dev, dry)));
3336
endGroup();
3437
}
3538

packages/actions/src/releasePackages/releasePackage.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async function gitTagAndRelease(release: ReleaseEntry, dry: boolean) {
4242
}
4343
}
4444

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

58-
await gitTagAndRelease(release, dry);
58+
if (!dev) await gitTagAndRelease(release, dry);
5959

6060
if (dry) return;
6161

@@ -76,4 +76,9 @@ export async function releasePackage(release: ReleaseEntry, dry: boolean) {
7676
}
7777
}, 15_000);
7878
});
79+
80+
if (dev) {
81+
// Send and forget, deprecations are less important than releasing other dev versions and can be done manually
82+
void $`pnpm exec npm-deprecate --name "*dev*" --message "This version is deprecated. Please use a newer version." --package ${release.name}`.nothrow();
83+
}
7984
}

0 commit comments

Comments
 (0)