Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set up release actions #138

Merged
merged 1 commit into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/_test.yml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 1 addition & 12 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
42 changes: 42 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# [Unreleased]

- Improved typing for Attributor and Registry.

# 3.0.0-alpha.1

- Fix ESM bundle not exposed in package.json.
Expand Down
168 changes: 168 additions & 0 deletions scripts/release.js
Original file line number Diff line number Diff line change
@@ -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 "[email protected]"');

/*
* 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 <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');