diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5d48c733..a7d3cf74 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -48,6 +48,9 @@ jobs: - name: Find packages to publish id: find run: node scripts/find-updated-packages.js ${{ inputs.kind }} ${{ inputs.packages }} + - name: Generate changelog + id: changelog + run: node scripts/generate-changelog.js ${{ steps.find.outputs.modified }} - name: Bump versions id: bump run: node scripts/bump-version.js ${{ inputs.kind }} ${{ steps.find.outputs.modified }} @@ -55,7 +58,7 @@ jobs: run: | git commit -am "v${{ steps.bump.outputs.version }}" git push - - name: Build pacakges and publish to NPM + - name: Build packages and publish to NPM id: build env: GH_RELEASE: 1 @@ -66,6 +69,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} tag: v${{ steps.bump.outputs.version }} name: v${{ steps.bump.outputs.version }} - artifacts: ${{ steps.build.outputs.tarballs }}} + artifacts: ${{ steps.build.outputs.tarballs }} + body: ${{ steps.changelog.outputs.changelog }} draft: false prerelease: false diff --git a/packages/tl/package.json b/packages/tl/package.json index 028aa50c..1e004787 100644 --- a/packages/tl/package.json +++ b/packages/tl/package.json @@ -1,6 +1,6 @@ { "name": "@mtcute/tl", - "version": "0.1.0", + "version": "166.0.0", "description": "TL schema used for mtcute", "main": "index.js", "author": "Alina Sireneva ", diff --git a/scripts/bump-version.js b/scripts/bump-version.js index 4c7fb6ff..7b3ab98f 100644 --- a/scripts/bump-version.js +++ b/scripts/bump-version.js @@ -29,6 +29,7 @@ function bumpVersions(packages, kind) { console.log('[i] Bumping versions to %s', nextVersion) for (const pkg of packages) { + if (pkg === 'tl') continue // own versioning const pkgJson = pkgJsons.find((it) => it.name === `@mtcute/${pkg}`) if (!pkgJson) { diff --git a/scripts/find-updated-packages.js b/scripts/find-updated-packages.js index 94f827fd..424e930e 100644 --- a/scripts/find-updated-packages.js +++ b/scripts/find-updated-packages.js @@ -2,25 +2,7 @@ const cp = require('child_process') const fs = require('fs') const path = require('path') const { listPackages } = require('./publish') - -function getLatestTag() { - try { - const res = cp.execSync('git describe --abbrev=0 --tags', { encoding: 'utf8', stdio: 'pipe' }).trim() - - return res - } catch (e) { - if (e.stderr.match(/^fatal: (No names found|No tags can describe)/i)) { - // no tags found, let's just return the first commit - return cp.execSync('git rev-list --max-parents=0 HEAD', { encoding: 'utf8' }).trim() - } - - throw e - } -} - -function findChangedFilesSince(tag, until = 'HEAD') { - return cp.execSync(`git diff --name-only ${tag} ${until}`, { encoding: 'utf8', stdio: 'pipe' }).trim().split('\n') -} +const { getLatestTag, findChangedFilesSince } = require('./git-utils') getTsconfigFiles.cache = {} @@ -46,9 +28,11 @@ function isMeaningfulChange(pkg, path) { if (getTsconfigFiles(pkg).indexOf(path) > -1) return true // some magic heuristics stuff - if (path.match(/\.md$/i)) return false + if (path.match(/\.(md|\.test\.ts)$/i)) return false if (path.match(/^\/(scripts|dist|tests|private)/i)) return false + console.log('[i] %s: %s is a meaningful change', pkg, path) + // to be safe return true } diff --git a/scripts/generate-changelog.js b/scripts/generate-changelog.js new file mode 100644 index 00000000..de0bc47b --- /dev/null +++ b/scripts/generate-changelog.js @@ -0,0 +1,60 @@ +const fs = require('fs') +const { getLatestTag, getCommitsSince, parseConventionalCommit, findChangedFilesSince } = require('./git-utils') + +function generateChangelog(onlyPackages) { + const byPackage = {} + + for (const commit of getCommitsSince(getLatestTag())) { + const parsed = parseConventionalCommit(commit.msg) + + if (!parsed) { + console.warn('[warn] Failed to parse commit message: %s', commit.msg) + continue + } + + const { type, breaking } = parsed + + if (!type || ['chore', 'ci', 'docs', 'test'].includes(type)) continue + + const changed = findChangedFilesSince(`${commit.hash}~1`, commit.hash) + + const line = `- ${commit.hash}: ${breaking ? '**❗ BREAKING** ' : ''}${commit.msg}` + + for (const file of changed) { + if (!file.startsWith('packages/')) continue + const pkg = file.split('/')[1] + if (onlyPackages && !onlyPackages.includes(pkg)) continue + + if (!byPackage[pkg]) byPackage[pkg] = {} + byPackage[pkg][commit.hash] = line + // console.log('including %s in %s because of %s', commit.hash, pkg, file) + } + } + + let ret = '' + + for (const [pkg, lines] of Object.entries(byPackage)) { + ret += `### ${pkg}\n` + ret += Object.values(lines).join('\n') + ret += '\n\n' + } + + return ret +} + +if (require.main === module) { + let onlyPackages = null + + if (process.argv[2]) { + onlyPackages = process.argv[2].split(',') + } + + const res = generateChangelog(onlyPackages) + + if (process.env.CI && process.env.GITHUB_OUTPUT) { + const delim = `---${require('crypto').randomUUID()}---${require('os').EOL}` + fs.appendFileSync(process.env.GITHUB_OUTPUT, `changelog<<${delim}${res}${delim}`) + } else { + console.log(res) + } +} diff --git a/scripts/git-utils.js b/scripts/git-utils.js new file mode 100644 index 00000000..43123fbf --- /dev/null +++ b/scripts/git-utils.js @@ -0,0 +1,50 @@ +const cp = require('child_process') + +function getLatestTag() { + try { + const res = cp.execSync('git describe --abbrev=0 --tags', { encoding: 'utf8', stdio: 'pipe' }).trim() + + return res + } catch (e) { + if (e.stderr.match(/^fatal: (No names found|No tags can describe)/i)) { + // no tags found, let's just return the first commit + return cp.execSync('git rev-list --max-parents=0 HEAD', { encoding: 'utf8' }).trim() + } + + throw e + } +} + +function findChangedFilesSince(tag, until = 'HEAD') { + return cp.execSync(`git diff --name-only ${tag} ${until}`, { encoding: 'utf8', stdio: 'pipe' }).trim().split('\n') +} + +function getCommitsSince(tag, until = 'HEAD') { + return cp + .execSync(`git log --pretty="format:%H %s" ${tag}..${until}`, { encoding: 'utf8', stdio: 'pipe' }) + .trim() + .split('\n') + .reverse() + .map((it) => { + const [hash, ...msg] = it.split(' ') + + return { hash, msg: msg.join(' ') } + }) +} + +function parseConventionalCommit(msg) { + const match = msg.match(/^(\w+)(?:\(([^)]+)\))?(!?): (.+)$/) + + if (!match) return null + + const [, type, scope, breaking, subject] = match + + return { type, scope, breaking: Boolean(breaking), subject } +} + +module.exports = { + getLatestTag, + findChangedFilesSince, + getCommitsSince, + parseConventionalCommit, +}