fix(core): Pick up release version and date from package.json (no-changelog) (#13666)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2025-03-04 11:35:07 +01:00 committed by GitHub
parent 9ba9443460
commit 093cc982b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 37 additions and 60 deletions

View file

@ -109,7 +109,6 @@ jobs:
context: ./docker/images/n8n context: ./docker/images/n8n
build-args: | build-args: |
N8N_VERSION=${{ needs.publish-to-npm.outputs.release }} N8N_VERSION=${{ needs.publish-to-npm.outputs.release }}
N8N_RELEASE_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
provenance: false provenance: false
push: true push: true

View file

@ -4,18 +4,15 @@ FROM n8nio/base:${NODE_VERSION}
ARG N8N_VERSION ARG N8N_VERSION
RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi RUN if [ -z "$N8N_VERSION" ] ; then echo "The N8N_VERSION argument is missing!" ; exit 1; fi
ARG N8N_RELEASE_DATE
LABEL org.opencontainers.image.title="n8n" LABEL org.opencontainers.image.title="n8n"
LABEL org.opencontainers.image.description="Workflow Automation Tool" LABEL org.opencontainers.image.description="Workflow Automation Tool"
LABEL org.opencontainers.image.source="https://github.com/n8n-io/n8n" LABEL org.opencontainers.image.source="https://github.com/n8n-io/n8n"
LABEL org.opencontainers.image.url="https://n8n.io" LABEL org.opencontainers.image.url="https://n8n.io"
LABEL org.opencontainers.image.version=${N8N_VERSION} LABEL org.opencontainers.image.version=${N8N_VERSION}
LABEL org.opencontainers.image.created=${N8N_RELEASE_DATE}
ENV N8N_VERSION=${N8N_VERSION} ENV N8N_VERSION=${N8N_VERSION}
ENV NODE_ENV=production ENV NODE_ENV=production
ENV N8N_RELEASE_TYPE=stable ENV N8N_RELEASE_TYPE=stable
ENV N8N_RELEASE_DATE=${N8N_RELEASE_DATE}
RUN set -eux; \ RUN set -eux; \
npm install -g --omit=dev n8n@${N8N_VERSION} --ignore-scripts && \ npm install -g --omit=dev n8n@${N8N_VERSION} --ignore-scripts && \
npm rebuild --prefix=/usr/local/lib/node_modules/n8n sqlite3 && \ npm rebuild --prefix=/usr/local/lib/node_modules/n8n sqlite3 && \

View file

@ -9,9 +9,6 @@ export class GenericConfig {
@Env('N8N_RELEASE_TYPE') @Env('N8N_RELEASE_TYPE')
releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev' = 'dev'; releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev' = 'dev';
@Env('N8N_RELEASE_DATE')
releaseDate?: Date;
/** Grace period (in seconds) to wait for components to shut down before process exit. */ /** Grace period (in seconds) to wait for components to shut down before process exit. */
@Env('N8N_GRACEFUL_SHUTDOWN_TIMEOUT') @Env('N8N_GRACEFUL_SHUTDOWN_TIMEOUT')
gracefulShutdownTimeout: number = 30; gracefulShutdownTimeout: number = 30;

View file

@ -10,14 +10,6 @@ export class SentryConfig {
@Env('N8N_FRONTEND_SENTRY_DSN') @Env('N8N_FRONTEND_SENTRY_DSN')
frontendDsn: string = ''; frontendDsn: string = '';
/**
* Version of the n8n instance
*
* @example '1.73.0'
*/
@Env('N8N_VERSION')
n8nVersion: string = '';
/** /**
* Environment of the n8n instance. * Environment of the n8n instance.
* *

View file

@ -243,7 +243,6 @@ describe('GlobalConfig', () => {
sentry: { sentry: {
backendDsn: '', backendDsn: '',
frontendDsn: '', frontendDsn: '',
n8nVersion: '',
environment: '', environment: '',
deploymentName: '', deploymentName: '',
}, },
@ -326,7 +325,6 @@ describe('GlobalConfig', () => {
DB_LOGGING_MAX_EXECUTION_TIME: '0', DB_LOGGING_MAX_EXECUTION_TIME: '0',
N8N_METRICS: 'TRUE', N8N_METRICS: 'TRUE',
N8N_TEMPLATES_ENABLED: '0', N8N_TEMPLATES_ENABLED: '0',
N8N_RELEASE_DATE: '2025-02-17T13:54:15Z',
}; };
const config = Container.get(GlobalConfig); const config = Container.get(GlobalConfig);
expect(structuredClone(config)).toEqual({ expect(structuredClone(config)).toEqual({
@ -358,10 +356,6 @@ describe('GlobalConfig', () => {
...defaultConfig.templates, ...defaultConfig.templates,
enabled: false, enabled: false,
}, },
generic: {
...defaultConfig.generic,
releaseDate: new Date('2025-02-17T13:54:15.000Z'),
},
}); });
expect(mockFs.readFileSync).not.toHaveBeenCalled(); expect(mockFs.readFileSync).not.toHaveBeenCalled();
}); });
@ -397,15 +391,4 @@ describe('GlobalConfig', () => {
'Invalid number value for DB_LOGGING_MAX_EXECUTION_TIME: abcd', 'Invalid number value for DB_LOGGING_MAX_EXECUTION_TIME: abcd',
); );
}); });
it('should handle invalid timestamps', () => {
process.env = {
N8N_RELEASE_DATE: 'abcd',
};
const config = Container.get(GlobalConfig);
expect(config.generic.releaseDate).toBeUndefined();
expect(consoleWarnMock).toHaveBeenCalledWith(
'Invalid timestamp value for N8N_RELEASE_DATE: abcd',
);
});
}); });

View file

@ -14,7 +14,13 @@ import { ensureError, sleep, UserError } from 'n8n-workflow';
import type { AbstractServer } from '@/abstract-server'; import type { AbstractServer } from '@/abstract-server';
import config from '@/config'; import config from '@/config';
import { LICENSE_FEATURES, inDevelopment, inTest } from '@/constants'; import {
LICENSE_FEATURES,
N8N_VERSION,
N8N_RELEASE_DATE,
inDevelopment,
inTest,
} from '@/constants';
import * as CrashJournal from '@/crash-journal'; import * as CrashJournal from '@/crash-journal';
import * as Db from '@/db'; import * as Db from '@/db';
import { getDataDeduplicationService } from '@/deduplication'; import { getDataDeduplicationService } from '@/deduplication';
@ -63,15 +69,14 @@ export abstract class BaseCommand extends Command {
async init(): Promise<void> { async init(): Promise<void> {
this.errorReporter = Container.get(ErrorReporter); this.errorReporter = Container.get(ErrorReporter);
const { releaseDate } = this.globalConfig.generic; const { backendDsn, environment, deploymentName } = this.globalConfig.sentry;
const { backendDsn, n8nVersion, environment, deploymentName } = this.globalConfig.sentry;
await this.errorReporter.init({ await this.errorReporter.init({
serverType: this.instanceSettings.instanceType, serverType: this.instanceSettings.instanceType,
dsn: backendDsn, dsn: backendDsn,
environment, environment,
release: n8nVersion, release: N8N_VERSION,
serverName: deploymentName, serverName: deploymentName,
releaseDate, releaseDate: N8N_RELEASE_DATE,
}); });
initExpressionEvaluator(); initExpressionEvaluator();

View file

@ -1,4 +1,4 @@
import { readFileSync } from 'fs'; import { readFileSync, statSync } from 'fs';
import type { n8n } from 'n8n-core'; import type { n8n } from 'n8n-core';
import type { ITaskDataConnections } from 'n8n-workflow'; import type { ITaskDataConnections } from 'n8n-workflow';
import { jsonParse, TRIMMED_TASK_DATA_CONNECTIONS_KEY } from 'n8n-workflow'; import { jsonParse, TRIMMED_TASK_DATA_CONNECTIONS_KEY } from 'n8n-workflow';
@ -18,9 +18,10 @@ export const TEMPLATES_DIR = join(CLI_DIR, 'templates');
export const NODES_BASE_DIR = dirname(require.resolve('n8n-nodes-base')); export const NODES_BASE_DIR = dirname(require.resolve('n8n-nodes-base'));
export const EDITOR_UI_DIST_DIR = join(dirname(require.resolve('n8n-editor-ui')), 'dist'); export const EDITOR_UI_DIST_DIR = join(dirname(require.resolve('n8n-editor-ui')), 'dist');
export function getN8nPackageJson() { const packageJsonPath = join(CLI_DIR, 'package.json');
return jsonParse<n8n.PackageJson>(readFileSync(join(CLI_DIR, 'package.json'), 'utf8')); const n8nPackageJson = jsonParse<n8n.PackageJson>(readFileSync(packageJsonPath, 'utf8'));
} export const N8N_VERSION = n8nPackageJson.version;
export const N8N_RELEASE_DATE = statSync(packageJsonPath).mtime;
export const STARTING_NODES = [ export const STARTING_NODES = [
'@n8n/n8n-nodes-langchain.manualChatTrigger', '@n8n/n8n-nodes-langchain.manualChatTrigger',
@ -28,8 +29,6 @@ export const STARTING_NODES = [
'n8n-nodes-base.manualTrigger', 'n8n-nodes-base.manualTrigger',
]; ];
export const N8N_VERSION = getN8nPackageJson().version;
export const NODE_PACKAGE_PREFIX = 'n8n-nodes-'; export const NODE_PACKAGE_PREFIX = 'n8n-nodes-';
export const STARTER_TEMPLATE_NAME = `${NODE_PACKAGE_PREFIX}starter`; export const STARTER_TEMPLATE_NAME = `${NODE_PACKAGE_PREFIX}starter`;

View file

@ -5,7 +5,7 @@ import { InstanceSettings, Logger } from 'n8n-core';
import type { IWorkflowBase } from 'n8n-workflow'; import type { IWorkflowBase } from 'n8n-workflow';
import config from '@/config'; import config from '@/config';
import { getN8nPackageJson, inDevelopment } from '@/constants'; import { inDevelopment, N8N_VERSION } from '@/constants';
import { isApiEnabled } from '@/public-api'; import { isApiEnabled } from '@/public-api';
import { import {
ENV_VARS_DOCS_URL, ENV_VARS_DOCS_URL,
@ -175,7 +175,7 @@ export class InstanceRiskReporter implements RiskReporter {
private async getOutdatedState() { private async getOutdatedState() {
let versions = []; let versions = [];
const localVersion = getN8nPackageJson().version; const localVersion = N8N_VERSION;
try { try {
versions = await this.getNextVersions(localVersion).then((v) => this.removeIconData(v)); versions = await this.getNextVersions(localVersion).then((v) => this.removeIconData(v));

View file

@ -114,9 +114,8 @@ export const MOCK_PACKAGE: InstalledPackages[] = [
export function simulateOutdatedInstanceOnce(versionName = MOCK_01110_N8N_VERSION.name) { export function simulateOutdatedInstanceOnce(versionName = MOCK_01110_N8N_VERSION.name) {
const baseUrl = Container.get(GlobalConfig).versionNotifications.endpoint + '/'; const baseUrl = Container.get(GlobalConfig).versionNotifications.endpoint + '/';
jest // @ts-expect-error readonly export
.spyOn(constants, 'getN8nPackageJson') constants.N8N_VERSION = versionName;
.mockReturnValueOnce({ name: 'n8n', version: versionName });
nock(baseUrl).get(versionName).reply(200, [MOCK_01110_N8N_VERSION, MOCK_09990_N8N_VERSION]); nock(baseUrl).get(versionName).reply(200, [MOCK_01110_N8N_VERSION, MOCK_09990_N8N_VERSION]);
} }
@ -124,9 +123,8 @@ export function simulateOutdatedInstanceOnce(versionName = MOCK_01110_N8N_VERSIO
export function simulateUpToDateInstance(versionName = MOCK_09990_N8N_VERSION.name) { export function simulateUpToDateInstance(versionName = MOCK_09990_N8N_VERSION.name) {
const baseUrl = Container.get(GlobalConfig).versionNotifications.endpoint + '/'; const baseUrl = Container.get(GlobalConfig).versionNotifications.endpoint + '/';
jest // @ts-expect-error readonly export
.spyOn(constants, 'getN8nPackageJson') constants.N8N_VERSION = versionName;
.mockReturnValueOnce({ name: 'n8n', version: versionName });
nock(baseUrl).persist().get(versionName).reply(200, [MOCK_09990_N8N_VERSION]); nock(baseUrl).persist().get(versionName).reply(200, [MOCK_09990_N8N_VERSION]);
} }

View file

@ -24,7 +24,8 @@ type ErrorReporterInitOptions = {
beforeSendFilter?: (event: ErrorEvent, hint: EventHint) => boolean; beforeSendFilter?: (event: ErrorEvent, hint: EventHint) => boolean;
}; };
const SIX_WEEKS_IN_MS = 6 * 7 * 24 * 60 * 60 * 1000; const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
const SIX_WEEKS_IN_MS = 6 * 7 * ONE_DAY_IN_MS;
const RELEASE_EXPIRATION_WARNING = const RELEASE_EXPIRATION_WARNING =
'Error tracking disabled because this release is older than 6 weeks.'; 'Error tracking disabled because this release is older than 6 weeks.';
@ -86,17 +87,23 @@ export class ErrorReporter {
}); });
if (releaseDate) { if (releaseDate) {
const releaseExpiresInMs = releaseDate.getTime() + SIX_WEEKS_IN_MS - Date.now(); const releaseExpiresAtMs = releaseDate.getTime() + SIX_WEEKS_IN_MS;
if (releaseExpiresInMs <= 0) { const releaseExpiresInMs = () => releaseExpiresAtMs - Date.now();
if (releaseExpiresInMs() <= 0) {
this.logger.warn(RELEASE_EXPIRATION_WARNING); this.logger.warn(RELEASE_EXPIRATION_WARNING);
return; return;
} }
// Once this release expires, reject all events const checkForExpiration = () => {
this.expirationTimer = setTimeout(() => { // Once this release expires, reject all events
this.logger.warn(RELEASE_EXPIRATION_WARNING); if (releaseExpiresInMs() <= 0) {
// eslint-disable-next-line @typescript-eslint/unbound-method this.logger.warn(RELEASE_EXPIRATION_WARNING);
this.report = this.defaultReport; // eslint-disable-next-line @typescript-eslint/unbound-method
}, releaseExpiresInMs); this.report = this.defaultReport;
} else {
setTimeout(checkForExpiration, ONE_DAY_IN_MS);
}
};
checkForExpiration();
} }
if (!dsn) return; if (!dsn) return;