Skip to content

Commit

Permalink
feat: add changeset preview action
Browse files Browse the repository at this point in the history
  • Loading branch information
TheUnderScorer committed Sep 19, 2024
1 parent 70a8ebd commit ed53bb9
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`getChangesForVersion parse changelog with multiple versions 1`] = `
[
{
"changes": [
"**events**: Introduce \`PUT\` endpoint for \`/events\` API ([e8bc23f](https://github.com/fingerprintjs/fingerprint-pro-server-api-openapi/commit/e8bc23f115c3b01f9d0d472b02093d0d05d3f4a5))",
"**visits**: Model fixes ([e8bc23f](https://github.com/fingerprintjs/fingerprint-pro-server-api-openapi/commit/e8bc23f115c3b01f9d0d472b02093d0d05d3f4a5))",
],
"type": "Minor Changes",
},
]
`;
9 changes: 9 additions & 0 deletions .github/actions/changeset-release-notes/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: 'Changeset release notes'
description: 'Returns release notes for latest changeset release'
outputs:
release-notes:
description: 'Release notes'

runs:
using: 'node20'
main: 'dist/index.js'
25 changes: 25 additions & 0 deletions .github/actions/changeset-release-notes/changelog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getChangesForVersion } from './changelog'

describe('getChangesForVersion', () => {
it('parse changelog with multiple versions', () => {
const changelogText = `# fingerprint-pro-server-api-openapi
## 1.1.0
### Minor Changes
- **events**: Introduce \`PUT\` endpoint for \`/events\` API ([e8bc23f](https://github.com/fingerprintjs/fingerprint-pro-server-api-openapi/commit/e8bc23f115c3b01f9d0d472b02093d0d05d3f4a5))
- **visits**: Model fixes ([e8bc23f](https://github.com/fingerprintjs/fingerprint-pro-server-api-openapi/commit/e8bc23f115c3b01f9d0d472b02093d0d05d3f4a5))
## 1.0.0
### Minor Changes
- Initial release
`

const result = getChangesForVersion('1.1.0', changelogText)

expect(result).toMatchSnapshot()
})
})
106 changes: 106 additions & 0 deletions .github/actions/changeset-release-notes/changelog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { NewChangeset, PackageJSON } from '@changesets/types'
import { sync as globSync } from 'glob'
import * as fs from 'fs'
import * as path from 'path'

export type ChangeLogEntry = {
version: string
changes: string[]
}

type Project = {
version: string
changelogPath: string
}

export type ReleaseNotes = {
projectName: string
changes: Changes[]
currentVersion: string
}

function listProjects(changesets: NewChangeset[]) {
const ids = new Set<string>(...changesets.map((c) => c.id))
const packageJsons = globSync('**/package.json', {
ignore: ['**/node_modules/**'],
})

const projects = new Map<string, Project>()

packageJsons.forEach((packageJsonPath) => {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageJSON
if (!ids.has(packageJson.name)) {
return
}

const changelogPath = path.join(path.dirname(packageJsonPath), 'CHANGELOG.md')
if (fs.existsSync(changelogPath)) {
projects.set(packageJson.name, {
version: packageJson.version,
changelogPath: changelogPath,
})
}
})

return projects
}

export function listChangesForAllProjects(changesets: NewChangeset[]) {
const notes: ReleaseNotes[] = []

const changelogs = listProjects(changesets)

changelogs.forEach((project, projectName) => {
const changelog = fs.readFileSync(project.changelogPath, 'utf-8')
notes.push({
changes: getChangesForVersion(project.version, changelog),
projectName: projectName,
currentVersion: project.version,
})
})

return notes
}

export type Changes = { type: string; changes: string[] }

export function getChangesForVersion(version: string, changelog: string): Changes[] {
// Split the changelog into lines for easier processing
const lines = changelog.split('\n')

// Initialize variables to track the current version and changes
let currentVersion = ''
let changeType = ''
const changes: Changes[] = []

for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim()

// Check for a version line (e.g., "## 1.1.0")
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/)
if (versionMatch) {
currentVersion = versionMatch[1]
// If the current version matches the requested version, continue processing
if (currentVersion === version) {
continue
} else if (changes.length > 0) {
// If we've already collected changes for the desired version, break the loop
break
}
}

// Check for a change type line (e.g., "### Minor Changes")
if (currentVersion === version && line.startsWith('###')) {
changeType = line.replace(/^###\s*/, '')
changes.push({ type: changeType, changes: [] })
}

// Collect changes under the current change type
if (currentVersion === version && line.startsWith('-')) {
// Add the change to the last changeType entry
changes[changes.length - 1].changes.push(line.slice(2).trim())
}
}

return changes
}
42 changes: 42 additions & 0 deletions .github/actions/changeset-release-notes/changeset-release-notes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as fs from 'fs'
import * as path from 'path'
import { PackageJSON } from '@changesets/types'
import * as core from '@actions/core'
import * as cp from 'child_process'
import readChangesets from '@changesets/read'
import { getReleaseNotes } from './notes'

