fix(core): Improve license diagnostics and CLI output (#30955)

This commit is contained in:
Iván Ovejero 2026-05-22 13:28:20 +02:00 committed by GitHub
parent 1c638d3272
commit affc3c1806
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 39 additions and 7 deletions

View File

@ -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');
}
}

View File

@ -358,7 +358,27 @@ export class Start extends BaseCommand<z.infer<typeof flagsSchema>> {
}
}
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() {

View File

@ -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<string, unknown> } = {},
) {
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 },
);
}
}

View File

@ -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);