ci: Setup a semi-automated release process (no-changelog) (#5504)

* ci: Setup a semi-automated release process (no-changelog)

* create tag/release before deleting the temporary branch
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-02-21 14:04:35 +01:00 committed by Jan Oberhauser
parent fcac1ddd9f
commit fe782c8f6a
15 changed files with 240 additions and 48 deletions

52
.github/scripts/bump-versions.mjs vendored Normal file
View file

@ -0,0 +1,52 @@
import semver from 'semver';
import { writeFile, readFile } from 'fs/promises';
import { resolve } from 'path';
import child_process from 'child_process';
import { promisify } from 'util';
import assert from 'assert';
const exec = promisify(child_process.exec);
const rootDir = process.cwd();
const releaseType = process.env.RELEASE_TYPE;
assert.match(releaseType, /^(patch|minor|major)$/, 'Invalid RELEASE_TYPE');
// TODO: if releaseType is `auto` determine release type based on the changelog
const lastTag = (await exec('git describe --tags --match "n8n@*" --abbrev=0')).stdout.trim();
const packages = JSON.parse((await exec('pnpm ls -r --only-projects --json')).stdout);
const packageMap = {};
for (let { name, path, version, private: isPrivate, dependencies } of packages) {
if (isPrivate && path !== rootDir) continue;
if (path === rootDir) name = 'monorepo-root';
const isDirty = await exec(`git diff --quiet HEAD ${lastTag} -- ${path}`)
.then(() => false)
.catch((error) => true);
packageMap[name] = { path, isDirty, version };
}
assert.ok(packageMap['n8n'].isDirty, 'No changes found since the last release');
// Keep the monorepo version up to date with the released version
packageMap['monorepo-root'].version = packageMap['n8n'].version;
for (const packageName in packageMap) {
const { path, version, isDirty } = packageMap[packageName];
const packageFile = resolve(path, 'package.json');
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
packageJson.version = packageMap[packageName].nextVersion =
isDirty ||
Object.keys(packageJson.dependencies).some(
(dependencyName) => packageMap[dependencyName]?.isDirty,
)
? semver.inc(version, releaseType)
: version;
await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
}
console.log(packageMap['n8n'].nextVersion);

6
.github/scripts/package.json vendored Normal file
View file

@ -0,0 +1,6 @@
{
"dependencies": {
"semver": "^7.3.8",
"conventional-changelog-cli": "^2.2.2"
}
}

View file

@ -6,6 +6,8 @@ on:
- opened - opened
- edited - edited
- synchronize - synchronize
branches:
- '!release/*'
jobs: jobs:
check-pr-title: check-pr-title:

69
.github/workflows/release-create-pr.yml vendored Normal file
View file

@ -0,0 +1,69 @@
name: 'Release: Create Pull Request'
on:
workflow_dispatch:
inputs:
base-branch:
description: 'The branch to create this release PR from.'
required: true
default: 'master'
release-type:
description: 'A SemVer release type.'
required: true
type: choice
default: 'minor'
options:
- patch
- minor
jobs:
create-release-pr:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.inputs.base-branch }}
- name: Push the base branch
run: |
git push -f origin ${{ github.event.inputs.base-branch }}:"release/${{ github.event.inputs.release-type }}"
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
node-version: 16.x
- run: npm install --prefix=.github/scripts --no-package-lock
- name: Bump package versions
run: |
echo "NEXT_RELEASE=$(node .github/scripts/bump-versions.mjs)" >> $GITHUB_ENV
pnpm i --lockfile-only
env:
RELEASE_TYPE: ${{ github.event.inputs.release-type }}
- name: Generate Changelog
run: npx conventional-changelog-cli -p angular -i CHANGELOG.md -s -t n8n@
- name: Push the release branch, and Create the PR
uses: peter-evans/create-pull-request@v4
with:
base: 'release/${{ github.event.inputs.release-type }}'
branch: 'release/${{ env.NEXT_RELEASE }}'
commit-message: ':rocket: Release ${{ env.NEXT_RELEASE }}'
delete-branch: true
labels: 'release'
title: ':rocket: Release ${{ env.NEXT_RELEASE }}'
# 'TODO: add generated changelog to the body. create a script to generate custom changelog'
body: ''
# TODO: post PR link to slack

57
.github/workflows/release-publish.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: 'Release: Publish'
on:
pull_request:
types:
- closed
branches:
- 'release/patch'
- 'release/minor'
jobs:
publish-release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2.2.4
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Publish to NPM
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
pnpm publish -r --publish-branch ${{github.event.pull_request.base.ref}} --access public
echo "RELEASE=$(node -e 'console.log(require("./package.json").version)')" >> $GITHUB_ENV
- name: Create Release
uses: ncipollo/release-action@v1
with:
commit: ${{github.event.pull_request.base.ref}}
tag: 'n8n@${{env.RELEASE}}'
- name: Merge Release into 'master'
run: |
git fetch origin
git checkout --track origin/master
git config user.name github-actions
git config user.email github-actions@github.com
git merge --ff origin/${{github.event.pull_request.base.ref}}
git push origin master
git push origin :${{github.event.pull_request.base.ref}}

