diff --git a/packages/cli/src/commands/license/info.ts b/packages/cli/src/commands/license/info.ts index 4b3e9d6763d..bec7ba53e1a 100644 --- a/packages/cli/src/commands/license/info.ts +++ b/packages/cli/src/commands/license/info.ts @@ -14,12 +14,13 @@ export class LicenseInfoCommand extends BaseCommand { const license = Container.get(License); await license.init({ isCli: true }); - this.logger.info('Printing license information:\n' + license.getInfo()); + // Write to stdout so output is independent of N8N_LOG_LEVEL. + process.stdout.write(license.getInfo() + '\n'); } async catch(error: Error) { - this.logger.error('\nGOT ERROR'); - this.logger.info('===================================='); - this.logger.error(error.message); + process.stderr.write('\nGOT ERROR\n'); + process.stderr.write('====================================\n'); + process.stderr.write(error.message + '\n'); } } diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 1a911352cbd..8f6358df281 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -358,7 +358,27 @@ export class Start extends BaseCommand> { } } - throw new FeatureNotLicensedError(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES); + throw new FeatureNotLicensedError(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES, { + extra: { + instance: { + type: this.instanceSettings.instanceType, + isLeader: this.instanceSettings.isLeader, + }, + config: { + autoRenewalEnabled: this.globalConfig.license?.autoRenewalEnabled, + activationKeySet: !!this.globalConfig.license?.activationKey, + usingEphemeralCert: !!this.globalConfig.license?.cert, + }, + cert: { + exists: (await this.license.loadCertStr()).length > 0, + isValid: this.license.isCertValid(), + hasMultiMain: this.license.hasFeatureInCert(LICENSE_FEATURES.MULTIPLE_MAIN_INSTANCES), + expiresAt: this.license.getExpiryDate()?.toISOString() ?? null, + terminatesAt: this.license.getTerminationDate()?.toISOString() ?? null, + consumerId: this.license.getConsumerId(), + }, + }, + }); } async run() { diff --git a/packages/cli/src/errors/feature-not-licensed.error.ts b/packages/cli/src/errors/feature-not-licensed.error.ts index 1871475d368..41f211fbdbf 100644 --- a/packages/cli/src/errors/feature-not-licensed.error.ts +++ b/packages/cli/src/errors/feature-not-licensed.error.ts @@ -2,10 +2,13 @@ import type { LICENSE_FEATURES } from '@n8n/constants'; import { UserError } from 'n8n-workflow'; export class FeatureNotLicensedError extends UserError { - constructor(feature: (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES]) { + constructor( + feature: (typeof LICENSE_FEATURES)[keyof typeof LICENSE_FEATURES], + opts: { extra?: Record } = {}, + ) { super( `Your license does not allow for ${feature}. To enable ${feature}, please upgrade to a license that supports this feature.`, - { level: 'warning' }, + { level: 'warning', extra: opts.extra }, ); } } diff --git a/packages/cli/src/license.ts b/packages/cli/src/license.ts index 48eece2dd44..e8c52a84348 100644 --- a/packages/cli/src/license.ts +++ b/packages/cli/src/license.ts @@ -255,6 +255,14 @@ export class License implements LicenseProvider { return this.manager?.hasFeatureEnabled(feature) ?? false; } + isCertValid(): boolean { + return this.manager?.isValid(false /* useLogger */) ?? false; + } + + hasFeatureInCert(feature: BooleanLicenseFeature): boolean { + return this.manager?.hasFeatureEnabled(feature, false) ?? false; + } + /** @deprecated Use `LicenseState.isDynamicCredentialsLicensed` instead. */ isDynamicCredentialsEnabled() { return this.isLicensed(LICENSE_FEATURES.DYNAMIC_CREDENTIALS);