function getCurrentVersion() {
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'))

return (pkg as PackageJSON).version
}

function doVersion() {
const lastVersion = getCurrentVersion()
cp.execSync('pnpm exec changeset version')
const nextVersion = getCurrentVersion()

return lastVersion !== nextVersion
}

async function main() {
const changesets = await readChangesets(process.cwd())
if (!changesets.length) {
return
}

if (!doVersion()) {
return
}

const notes = getReleaseNotes(changesets)

if (notes) {
core.setOutput('release-notes', notes)
}
}

main().catch((err) => {
core.setFailed(err)
})
22 changes: 22 additions & 0 deletions .github/actions/changeset-release-notes/notes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NewChangeset } from '@changesets/types'
import { listChangesForAllProjects } from './changelog'

export function getReleaseNotes(changesets: NewChangeset[]) {
let result = ''

const changes = listChangesForAllProjects(changesets)

changes.forEach((change) => {
result += `## ${change.projectName}@${change.currentVersion}\n\n`

change.changes.forEach((change) => {
result += `### ${change.type}`

change.changes.forEach((description) => {
result += `\n- ${description}`
})
})
})

return result
}
48 changes: 48 additions & 0 deletions .github/workflows/preview-changeset-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: 'Preview changeset release'
on:
workflow_call:
inputs:
pr-title:
description: Title of created PR
required: true
type: string
node-version:
description: 'Node version to use'
required: false
type: string
default: 'lts/*'

jobs:
preview:
name: Generate release notes
runs-on: ubuntu-latest
if: ${{ !contains('[changeset] ', inputs.pr-title) }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: 'Install latest node version'
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}

- name: 'Get release notes'
id: notes
# TODO Replace with this
#uses: fingerprintjs/dx-team-toolkit/.github/actions/update-sdk-schema@v1
uses: ./.github/actions/changeset-release-notes

- name: 'Add comment to PR'
if: steps.notes.outputs.release-notes != ''
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31
with:
header: Release notes
recreate: true
message: ${{ steps.notes.outputs.release-notes }}

- name: 'Add release notes preview to the job summary'
if: steps.notes.outputs.release-notes != ''
run: |
echo "${{ steps.notes.outputs.release-notes }}" >> $GITHUB_STEP_SUMMARY
17 changes: 17 additions & 0 deletions .github/workflows/test-preview-changeset-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: 'Preview changeset release'
on:
pull_request:

permissions:
pull-requests: write
contents: write

jobs:
preview:
name: Preview changeset release
uses: ./.github/workflows/preview-changeset-release.yml
with:
pr-title: ${{ github.event.pull_request.title }}



26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This monorepo stores reusable configurations for tools like ESLint, Prettier, et
- [9. Create Prerelease Branch and Force Push](#9-create-prerelease-branch-and-force-push)
- [10. Release SDKs using changesets](#10-release-sdks-using-changesets)
- [11. Sync server-side SDK schema with OpenAPI release](#11-sync-server-side-sdk-schema-with-openapi-release)
- [12. Preview changeset release](#12-preview-changeset-release)

### 1. Run tests and show coverage diff

Expand Down Expand Up @@ -613,3 +614,28 @@ jobs:
secrets:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
```

### 12. Preview changeset release

This reusable workflow processes parsed [changesets](https://github.com/changesets/changesets) and generates preview of release notes.

#### Prerequisites:

1. Project is properly configured to release using changesets.

#### Example of usage:

```yaml
name: 'Preview changeset release'
on:
pull_request:
permissions:
pull-requests: write
contents: write
jobs:
preview:
name: Preview changeset release
uses: fingerprintjs/dx-team-toolkit/.github/workflows/preview-changeset-release.yml@1
```
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"prepare": "husky install",
"build": "pnpm run -r build",
"build:actions": "ncc build .github/actions/update-sdk-schema/update-schema.ts -o .github/actions/update-sdk-schema/dist",
"build:actions": "bash ./scripts/build-actions.sh",
"test": "jest",
"test:coverage": "jest --coverage",
"docs": "mkdir docs && cp README.md ./docs/README.md",
Expand All @@ -17,6 +17,8 @@
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.1",
"@changesets/read": "^0.6.1",
"@changesets/types": "^6.0.0",
"@commitlint/cli": "^17.8.1",
"@fingerprintjs/commit-lint-dx-team": "workspace:local",
"@fingerprintjs/conventional-changelog-dx-team": "workspace:local",
Expand All @@ -32,6 +34,7 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-prettier": "^4.2.1",
"glob": "^11.0.0",
"human-id": "^4.1.1",
"husky": "^8.0.3",
"jest": "^29.7.0",
Expand Down
Loading

0 comments on commit ed53bb9

Please sign in to comment.