From 958f7ff81cca8bb809d22bf765c5fda55714b836 Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Tue, 31 Oct 2023 21:17:39 +0300 Subject: [PATCH] ci: release building --- .github/workflows/docs.yaml | 1 + .github/workflows/release.yaml | 71 +++++++++++ .github/workflows/test.yaml | 1 + .npmrc | 1 - e2e/cli.sh | 1 + package.json | 2 +- packages/client/package.json | 2 +- packages/core/package.json | 2 +- packages/create-bot/package.json | 2 +- packages/create-bot/template/.npmrc | 1 - packages/create-bot/template/package.json.hbs | 2 +- packages/crypto-node/package.json | 2 +- packages/crypto/package.json | 5 - packages/dispatcher/package.json | 2 +- packages/file-id/package.json | 2 +- packages/html-parser/package.json | 2 +- packages/http-proxy/package.json | 2 +- packages/i18n/package.json | 2 +- packages/markdown-parser/package.json | 2 +- packages/mtproxy/package.json | 2 +- packages/node/package.json | 2 +- packages/socks-proxy/package.json | 2 +- packages/sqlite/package.json | 2 +- packages/tl-runtime/package.json | 2 +- packages/tl-utils/package.json | 2 +- scripts/build-package.js | 12 +- scripts/bump-version.js | 81 ++++++++++++ scripts/find-updated-packages.js | 115 ++++++++++++++++++ scripts/publish.js | 25 +++- 29 files changed, 321 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/release.yaml delete mode 100644 .npmrc delete mode 100644 packages/create-bot/template/.npmrc delete mode 100644 packages/crypto/package.json create mode 100644 scripts/bump-version.js create mode 100644 scripts/find-updated-packages.js diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 2da9c592..b3717582 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -17,6 +17,7 @@ concurrency: jobs: build: + if: github.repository == 'mtcute/mtcute' # do not run on forks runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..5d48c733 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,71 @@ +name: Run release + +on: + workflow_dispatch: + inputs: + packages: + description: 'Packages to release (comma separated names, `all` or `updated`)' + required: true + default: 'updated' + kind: + description: 'Release kind (major, minor, patch)' + required: true + default: 'patch' + type: choice + options: + - major + - minor + - patch + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + token: ${{ secrets.BOT_PAT }} + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: "18" + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + - run: pnpm install --frozen-lockfile + - name: 'TL codegen' + run: pnpm -C packages/tl run gen-code + - name: Initialize configs + run: | + git config user.name "mtcute-bot" + git config user.email mtcute-bot@tei.su + npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }} + - name: Find packages to publish + id: find + run: node scripts/find-updated-packages.js ${{ inputs.kind }} ${{ inputs.packages }} + - name: Bump versions + id: bump + run: node scripts/bump-version.js ${{ inputs.kind }} ${{ steps.find.outputs.modified }} + - name: Commit version bumps + run: | + git commit -am "v${{ steps.bump.outputs.version }}" + git push + - name: Build pacakges and publish to NPM + id: build + env: + GH_RELEASE: 1 + run: node scripts/publish.js ${{ steps.find.outputs.modified }} + - name: GitHub Release + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: v${{ steps.bump.outputs.version }} + name: v${{ steps.bump.outputs.version }} + artifacts: ${{ steps.build.outputs.tarballs }}} + draft: false + prerelease: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 47366e1b..df9d456b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,6 +10,7 @@ on: jobs: test: runs-on: ubuntu-latest + if: github.actor != 'mtcute-bot' # do not after release strategy: matrix: diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 68335980..00000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@mtcute:registry=https://npm.tei.su \ No newline at end of file diff --git a/e2e/cli.sh b/e2e/cli.sh index 27609223..a92e8466 100755 --- a/e2e/cli.sh +++ b/e2e/cli.sh @@ -30,6 +30,7 @@ case "$method" in pnpm install ;; "ci") + set -eaux chmod -R 777 .verdaccio docker compose up -d verdaccio docker compose run --rm --build build diff --git a/package.json b/package.json index b266cc14..438ca399 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mtcute", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Type-safe library for MTProto (Telegram API) for browser and NodeJS", "license": "MIT", "author": "Alina Sireneva ", diff --git a/packages/client/package.json b/packages/client/package.json index 19c73bf9..fce1f60a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/client", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "High-level API over @mtcute/core", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/core/package.json b/packages/core/package.json index afce4803..f83f41c6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/core", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Core functions and base MTProto client", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/create-bot/package.json b/packages/create-bot/package.json index e9c4fe3f..cc83fbdf 100644 --- a/packages/create-bot/package.json +++ b/packages/create-bot/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/create-bot", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Bot starter kit for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/create-bot/template/.npmrc b/packages/create-bot/template/.npmrc deleted file mode 100644 index 68335980..00000000 --- a/packages/create-bot/template/.npmrc +++ /dev/null @@ -1 +0,0 @@ -@mtcute:registry=https://npm.tei.su \ No newline at end of file diff --git a/packages/create-bot/template/package.json.hbs b/packages/create-bot/template/package.json.hbs index b15d3894..219b02c5 100644 --- a/packages/create-bot/template/package.json.hbs +++ b/packages/create-bot/template/package.json.hbs @@ -1,7 +1,7 @@ { "name": "{{ name }}", "license": "MIT", - "version": "0.1.0", + "version": "0.0.0", {{#if features.typescript}} "main": "dist/index.js", {{else}} diff --git a/packages/crypto-node/package.json b/packages/crypto-node/package.json index d5160255..808696bd 100644 --- a/packages/crypto-node/package.json +++ b/packages/crypto-node/package.json @@ -1,6 +1,6 @@ { "name": "@mtcute/crypto-node", - "version": "0.1.0", + "version": "0.0.0", "description": "Native crypto implementation for NodeJS", "author": "Alina Sireneva ", "main": "src/index.ts", diff --git a/packages/crypto/package.json b/packages/crypto/package.json deleted file mode 100644 index f9ebb5d2..00000000 --- a/packages/crypto/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@mtcute/crypto", - "private": true, - "version": "1.0.0" -} diff --git a/packages/dispatcher/package.json b/packages/dispatcher/package.json index a1ee913c..831442e2 100644 --- a/packages/dispatcher/package.json +++ b/packages/dispatcher/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/dispatcher", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Updates dispatcher and bot framework for @mtcute/client", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/file-id/package.json b/packages/file-id/package.json index 4d08f506..069f0a00 100644 --- a/packages/file-id/package.json +++ b/packages/file-id/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/file-id", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Support for TDLib and Bot API file ID for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/html-parser/package.json b/packages/html-parser/package.json index 909fa04f..0c3db74e 100644 --- a/packages/html-parser/package.json +++ b/packages/html-parser/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/html-parser", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "HTML entities parser for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/http-proxy/package.json b/packages/http-proxy/package.json index 59ea8ba0..f4bb0bc1 100644 --- a/packages/http-proxy/package.json +++ b/packages/http-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/http-proxy", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "HTTP(S) proxy support for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 15a72269..f1d168e8 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/i18n", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "I18n for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/markdown-parser/package.json b/packages/markdown-parser/package.json index 958e58ba..3479192f 100644 --- a/packages/markdown-parser/package.json +++ b/packages/markdown-parser/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/markdown-parser", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Markdown entities parser for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/mtproxy/package.json b/packages/mtproxy/package.json index 510d2db8..1d86ede2 100644 --- a/packages/mtproxy/package.json +++ b/packages/mtproxy/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/mtproxy", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "MTProto proxy (MTProxy) support for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/node/package.json b/packages/node/package.json index 410e4b49..2da065ff 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/node", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Meta-package for Node JS", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/socks-proxy/package.json b/packages/socks-proxy/package.json index 27034228..08bb498e 100644 --- a/packages/socks-proxy/package.json +++ b/packages/socks-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/socks-proxy", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "SOCKS4/5 proxy support for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json index e39ff014..2c60dc93 100644 --- a/packages/sqlite/package.json +++ b/packages/sqlite/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/sqlite", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "SQLite-based storage for mtcute", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/tl-runtime/package.json b/packages/tl-runtime/package.json index f12a6802..dabe94cd 100644 --- a/packages/tl-runtime/package.json +++ b/packages/tl-runtime/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/tl-runtime", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Runtime for TL", "author": "Alina Sireneva ", "license": "MIT", diff --git a/packages/tl-utils/package.json b/packages/tl-utils/package.json index 2289db36..d37af897 100644 --- a/packages/tl-utils/package.json +++ b/packages/tl-utils/package.json @@ -1,7 +1,7 @@ { "name": "@mtcute/tl-utils", "private": true, - "version": "0.1.0", + "version": "0.0.0", "description": "Utils for working with TL schema", "author": "Alina Sireneva ", "license": "MIT", diff --git a/scripts/build-package.js b/scripts/build-package.js index 95614f55..c8a2722b 100644 --- a/scripts/build-package.js +++ b/scripts/build-package.js @@ -151,7 +151,17 @@ function buildPackageJson() { const value = dependencies[name] if (value.startsWith('workspace:')) { - dependencies[name] = value.replace('workspace:', '') + if (value !== 'workspace:^') { + throw new Error( + `Cannot replace workspace dependency ${name} with ${value} - only workspace:^ is supported`, + ) + } + if (!name.startsWith('@mtcute/')) { + throw new Error(`Cannot replace workspace dependency ${name} - only @mtcute/* is supported`) + } + + const depVersion = require(path.join(packageDir, '..', name.slice(8), 'package.json')).version + dependencies[name] = `^${depVersion}` } } } diff --git a/scripts/bump-version.js b/scripts/bump-version.js new file mode 100644 index 00000000..4c7fb6ff --- /dev/null +++ b/scripts/bump-version.js @@ -0,0 +1,81 @@ +const fs = require('fs') +const path = require('path') +const semver = require('semver') + +function collectPackageJsons() { + return fs + .readdirSync(path.join(__dirname, '../packages')) + .filter((s) => !s.startsWith('.')) + .map((name) => { + try { + return JSON.parse(fs.readFileSync(path.join(__dirname, '../packages', name, 'package.json'), 'utf-8')) + } catch (e) { + if (e.code !== 'ENOENT') throw e + + return null + } + }) + .filter(Boolean) +} + +function bumpVersions(packages, kind) { + const pkgJsons = collectPackageJsons() + const maxVersion = pkgJsons + .filter((it) => it.name !== '@mtcute/tl') + .map((it) => it.version) + .sort(semver.rcompare)[0] + + const nextVersion = semver.inc(maxVersion, kind) + console.log('[i] Bumping versions to %s', nextVersion) + + for (const pkg of packages) { + const pkgJson = pkgJsons.find((it) => it.name === `@mtcute/${pkg}`) + + if (!pkgJson) { + console.error(`Package ${pkg} not found!`) + process.exit(1) + } + + pkgJson.version = nextVersion + fs.writeFileSync( + path.join(__dirname, '../packages', pkg, 'package.json'), + JSON.stringify(pkgJson, null, 4) + '\n', + ) + } + + const rootPkgJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8')) + rootPkgJson.version = nextVersion + fs.writeFileSync(path.join(__dirname, '../package.json'), JSON.stringify(rootPkgJson, null, 4) + '\n') + + return nextVersion +} + +if (require.main === module) { + const kind = process.argv[2] + const packages = process.argv[3] + + if (!packages || !kind) { + console.log('Usage: bump-version.js ') + process.exit(1) + } + + const packagesList = packages.split(',') + + if (packagesList.length === 0) { + console.error('No packages specified!') + process.exit(1) + } + + if (kind === 'major' && packagesList.length !== collectPackageJsons().length) { + console.error('Cannot bump major version only for some packages!') + process.exit(1) + } + + const ver = bumpVersions(packagesList, kind) + + if (process.env.GITHUB_OUTPUT) { + fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${ver}${require('os').EOL}`) + } +} + +module.exports = { bumpVersions } diff --git a/scripts/find-updated-packages.js b/scripts/find-updated-packages.js new file mode 100644 index 00000000..94f827fd --- /dev/null +++ b/scripts/find-updated-packages.js @@ -0,0 +1,115 @@ +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') +} + +getTsconfigFiles.cache = {} + +function getTsconfigFiles(pkg) { + if (!fs.existsSync(path.join(__dirname, `../packages/${pkg}/tsconfig.json`))) { + throw new Error(`[!] ${pkg} does not have a tsconfig.json`) + } + if (pkg in getTsconfigFiles.cache) return getTsconfigFiles.cache[pkg] + + console.log('[i] Getting tsconfig files for %s', pkg) + const res = cp.execSync('pnpm exec tsc --showConfig', { + encoding: 'utf8', + stdio: 'pipe', + cwd: path.join(__dirname, `../packages/${pkg}`), + }) + + const json = JSON.parse(res) + + return (getTsconfigFiles.cache[pkg] = json.files.map((it) => it.replace(/^\.\//, ''))) +} + +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(/^\/(scripts|dist|tests|private)/i)) return false + + // to be safe + return true +} + +function findChangedPackagesSince(tag, until) { + const packages = new Set(listPackages()) + const changedFiles = findChangedFilesSince(tag, until) + + const changedPackages = new Set() + + for (const file of changedFiles) { + const [dir, pkgname, ...rest] = file.split('/') + if (dir !== 'packages') continue + if (!packages.has(pkgname)) continue + + // already checked, no need to check again + if (changedPackages.has(pkgname)) continue + + const relpath = rest.join('/') + + if (isMeaningfulChange(pkgname, relpath)) { + changedPackages.add(pkgname) + } + } + + return Array.from(changedPackages) +} + +module.exports = { findChangedPackagesSince, getLatestTag } + +if (require.main === module && process.env.CI && process.env.GITHUB_OUTPUT) { + const kind = process.argv[2] + const input = process.argv[3] + + if (!input) { + // for simpler flow, one can pass all or package list as the first argument, + // and they will be returned as is, so that we can later simply + // use the outputs of this script + console.log('Usage: find-updated-packages.js ') + process.exit(1) + } + + if (kind === 'major' && input !== 'all') { + throw new Error('For major releases, all packages must be published') + } + + console.log('[i] Determining packages to publish...') + + let res + + if (input === 'all') { + res = listPackages() + } else if (input === 'updated') { + const tag = getLatestTag() + console.log('[i] Latest tag is %s', tag) + + res = findChangedPackagesSince(tag) + } else { + res = input.split(',') + } + + console.log('[i] Will publish:', res) + fs.appendFileSync(process.env.GITHUB_OUTPUT, `modified=${res.join(',')}${require('os').EOL}`) +} diff --git a/scripts/publish.js b/scripts/publish.js index 72c3ded1..2a168e35 100644 --- a/scripts/publish.js +++ b/scripts/publish.js @@ -2,8 +2,7 @@ const fs = require('fs') const path = require('path') const cp = require('child_process') -// const REGISTRY = 'https://registry.npmjs.org' -const REGISTRY = process.env.REGISTRY || 'https://npm.tei.su/' +const REGISTRY = process.env.REGISTRY || 'https://registry.npmjs.org' exports.REGISTRY = REGISTRY async function checkVersion(name, version, retry = 0) { @@ -82,6 +81,8 @@ async function main(arg = process.argv[2]) { console.log('[i] Using registry %s', REGISTRY) + const published = [] + if (arg === 'all' || arg === 'updated') { for (const pkg of listPackages()) { if (arg === 'updated') { @@ -95,9 +96,27 @@ async function main(arg = process.argv[2]) { } await publishSinglePackage(pkg) + published.push(pkg) } } else { - await publishSinglePackage(arg) + for (const pkg of arg.split(',')) { + await publishSinglePackage(pkg) + published.push(pkg) + } + } + + if (process.env.GH_RELEASE) { + // we should also generate tgz files for all published packages + // for a github release, and also generate a title + const tarballs = [] + + for (const pkg of published) { + const dir = path.join(__dirname, '../packages', pkg, 'dist') + const tar = cp.execSync('npm pack -q', { cwd: dir }) + tarballs.push(path.join(dir, tar.toString().trim())) + } + + fs.writeFileSync(process.env.GITHUB_OUTPUT, `tarballs=${tarballs.join(',')}\n`, { flag: 'a' }) } }