Skip to content
Open
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
21 changes: 19 additions & 2 deletions .github/scripts/bump-versions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,24 @@ function generateExperimentalVersion(currentVersion) {
return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.0`;
}

function generateRcVersion(currentVersion) {
const parsed = semver.parse(currentVersion);
if (!parsed) throw new Error(`Invalid version: ${currentVersion}`);

// Check if it's already an RC version
if (parsed.prerelease.length > 0 && parsed.prerelease[0] === 'rc') {
// Increment the RC number
const rcNumber = (parsed.prerelease[1] || 0) + 1;
return `${parsed.major}.${parsed.minor}.${parsed.patch}-rc.${rcNumber}`;
}

// Create new RC version: <major>.<minor>.<patch>-rc.1
return `${parsed.major}.${parsed.minor}.${parsed.patch}-rc.1`;
}

const rootDir = process.cwd();
const releaseType = process.env.RELEASE_TYPE;
assert.match(releaseType, /^(patch|minor|major|experimental)$/, 'Invalid RELEASE_TYPE');
assert.match(releaseType, /^(patch|minor|major|experimental|rc)$/, 'Invalid RELEASE_TYPE');

// TODO: if releaseType is `auto` determine release type based on the changelog

Expand Down Expand Up @@ -63,7 +78,9 @@ for (const packageName in packageMap) {
)
? releaseType === 'experimental'
? generateExperimentalVersion(version)
: semver.inc(version, releaseType)
: releaseType === 'rc'
? generateRcVersion(version)
: semver.inc(version, releaseType)
: version;

await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
Expand Down
104 changes: 104 additions & 0 deletions .github/scripts/classify-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env node

import { appendFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import semver from 'semver';

class VersionClassifier {
constructor() {
this.githubOutput = process.env.GITHUB_OUTPUT || null;
}

classify(versionString) {
// Reject versions with 'v' prefix
if (versionString.startsWith('v')) {
throw new Error(`Invalid version format: ${versionString}`);
}

// Parse version with semver
const parsed = semver.parse(versionString);

if (!parsed) {
throw new Error(`Invalid version format: ${versionString}`);
}

// Extract components
const major = parsed.major;
const minor = parsed.minor;
const patch = parsed.patch;
const prerelease = parsed.prerelease.length > 0 ? parsed.prerelease.join('.') : '';
const isPrerelease = parsed.prerelease.length > 0;

// Identify prerelease types
const isRc = parsed.prerelease.length > 0 && parsed.prerelease[0] === 'rc';
const isBeta = parsed.prerelease.length > 0 && parsed.prerelease[0] === 'beta';
const isDev = parsed.prerelease.length > 0 && parsed.prerelease[0] === 'dev';
const isExp = parsed.prerelease.length > 0 && parsed.prerelease[0] === 'exp';

return {
major,
minor,
patch,
prerelease,
is_prerelease: isPrerelease,
is_rc: isRc,
is_beta: isBeta,
is_dev: isDev,
is_exp: isExp,
};
}

output(data) {
if (this.githubOutput) {
// Write to GITHUB_OUTPUT for GitHub Actions
const outputs = [
`major=${data.major}`,
`minor=${data.minor}`,
`patch=${data.patch}`,
`prerelease=${data.prerelease}`,
`is_prerelease=${data.is_prerelease}`,
`is_rc=${data.is_rc}`,
`is_beta=${data.is_beta}`,
`is_dev=${data.is_dev}`,
`is_exp=${data.is_exp}`,
];
appendFileSync(this.githubOutput, outputs.join('\n') + '\n');

// Debug output for CI logs
console.log(`Parsed version: major=${data.major}, minor=${data.minor}, patch=${data.patch}, prerelease=${data.prerelease}`);
} else {
// Output JSON to stdout for local testing
console.log(JSON.stringify(data, null, 2));
}
}
}

// CLI - Simple argument parsing
if (fileURLToPath(import.meta.url) === process.argv[1]) {
const args = process.argv.slice(2);
const getArg = (name) => {
const index = args.indexOf(`--${name}`);
if (index === -1 || !args[index + 1]) return undefined;
const value = args[index + 1];
// Handle empty strings and 'null' as undefined
return value === '' || value === 'null' ? undefined : value;
};

try {
const versionString = getArg('version');

if (!versionString) {
console.error('Error: --version is required');
process.exit(1);
}

const classifier = new VersionClassifier();
const result = classifier.classify(versionString);
classifier.output(result);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}

export default VersionClassifier;
10 changes: 9 additions & 1 deletion .github/scripts/docker/docker-config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import { appendFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

class BuildContext {
constructor() {
Expand Down Expand Up @@ -52,6 +53,13 @@ class BuildContext {
case 'release':
if (!version) throw new Error('Version required for release');
context.version = version;
// Release types:
// - stable: Production releases (v2.x)
// - 1: V1 maintenance releases (v1.x) - behaves like stable
// - beta: Beta/RC releases (prereleases)
// - nightly: Nightly builds
// - dev: Development builds
// - branch: Branch-specific builds
context.release_type = releaseType || 'stable';
context.push_to_docker = true;
break;
Expand Down Expand Up @@ -125,7 +133,7 @@ class BuildContext {
}

// CLI - Simple argument parsing
if (import.meta.url === `file://${process.argv[1]}`) {
if (fileURLToPath(import.meta.url) === process.argv[1]) {
const args = process.argv.slice(2);
const getArg = (name) => {
const index = args.indexOf(`--${name}`);
Expand Down
3 changes: 2 additions & 1 deletion .github/scripts/docker/docker-tags.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import { appendFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';

class TagGenerator {
constructor() {
Expand Down Expand Up @@ -64,7 +65,7 @@ class TagGenerator {
}
}

if (import.meta.url === `file://${process.argv[1]}`) {
if (fileURLToPath(import.meta.url) === process.argv[1]) {
const args = process.argv.slice(2);
const getArg = (name) => {
const index = args.indexOf(`--${name}`);
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/check-pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- synchronize
branches:
- 'master'
- 'v1.x'

jobs:
check-pr-title:
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/ci-v1.x.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Test v1.x Branch

on:
push:
branches:
- v1.x
paths-ignore:
- packages/@n8n/task-runner-python/**

jobs:
build-github:
name: Build for Github Cache
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Setup and Build
uses: ./.github/actions/setup-nodejs-github

unit-test:
name: Unit tests
uses: ./.github/workflows/units-tests-reusable.yml
strategy:
matrix:
node-version: [20.x, 22.x, 24.3.x]
with:
ref: ${{ github.sha }}
nodeVersion: ${{ matrix.node-version }}
collectCoverage: ${{ matrix.node-version == '22.x' }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

lint:
name: Lint
uses: ./.github/workflows/linting-reusable.yml
with:
ref: ${{ github.sha }}

notify-on-failure:
name: Notify Slack on failure
runs-on: ubuntu-latest
needs: [unit-test, lint, build-github]
if: failure()
steps:
- name: Notify Slack on failure
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
with:
status: ${{ job.status }}
channel: '#alerts-build'
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
message: v1.x branch (build or test or lint) failed (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
10 changes: 7 additions & 3 deletions .github/workflows/docker-build-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ on:
required: true
type: string
release_type:
description: 'Release type (stable, nightly, dev)'
description: 'Release type (stable, 1, beta, nightly, dev)'
required: false
type: string
default: 'stable'
Expand Down Expand Up @@ -285,7 +285,9 @@ jobs:
if: |
success() &&
(needs.determine-build-context.outputs.release_type == 'stable' ||
needs.determine-build-context.outputs.release_type == 'nightly')
needs.determine-build-context.outputs.release_type == 'nightly' ||
needs.determine-build-context.outputs.release_type == '1' ||
needs.determine-build-context.outputs.release_type == 'beta')
uses: ./.github/workflows/security-trivy-scan-callable.yml
with:
image_ref: ${{ needs.build-and-push-docker.outputs.image_ref }}
Expand All @@ -297,7 +299,9 @@ jobs:
if: |
success() &&
(needs.determine-build-context.outputs.release_type == 'stable' ||
needs.determine-build-context.outputs.release_type == 'nightly')
needs.determine-build-context.outputs.release_type == 'nightly' ||
needs.determine-build-context.outputs.release_type == '1' ||
needs.determine-build-context.outputs.release_type == 'beta')
uses: ./.github/workflows/security-trivy-scan-callable.yml
with:
image_ref: ${{ needs.build-and-push-docker.outputs.runners_primary_ghcr_manifest_tag }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-create-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
- minor
- major
- experimental
- rc

jobs:
create-release-pr:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ jobs:
uses: ./.github/workflows/docker-build-push.yml
with:
n8n_version: ${{ needs.publish-to-npm.outputs.release }}
release_type: stable
release_type: ${{ contains(needs.publish-to-npm.outputs.release, '-rc.') && 'beta' || contains(needs.publish-to-npm.outputs.release, '-beta.') && 'beta' || startsWith(needs.publish-to-npm.outputs.release, '1.') && '1' || 'stable' }}
secrets: inherit

create-github-release:
Expand Down
Loading
Loading