From 3ae005cafecf51ce3d475c88bab73fd92d112be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Tue, 21 Feb 2023 14:04:35 +0100 Subject: [PATCH] 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 --- .github/scripts/bump-versions.mjs | 52 +++++++++++++++++++ .github/scripts/package.json | 6 +++ .github/workflows/check-pr-title.yml | 2 + .github/workflows/release-create-pr.yml | 69 +++++++++++++++++++++++++ .github/workflows/release-publish.yml | 57 ++++++++++++++++++++ CONTRIBUTING.md | 23 +++++++-- package.json | 4 +- packages/cli/package.json | 8 +-- packages/core/package.json | 2 +- packages/editor-ui/package.json | 4 +- packages/editor-ui/vite.config.ts | 24 +++------ packages/node-dev/package.json | 4 +- packages/nodes-base/package.json | 4 +- packages/workflow/package.json | 1 - pnpm-lock.yaml | 28 +++++----- 15 files changed, 240 insertions(+), 48 deletions(-) create mode 100644 .github/scripts/bump-versions.mjs create mode 100644 .github/scripts/package.json create mode 100644 .github/workflows/release-create-pr.yml create mode 100644 .github/workflows/release-publish.yml diff --git a/.github/scripts/bump-versions.mjs b/.github/scripts/bump-versions.mjs new file mode 100644 index 0000000000..da45c92a80 --- /dev/null +++ b/.github/scripts/bump-versions.mjs @@ -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); diff --git a/.github/scripts/package.json b/.github/scripts/package.json new file mode 100644 index 0000000000..60f945f3f5 --- /dev/null +++ b/.github/scripts/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "semver": "^7.3.8", + "conventional-changelog-cli": "^2.2.2" + } +} diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml index 85011b7045..9ac4d71da5 100644 --- a/.github/workflows/check-pr-title.yml +++ b/.github/workflows/check-pr-title.yml @@ -6,6 +6,8 @@ on: - opened - edited - synchronize + branches: + - '!release/*' jobs: check-pr-title: diff --git a/.github/workflows/release-create-pr.yml b/.github/workflows/release-create-pr.yml new file mode 100644 index 0000000000..1a4e5668a1 --- /dev/null +++ b/.github/workflows/release-create-pr.yml @@ -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 diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 0000000000..466ce2eacf --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -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}} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c3408bff2..6960d5ce28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,15 +11,16 @@ Great that you are here and you want to contribute to n8n - [Development setup](#development-setup) - [Requirements](#requirements) - [Node.js](#nodejs) + - [pnpm](#pnpm) + - [pnpm workspaces](#pnpm-workspaces) + - [corepack](#corepack) - [Build tools](#build-tools) - - [pnpm workspaces](#pnpm-workspaces) - [Actual n8n setup](#actual-n8n-setup) - [Start](#start) - [Development cycle](#development-cycle) - [Test suite](#test-suite) + - [Releasing](#releasing) - [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) - [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 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 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/). diff --git a/package.json b/package.json index 530c323e4c..5a55493af9 100644 --- a/package.json +++ b/package.json @@ -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'" }, "dependencies": { - "n8n": "*" + "n8n": "workspace:*" }, "devDependencies": { - "@n8n_io/eslint-config": "*", + "@n8n_io/eslint-config": "workspace:*", "@ngneat/falso": "^6.1.0", "@types/jest": "^29.2.2", "@types/supertest": "^2.0.12", diff --git a/packages/cli/package.json b/packages/cli/package.json index df23a55365..d64fbcaecd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -170,10 +170,10 @@ "lodash.unset": "^4.5.2", "luxon": "^3.1.0", "mysql2": "~2.3.3", - "n8n-core": "~0.155.0", - "n8n-editor-ui": "~0.182.0", - "n8n-nodes-base": "~0.214.0", - "n8n-workflow": "~0.137.0", + "n8n-core": "workspace:*", + "n8n-editor-ui": "workspace:*", + "n8n-nodes-base": "workspace:*", + "n8n-workflow": "workspace:*", "nodemailer": "^6.7.1", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 53932ea78d..96585c2491 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -55,7 +55,7 @@ "form-data": "^4.0.0", "lodash.get": "^4.4.2", "mime-types": "^2.1.27", - "n8n-workflow": "~0.137.0", + "n8n-workflow": "workspace:*", "oauth-1.0a": "^2.2.6", "p-cancelable": "^2.0.0", "pretty-bytes": "^5.6.0", diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index eb4ead029a..56c76aaa1c 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -63,8 +63,8 @@ "lodash.set": "^4.3.2", "luxon": "^3.1.0", "monaco-editor": "^0.33.0", - "n8n-design-system": "~0.54.0", - "n8n-workflow": "~0.137.0", + "n8n-design-system": "workspace:*", + "n8n-workflow": "workspace:*", "normalize-wheel": "^1.0.1", "pinia": "^2.0.22", "prettier": "^2.8.3", diff --git a/packages/editor-ui/vite.config.ts b/packages/editor-ui/vite.config.ts index 82d3be9ee7..1277051887 100644 --- a/packages/editor-ui/vite.config.ts +++ b/packages/editor-ui/vite.config.ts @@ -7,8 +7,6 @@ import { defineConfig as defineVitestConfig } from 'vitest/config'; import packageJSON from './package.json'; -const isCI = process.env.CI === 'true'; - const vendorChunks = ['vue', 'vue-router']; const n8nChunks = ['n8n-workflow', 'n8n-design-system']; const ignoreChunks = [ @@ -63,18 +61,14 @@ export default mergeConfig( }, plugins: [ vue(), - ...(!isCI - ? [ - legacy({ - targets: ['defaults', 'not IE 11'], - }), - monacoEditorPlugin({ - publicPath: 'assets/monaco-editor', - customDistPath: (root: string, buildOutDir: string, base: string) => - `${root}/${buildOutDir}/assets/monaco-editor`, - }), - ] - : []), + legacy({ + targets: ['defaults', 'not IE 11'], + }), + monacoEditorPlugin({ + publicPath: 'assets/monaco-editor', + customDistPath: (root: string, buildOutDir: string, base: string) => + `${root}/${buildOutDir}/assets/monaco-editor`, + }), ], resolve: { alias: [ @@ -113,11 +107,9 @@ export default mergeConfig( }, }, build: { - minify: !isCI, assetsInlineLimit: 0, sourcemap: false, rollupOptions: { - treeshake: !isCI, output: { manualChunks: { vendor: vendorChunks, diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 94b8dc3b2f..b873b7c64f 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -59,8 +59,8 @@ "change-case": "^4.1.1", "fast-glob": "^3.2.5", "inquirer": "^7.0.1", - "n8n-core": "~0.155.0", - "n8n-workflow": "~0.137.0", + "n8n-core": "workspace:*", + "n8n-workflow": "workspace:*", "oauth-1.0a": "^2.2.6", "replace-in-file": "^6.0.0", "request": "^2.88.2", diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 99b6cbbd51..73ef88e91f 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -758,7 +758,7 @@ "@types/xml2js": "^0.4.3", "eslint-plugin-n8n-nodes-base": "^1.12.0", "gulp": "^4.0.0", - "n8n-workflow": "~0.137.0" + "n8n-workflow": "workspace:*" }, "dependencies": { "@kafkajs/confluent-schema-registry": "1.0.6", @@ -797,7 +797,7 @@ "mqtt": "4.2.6", "mssql": "^8.1.2", "mysql2": "~2.3.0", - "n8n-core": "~0.155.0", + "n8n-core": "workspace:*", "node-html-markdown": "^1.1.3", "node-ssh": "^12.0.0", "nodemailer": "^6.7.1", diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 5814aa2169..0a89d973c0 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -39,7 +39,6 @@ "dist/**/*" ], "devDependencies": { - "@n8n_io/eslint-config": "", "@types/crypto-js": "^4.1.1", "@types/deep-equal": "^1.0.1", "@types/express": "^4.17.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e45e143d5..137ae4a86e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,7 +26,7 @@ importers: .: specifiers: - '@n8n_io/eslint-config': '*' + '@n8n_io/eslint-config': workspace:* '@ngneat/falso': ^6.1.0 '@types/jest': ^29.2.2 '@types/supertest': ^2.0.12 @@ -37,7 +37,7 @@ importers: jest-environment-jsdom: ^29.4.2 jest-mock: ^29.4.2 jest-mock-extended: ^3.0.1 - n8n: '*' + n8n: workspace:* nock: ^13.2.9 node-fetch: ^2.6.7 p-limit: ^3.1.0 @@ -208,10 +208,10 @@ importers: luxon: ^3.1.0 mock-jwks: ^1.0.9 mysql2: ~2.3.3 - n8n-core: ~0.155.0 - n8n-editor-ui: ~0.182.0 - n8n-nodes-base: ~0.214.0 - n8n-workflow: ~0.137.0 + n8n-core: workspace:* + n8n-editor-ui: workspace:* + n8n-nodes-base: workspace:* + n8n-workflow: workspace:* nodemailer: ^6.7.1 nodemon: ^2.0.2 oauth-1.0a: ^2.2.6 @@ -405,7 +405,7 @@ importers: form-data: ^4.0.0 lodash.get: ^4.4.2 mime-types: ^2.1.27 - n8n-workflow: ~0.137.0 + n8n-workflow: workspace:* oauth-1.0a: ^2.2.6 p-cancelable: ^2.0.0 pretty-bytes: ^5.6.0 @@ -592,8 +592,8 @@ importers: lodash.set: ^4.3.2 luxon: ^3.1.0 monaco-editor: ^0.33.0 - n8n-design-system: ~0.54.0 - n8n-workflow: ~0.137.0 + n8n-design-system: workspace:* + n8n-workflow: workspace:* normalize-wheel: ^1.0.1 pinia: ^2.0.22 prettier: ^2.8.3 @@ -723,8 +723,8 @@ importers: change-case: ^4.1.1 fast-glob: ^3.2.5 inquirer: ^7.0.1 - n8n-core: ~0.155.0 - n8n-workflow: ~0.137.0 + n8n-core: workspace:* + n8n-workflow: workspace:* oauth-1.0a: ^2.2.6 replace-in-file: ^6.0.0 request: ^2.88.2 @@ -816,8 +816,8 @@ importers: mqtt: 4.2.6 mssql: ^8.1.2 mysql2: ~2.3.0 - n8n-core: ~0.155.0 - n8n-workflow: ~0.137.0 + n8n-core: workspace:* + n8n-workflow: workspace:* node-html-markdown: ^1.1.3 node-ssh: ^12.0.0 nodemailer: ^6.7.1 @@ -933,7 +933,6 @@ importers: packages/workflow: specifiers: - '@n8n_io/eslint-config': '' '@n8n_io/riot-tmpl': ^2.0.0 '@types/crypto-js': ^4.1.1 '@types/deep-equal': ^1.0.1 @@ -978,7 +977,6 @@ importers: transliteration: 2.3.5 xml2js: 0.4.23 devDependencies: - '@n8n_io/eslint-config': link:../@n8n_io/eslint-config '@types/crypto-js': 4.1.1 '@types/deep-equal': 1.0.1 '@types/express': 4.17.14