From ec738c27ea6f5545ebc2123ea245ebf7519135e4 Mon Sep 17 00:00:00 2001 From: Zihua Li Date: Tue, 16 Jan 2024 20:03:27 +0800 Subject: [PATCH] Set up release actions --- .eslintrc.json | 2 +- .github/workflows/_test.yml | 17 ++++ .github/workflows/main.yml | 13 +-- .github/workflows/release.yml | 42 +++++++++ CHANGELOG.md | 4 + scripts/release.js | 168 ++++++++++++++++++++++++++++++++++ 6 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/_test.yml create mode 100644 .github/workflows/release.yml create mode 100755 scripts/release.js diff --git a/.eslintrc.json b/.eslintrc.json index 05e8809..9574a7d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,7 @@ "plugin:prettier/recommended", "plugin:@typescript-eslint/recommended" ], - "ignorePatterns": ["vite.config.ts"], + "ignorePatterns": ["vite.config.ts", "scripts"], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "parserOptions": { diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml new file mode 100644 index 0000000..15d5357 --- /dev/null +++ b/.github/workflows/_test.yml @@ -0,0 +1,17 @@ +name: Tests +on: + workflow_call: +jobs: + test: + runs-on: ubuntu-latest + # https://playwright.dev/docs/ci#via-containers + container: + image: mcr.microsoft.com/playwright:v1.36.0-jammy + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 16 + - run: npm ci + - run: npm run lint + - run: npm test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c204f42..49899fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,15 +7,4 @@ on: jobs: test: - runs-on: ubuntu-latest - # https://playwright.dev/docs/ci#via-containers - container: - image: mcr.microsoft.com/playwright:v1.36.0-jammy - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - run: npm ci - - run: npm run lint - - run: npm test + uses: ./.github/workflows/_test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4765982 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: 'npm version. Examples: "2.0.0", "2.0.0-beta.0". To deploy an experimental version, type "experimental".' + default: 'experimental' + required: true + dry-run: + description: 'Only create a tarball, do not publish to npm or create a release on GitHub.' + type: boolean + required: true + +jobs: + test: + uses: ./.github/workflows/_test.yml + + release: + runs-on: ubuntu-latest + needs: test + + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + + - run: npm ci + - run: ./scripts/release.js --version ${{ github.event.inputs.version }} ${{ github.event.inputs.dry-run == 'true' && '--dry-run' || '' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Archive npm package tarball + uses: actions/upload-artifact@v3 + with: + name: npm + path: | + packages/quill/dist/*.tgz diff --git a/CHANGELOG.md b/CHANGELOG.md index c6cc92f..95c1901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [Unreleased] + +- Improved typing for Attributor and Registry. + # 3.0.0-alpha.1 - Fix ESM bundle not exposed in package.json. diff --git a/scripts/release.js b/scripts/release.js new file mode 100755 index 0000000..1a24b20 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,168 @@ +#!/usr/bin/env node + +const exec = require('node:child_process').execSync; +const fs = require('node:fs'); +const crypto = require('node:crypto'); +const { parseArgs } = require('node:util'); + +const args = parseArgs({ + options: { + version: { type: 'string' }, + 'dry-run': { type: 'boolean', default: false }, + }, +}); + +const dryRun = args.values['dry-run']; + +if (dryRun) { + console.log('Running in "dry-run" mode'); +} + +const exitWithError = (message) => { + console.error(`Exit with error: ${message}`); + process.exit(1); +}; + +if (!process.env.CI) { + exitWithError('The script should only be run in CI'); +} + +exec('git config --global user.name "Zihua Li"'); +exec('git config --global user.email "635902+luin@users.noreply.github.com"'); + +/* + * Check that the git working directory is clean + */ +if (exec('git status --porcelain').length) { + exitWithError( + 'Make sure the git working directory is clean before releasing', + ); +} + +/* + * Check that the version is valid. Also extract the dist-tag from the version. + */ +const [version, distTag] = (() => { + const inputVersion = args.values.version; + if (!inputVersion) { + exitWithError('Missing required argument: "--version "'); + } + + if (inputVersion === 'experimental') { + const randomId = crypto + .randomBytes(Math.ceil(9 / 2)) + .toString('hex') + .slice(0, 9); + + return [ + `0.0.0-experimental-${randomId}-${new Date() + .toISOString() + .slice(0, 10) + .replace(/-/g, '')}`, + 'experimental', + ]; + } + + const match = inputVersion.match( + /^(?:[0-9]+\.){2}(?:[0-9]+)(?:-(dev|alpha|beta|rc)\.[0-9]+)?$/, + ); + if (!match) { + exitWithError(`Invalid version: ${inputVersion}`); + } + + return [inputVersion, match[1] || 'latest']; +})(); + +/* + * Get the current version + */ +const currentVersion = JSON.parse( + fs.readFileSync('package.json', 'utf-8'), +).version; +console.log( + `Releasing with version: ${currentVersion} -> ${version} and dist-tag: ${distTag}`, +); + +/* + * Update version in CHANGELOG.md + */ +console.log('Updating CHANGELOG.md and bumping versions'); +const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); +const UNRELEASED_PLACEHOLDER = '# [Unreleased]'; + +const index = changelog.indexOf(UNRELEASED_PLACEHOLDER); +if (index === -1) { + exitWithError(`Could not find "${UNRELEASED_PLACEHOLDER}" in CHANGELOG.md`); +} +let nextVersionIndex = changelog.indexOf('\n# v', index); +if (nextVersionIndex === -1) { + nextVersionIndex = change.length - 1; +} + +const releaseNots = changelog + .substring(index + UNRELEASED_PLACEHOLDER.length, nextVersionIndex) + .trim(); + +fs.writeFileSync( + 'CHANGELOG.md', + changelog.replace( + UNRELEASED_PLACEHOLDER, + `${UNRELEASED_PLACEHOLDER}\n\n# v${version}`, + ), +); + +/* + * Bump npm versions + */ +exec('git add CHANGELOG.md'); +exec(`npm version ${version} -f`); + +const pushCommand = 'git push --tags'; +if (dryRun) { + console.log(`Skipping: "${pushCommand}" in dry-run mode`); +} else { + exec(pushCommand); +} + +/* + * Build Quill package + */ +console.log('Building Quill'); +exec('npm run build'); + +/* + * Publish Quill package + */ +console.log('Publishing Quill'); +if (JSON.parse(fs.readFileSync('package.json', 'utf-8')).version !== version) { + exitWithError('Version mismatch'); +} + +exec(`npm publish --tag ${distTag}${dryRun ? ' --dry-run' : ''}`); + +/* + * Create GitHub release + */ +if (version === 'experimental') { + console.log('Skipping GitHub release for experimental version'); +} else { + const filename = `release-note-${version}-${(Math.random() * 1000) | 0}.txt`; + fs.writeFileSync(filename, releaseNots); + try { + const prereleaseFlag = distTag === 'latest' ? '--latest' : ' --prerelease'; + const releaseCommand = `gh release create v${version} ${prereleaseFlag} -t "Version ${version}" --notes-file "${filename}" --draft`; + if (dryRun) { + console.log(`Skipping: "${releaseCommand}" in dry-run mode`); + console.log(`Release note:\n${releaseNots}`); + } else { + exec(releaseCommand); + } + } finally { + fs.unlinkSync(filename); + } +} + +/* + * Create npm package tarball + */ +exec('npm pack');