View file

@ -11,15 +11,16 @@ Great that you are here and you want to contribute to n8n
- [Development setup](#development-setup) - [Development setup](#development-setup)
- [Requirements](#requirements) - [Requirements](#requirements)
- [Node.js](#nodejs) - [Node.js](#nodejs)
- [Build tools](#build-tools) - [pnpm](#pnpm)
- [pnpm workspaces](#pnpm-workspaces) - [pnpm workspaces](#pnpm-workspaces)
- [corepack](#corepack)
- [Build tools](#build-tools)
- [Actual n8n setup](#actual-n8n-setup) - [Actual n8n setup](#actual-n8n-setup)
- [Start](#start) - [Start](#start)
- [Development cycle](#development-cycle) - [Development cycle](#development-cycle)
- [Test suite](#test-suite) - [Test suite](#test-suite)
- [Releasing](#releasing)
- [Create custom nodes](#create-custom-nodes) - [Create custom nodes](#create-custom-nodes)
- [Create a new node to contribute to n8n](#create-a-new-node-to-contribute-to-n8n)
- [Checklist before submitting a new node](#checklist-before-submitting-a-new-node)
- [Extend documentation](#extend-documentation) - [Extend documentation](#extend-documentation)
- [Contributor License Agreement](#contributor-license-agreement) - [Contributor License Agreement](#contributor-license-agreement)
@ -195,6 +196,22 @@ If that gets executed in one of the package folders it will only run the tests
of this package. If it gets executed in the n8n-root folder it will run all of this package. If it gets executed in the n8n-root folder it will run all
tests of all packages. tests of all packages.
## Releasing
To start a release, trigger [this workflow](https://github.com/n8n-io/n8n/actions/workflows/release-create-pr.yml) with the SemVer release type, and select a branch to cut this release from. This workflow will then
1. Bump versions of packages that have changed or have dependencies that have changed
2. Update the Changelog
3. Create a new branch called `release/${VERSION}`, and
4. Create a new pull-request to track any further changes that need to be included in this release
Once ready to release, simply merge the pull-request.
This triggers [another workflow](https://github.com/n8n-io/n8n/actions/workflows/release-publish.yml), that will
1. Build and publish the packages that have a new version in this release
2. Create a new tag, and GitHub release from squashed release commit
3. Merge the squashed release commit back into `master`
## Create custom nodes ## Create custom nodes
Learn about [building nodes](https://docs.n8n.io/integrations/creating-nodes/) to create custom nodes for n8n. You can create community nodes and make them available using [npm](https://www.npmjs.com/). Learn about [building nodes](https://docs.n8n.io/integrations/creating-nodes/) to create custom nodes for n8n. You can create community nodes and make them available using [npm](https://www.npmjs.com/).

View file

@ -34,10 +34,10 @@
"test:e2e:all": "cross-env E2E_TESTS=true start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'" "test:e2e:all": "cross-env E2E_TESTS=true start-server-and-test start http://localhost:5678/favicon.ico 'cypress run --headless'"
}, },
"dependencies": { "dependencies": {
"n8n": "*" "n8n": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@n8n_io/eslint-config": "*", "@n8n_io/eslint-config": "workspace:*",
"@ngneat/falso": "^6.1.0", "@ngneat/falso": "^6.1.0",
"@types/jest": "^29.2.2", "@types/jest": "^29.2.2",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",

View file

@ -167,10 +167,10 @@
"lodash.unset": "^4.5.2", "lodash.unset": "^4.5.2",
"luxon": "^3.1.0", "luxon": "^3.1.0",
"mysql2": "~2.3.3", "mysql2": "~2.3.3",
"n8n-core": "~0.155.0", "n8n-core": "workspace:*",
"n8n-editor-ui": "~0.182.0", "n8n-editor-ui": "workspace:*",
"n8n-nodes-base": "~0.214.0", "n8n-nodes-base": "workspace:*",
"n8n-workflow": "~0.137.0", "n8n-workflow": "workspace:*",
"nodemailer": "^6.7.1", "nodemailer": "^6.7.1",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",
"open": "^7.0.0", "open": "^7.0.0",

View file

@ -55,7 +55,7 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"n8n-workflow": "~0.137.0", "n8n-workflow": "workspace:*",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",
"p-cancelable": "^2.0.0", "p-cancelable": "^2.0.0",
"pretty-bytes": "^5.6.0", "pretty-bytes": "^5.6.0",

View file

@ -63,8 +63,8 @@
"lodash.set": "^4.3.2", "lodash.set": "^4.3.2",
"luxon": "^3.1.0", "luxon": "^3.1.0",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"n8n-design-system": "~0.54.0", "n8n-design-system": "workspace:*",
"n8n-workflow": "~0.137.0", "n8n-workflow": "workspace:*",
"normalize-wheel": "^1.0.1", "normalize-wheel": "^1.0.1",
"pinia": "^2.0.22", "pinia": "^2.0.22",
"prettier": "^2.8.3", "prettier": "^2.8.3",

View file

@ -7,8 +7,6 @@ import { defineConfig as defineVitestConfig } from 'vitest/config';
import packageJSON from './package.json'; import packageJSON from './package.json';
const isCI = process.env.CI === 'true';
const vendorChunks = ['vue', 'vue-router']; const vendorChunks = ['vue', 'vue-router'];
const n8nChunks = ['n8n-workflow', 'n8n-design-system']; const n8nChunks = ['n8n-workflow', 'n8n-design-system'];
const ignoreChunks = [ const ignoreChunks = [
@ -63,8 +61,6 @@ export default mergeConfig(
}, },
plugins: [ plugins: [
vue(), vue(),
...(!isCI
? [
legacy({ legacy({
targets: ['defaults', 'not IE 11'], targets: ['defaults', 'not IE 11'],
}), }),
@ -73,8 +69,6 @@ export default mergeConfig(
customDistPath: (root: string, buildOutDir: string, base: string) => customDistPath: (root: string, buildOutDir: string, base: string) =>
`${root}/${buildOutDir}/assets/monaco-editor`, `${root}/${buildOutDir}/assets/monaco-editor`,
}), }),
]
: []),
], ],
resolve: { resolve: {
alias: [ alias: [
@ -113,11 +107,9 @@ export default mergeConfig(
}, },
}, },
build: { build: {
minify: !isCI,
assetsInlineLimit: 0, assetsInlineLimit: 0,
sourcemap: false, sourcemap: false,
rollupOptions: { rollupOptions: {
treeshake: !isCI,
output: { output: {
manualChunks: { manualChunks: {
vendor: vendorChunks, vendor: vendorChunks,

View file

@ -59,8 +59,8 @@
"change-case": "^4.1.1", "change-case": "^4.1.1",
"fast-glob": "^3.2.5", "fast-glob": "^3.2.5",
"inquirer": "^7.0.1", "inquirer": "^7.0.1",
"n8n-core": "~0.155.0", "n8n-core": "workspace:*",
"n8n-workflow": "~0.137.0", "n8n-workflow": "workspace:*",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",
"replace-in-file": "^6.0.0", "replace-in-file": "^6.0.0",
"request": "^2.88.2", "request": "^2.88.2",

View file

@ -758,7 +758,7 @@
"@types/xml2js": "^0.4.3", "@types/xml2js": "^0.4.3",
"eslint-plugin-n8n-nodes-base": "^1.12.0", "eslint-plugin-n8n-nodes-base": "^1.12.0",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"n8n-workflow": "~0.137.0" "n8n-workflow": "workspace:*"
}, },
"dependencies": { "dependencies": {
"@kafkajs/confluent-schema-registry": "1.0.6", "@kafkajs/confluent-schema-registry": "1.0.6",
@ -797,7 +797,7 @@
"mqtt": "4.2.6", "mqtt": "4.2.6",
"mssql": "^8.1.2", "mssql": "^8.1.2",
"mysql2": "~2.3.0", "mysql2": "~2.3.0",
"n8n-core": "~0.155.0", "n8n-core": "workspace:*",
"node-html-markdown": "^1.1.3", "node-html-markdown": "^1.1.3",
"node-ssh": "^12.0.0", "node-ssh": "^12.0.0",
"nodemailer": "^6.7.1", "nodemailer": "^6.7.1",

View file

@ -39,7 +39,6 @@
"dist/**/*" "dist/**/*"
], ],
"devDependencies": { "devDependencies": {
"@n8n_io/eslint-config": "",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/deep-equal": "^1.0.1", "@types/deep-equal": "^1.0.1",
"@types/express": "^4.17.6", "@types/express": "^4.17.6",

View file

@ -26,7 +26,7 @@ importers:
.: .:
specifiers: specifiers:
'@n8n_io/eslint-config': '*' '@n8n_io/eslint-config': workspace:*
'@ngneat/falso': ^6.1.0 '@ngneat/falso': ^6.1.0
'@types/jest': ^29.2.2 '@types/jest': ^29.2.2
'@types/supertest': ^2.0.12 '@types/supertest': ^2.0.12
@ -37,7 +37,7 @@ importers:
jest-environment-jsdom: ^29.4.2 jest-environment-jsdom: ^29.4.2
jest-mock: ^29.4.2 jest-mock: ^29.4.2
jest-mock-extended: ^3.0.1 jest-mock-extended: ^3.0.1
n8n: '*' n8n: workspace:*
nock: ^13.2.9 nock: ^13.2.9
node-fetch: ^2.6.7 node-fetch: ^2.6.7
p-limit: ^3.1.0 p-limit: ^3.1.0
@ -207,10 +207,10 @@ importers:
luxon: ^3.1.0 luxon: ^3.1.0
mock-jwks: ^1.0.9 mock-jwks: ^1.0.9
mysql2: ~2.3.3 mysql2: ~2.3.3
n8n-core: ~0.155.0 n8n-core: workspace:*
n8n-editor-ui: ~0.182.0 n8n-editor-ui: workspace:*
n8n-nodes-base: ~0.214.0 n8n-nodes-base: workspace:*
n8n-workflow: ~0.137.0 n8n-workflow: workspace:*
nodemailer: ^6.7.1 nodemailer: ^6.7.1
nodemon: ^2.0.2 nodemon: ^2.0.2
oauth-1.0a: ^2.2.6 oauth-1.0a: ^2.2.6
@ -403,7 +403,7 @@ importers:
form-data: ^4.0.0 form-data: ^4.0.0
lodash.get: ^4.4.2 lodash.get: ^4.4.2
mime-types: ^2.1.27 mime-types: ^2.1.27
n8n-workflow: ~0.137.0 n8n-workflow: workspace:*
oauth-1.0a: ^2.2.6 oauth-1.0a: ^2.2.6
p-cancelable: ^2.0.0 p-cancelable: ^2.0.0
pretty-bytes: ^5.6.0 pretty-bytes: ^5.6.0
@ -590,8 +590,8 @@ importers:
lodash.set: ^4.3.2 lodash.set: ^4.3.2
luxon: ^3.1.0 luxon: ^3.1.0
monaco-editor: ^0.33.0 monaco-editor: ^0.33.0
n8n-design-system: ~0.54.0 n8n-design-system: workspace:*
n8n-workflow: ~0.137.0 n8n-workflow: workspace:*
normalize-wheel: ^1.0.1 normalize-wheel: ^1.0.1
pinia: ^2.0.22 pinia: ^2.0.22
prettier: ^2.8.3 prettier: ^2.8.3
@ -721,8 +721,8 @@ importers:
change-case: ^4.1.1 change-case: ^4.1.1
fast-glob: ^3.2.5 fast-glob: ^3.2.5
inquirer: ^7.0.1 inquirer: ^7.0.1
n8n-core: ~0.155.0 n8n-core: workspace:*
n8n-workflow: ~0.137.0 n8n-workflow: workspace:*
oauth-1.0a: ^2.2.6 oauth-1.0a: ^2.2.6
replace-in-file: ^6.0.0 replace-in-file: ^6.0.0
request: ^2.88.2 request: ^2.88.2
@ -814,8 +814,8 @@ importers:
mqtt: 4.2.6 mqtt: 4.2.6
mssql: ^8.1.2 mssql: ^8.1.2
mysql2: ~2.3.0 mysql2: ~2.3.0
n8n-core: ~0.155.0 n8n-core: workspace:*
n8n-workflow: ~0.137.0 n8n-workflow: workspace:*
node-html-markdown: ^1.1.3 node-html-markdown: ^1.1.3
node-ssh: ^12.0.0 node-ssh: ^12.0.0
nodemailer: ^6.7.1 nodemailer: ^6.7.1
@ -931,7 +931,6 @@ importers:
packages/workflow: packages/workflow:
specifiers: specifiers:
'@n8n_io/eslint-config': ''
'@n8n_io/riot-tmpl': ^2.0.0 '@n8n_io/riot-tmpl': ^2.0.0
'@types/crypto-js': ^4.1.1 '@types/crypto-js': ^4.1.1
'@types/deep-equal': ^1.0.1 '@types/deep-equal': ^1.0.1
@ -976,7 +975,6 @@ importers:
transliteration: 2.3.5 transliteration: 2.3.5
xml2js: 0.4.23 xml2js: 0.4.23
devDependencies: devDependencies:
'@n8n_io/eslint-config': link:../@n8n_io/eslint-config
'@types/crypto-js': 4.1.1 '@types/crypto-js': 4.1.1
'@types/deep-equal': 1.0.1 '@types/deep-equal': 1.0.1
'@types/express': 4.17.14 '@types/express': 4.17.14