refactor(core): Switch plain errors in cli to ApplicationError (#7857)
Ensure all errors in `cli` are `ApplicationError` or children of it and contain no variables in the message, to continue normalizing all the errors we report to Sentry Follow-up to: https://github.com/n8n-io/n8n/pull/7839
This commit is contained in:
@@ -2,7 +2,7 @@ import 'reflect-metadata';
|
||||
import { Command } from '@oclif/command';
|
||||
import { ExitError } from '@oclif/errors';
|
||||
import { Container } from 'typedi';
|
||||
import { ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
|
||||
import { ApplicationError, ErrorReporterProxy as ErrorReporter, sleep } from 'n8n-workflow';
|
||||
import { BinaryDataService, InstanceSettings, ObjectStoreService } from 'n8n-core';
|
||||
import type { AbstractServer } from '@/AbstractServer';
|
||||
import { Logger } from '@/Logger';
|
||||
@@ -127,7 +127,7 @@ export abstract class BaseCommand extends Command {
|
||||
if (!isSelected && !isAvailable) return;
|
||||
|
||||
if (isSelected && !isAvailable) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage selected but unavailable. Please make external storage available by adding "s3" to `N8N_AVAILABLE_BINARY_DATA_MODES`.',
|
||||
);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export abstract class BaseCommand extends Command {
|
||||
const host = config.getEnv('externalStorage.s3.host');
|
||||
|
||||
if (host === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage host not configured. Please set `N8N_EXTERNAL_STORAGE_S3_HOST`.',
|
||||
);
|
||||
}
|
||||
@@ -182,13 +182,13 @@ export abstract class BaseCommand extends Command {
|
||||
};
|
||||
|
||||
if (bucket.name === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage bucket name not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (bucket.region === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage bucket region not configured. Please set `N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION`.',
|
||||
);
|
||||
}
|
||||
@@ -199,13 +199,13 @@ export abstract class BaseCommand extends Command {
|
||||
};
|
||||
|
||||
if (credentials.accessKey === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage access key not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY`.',
|
||||
);
|
||||
}
|
||||
|
||||
if (credentials.accessSecret === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'External storage access secret not configured. Please set `N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET`.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Risk } from '@/security-audit/types';
|
||||
import { BaseCommand } from './BaseCommand';
|
||||
import { Container } from 'typedi';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class SecurityAudit extends BaseCommand {
|
||||
static description = 'Generate a security audit report for this n8n instance';
|
||||
@@ -46,7 +47,7 @@ export class SecurityAudit extends BaseCommand {
|
||||
|
||||
const hint = `Valid categories are: ${RISK_CATEGORIES.join(', ')}`;
|
||||
|
||||
throw new Error([message, hint].join('. '));
|
||||
throw new ApplicationError([message, hint].join('. '));
|
||||
}
|
||||
|
||||
const result = await Container.get(SecurityAuditService).run(
|
||||
|
||||
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
|
||||
import { flags } from '@oclif/command';
|
||||
import { PLACEHOLDER_EMPTY_WORKFLOW_ID } from 'n8n-core';
|
||||
import type { IWorkflowBase } from 'n8n-workflow';
|
||||
import { ExecutionBaseError } from 'n8n-workflow';
|
||||
import { ApplicationError, ExecutionBaseError } from 'n8n-workflow';
|
||||
|
||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
@@ -89,7 +89,7 @@ export class Execute extends BaseCommand {
|
||||
}
|
||||
|
||||
if (!workflowData) {
|
||||
throw new Error('Failed to retrieve workflow data for requested workflow');
|
||||
throw new ApplicationError('Failed to retrieve workflow data for requested workflow');
|
||||
}
|
||||
|
||||
if (!isWorkflowIdValid(workflowId)) {
|
||||
@@ -113,7 +113,7 @@ export class Execute extends BaseCommand {
|
||||
const data = await activeExecutions.getPostExecutePromise(executionId);
|
||||
|
||||
if (data === undefined) {
|
||||
throw new Error('Workflow did not return any data!');
|
||||
throw new ApplicationError('Workflow did not return any data');
|
||||
}
|
||||
|
||||
if (data.data.resultData.error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { flags } from '@oclif/command';
|
||||
import type { IRun, ITaskData } from 'n8n-workflow';
|
||||
import { jsonParse, sleep } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse, sleep } from 'n8n-workflow';
|
||||
import { sep } from 'path';
|
||||
import { diff } from 'json-diff';
|
||||
import pick from 'lodash/pick';
|
||||
@@ -486,7 +486,7 @@ export class ExecuteBatch extends BaseCommand {
|
||||
this.updateStatus();
|
||||
}
|
||||
} else {
|
||||
throw new Error('Wrong execution status - cannot proceed');
|
||||
throw new ApplicationError('Wrong execution status - cannot proceed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ICredentialsDb, ICredentialsDecryptedDb } from '@/Interfaces';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
import Container from 'typedi';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class ExportCredentialsCommand extends BaseCommand {
|
||||
static description = 'Export credentials';
|
||||
@@ -125,7 +126,7 @@ export class ExportCredentialsCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
if (credentials.length === 0) {
|
||||
throw new Error('No credentials found with specified filters.');
|
||||
throw new ApplicationError('No credentials found with specified filters');
|
||||
}
|
||||
|
||||
if (flags.separate) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
import { WorkflowRepository } from '@db/repositories/workflow.repository';
|
||||
import Container from 'typedi';
|
||||
import { ApplicationError } from 'n8n-workflow';
|
||||
|
||||
export class ExportWorkflowsCommand extends BaseCommand {
|
||||
static description = 'Export workflows';
|
||||
@@ -111,7 +112,7 @@ export class ExportWorkflowsCommand extends BaseCommand {
|
||||
});
|
||||
|
||||
if (workflows.length === 0) {
|
||||
throw new Error('No workflows found with specified filters.');
|
||||
throw new ApplicationError('No workflows found with specified filters');
|
||||
}
|
||||
|
||||
if (flags.separate) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
||||
import { BaseCommand } from '../BaseCommand';
|
||||
import type { ICredentialsEncrypted } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { UM_FIX_INSTRUCTION } from '@/constants';
|
||||
import { UserRepository } from '@db/repositories/user.repository';
|
||||
@@ -113,7 +113,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
totalImported = credentials.length;
|
||||
|
||||
if (!Array.isArray(credentials)) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'File does not seem to contain credentials. Make sure the credentials are contained in an array.',
|
||||
);
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
const ownerCredentialRole = await Container.get(RoleService).findCredentialOwnerRole();
|
||||
|
||||
if (!ownerCredentialRole) {
|
||||
throw new Error(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner credential role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
this.ownerCredentialRole = ownerCredentialRole;
|
||||
@@ -179,7 +179,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
(await Container.get(UserRepository).findOneBy({ globalRoleId: ownerGlobalRole.id }));
|
||||
|
||||
if (!owner) {
|
||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return owner;
|
||||
@@ -189,7 +189,7 @@ export class ImportCredentialsCommand extends BaseCommand {
|
||||
const user = await Container.get(UserRepository).findOneBy({ id: userId });
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`Failed to find user with ID ${userId}`);
|
||||
throw new ApplicationError('Failed to find user', { extra: { userId } });
|
||||
}
|
||||
|
||||
return user;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { flags } from '@oclif/command';
|
||||
import type { INode, INodeCredentialsDetails } from 'n8n-workflow';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
import { ApplicationError, jsonParse } from 'n8n-workflow';
|
||||
import fs from 'fs';
|
||||
import glob from 'fast-glob';
|
||||
import { Container } from 'typedi';
|
||||
@@ -24,7 +24,7 @@ import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||
|
||||
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
|
||||
if (!Array.isArray(workflows)) {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'File does not seem to contain workflows. Make sure the workflows are contained in an array.',
|
||||
);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IW
|
||||
!Object.prototype.hasOwnProperty.call(workflow, 'nodes') ||
|
||||
!Object.prototype.hasOwnProperty.call(workflow, 'connections')
|
||||
) {
|
||||
throw new Error('File does not seem to contain valid workflows.');
|
||||
throw new ApplicationError('File does not seem to contain valid workflows.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||
const ownerWorkflowRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
if (!ownerWorkflowRole) {
|
||||
throw new Error(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner workflow role. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
this.ownerWorkflowRole = ownerWorkflowRole;
|
||||
@@ -244,7 +244,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||
(await Container.get(UserRepository).findOneBy({ globalRoleId: ownerGlobalRole?.id }));
|
||||
|
||||
if (!owner) {
|
||||
throw new Error(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
throw new ApplicationError(`Failed to find owner. ${UM_FIX_INSTRUCTION}`);
|
||||
}
|
||||
|
||||
return owner;
|
||||
@@ -254,7 +254,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||
const user = await Container.get(UserRepository).findOneBy({ id: userId });
|
||||
|
||||
if (!user) {
|
||||
throw new Error(`Failed to find user with ID ${userId}`);
|
||||
throw new ApplicationError('Failed to find user', { extra: { userId } });
|
||||
}
|
||||
|
||||
return user;
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
INodeTypes,
|
||||
IRun,
|
||||
} from 'n8n-workflow';
|
||||
import { Workflow, NodeOperationError, sleep } from 'n8n-workflow';
|
||||
import { Workflow, NodeOperationError, sleep, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
import * as Db from '@/Db';
|
||||
import * as ResponseHelper from '@/ResponseHelper';
|
||||
@@ -125,8 +125,9 @@ export class Worker extends BaseCommand {
|
||||
`Worker failed to find data of execution "${executionId}" in database. Cannot continue.`,
|
||||
{ executionId },
|
||||
);
|
||||
throw new Error(
|
||||
`Unable to find data of execution "${executionId}" in database. Aborting execution.`,
|
||||
throw new ApplicationError(
|
||||
'Unable to find data of execution in database. Aborting execution.',
|
||||
{ extra: { executionId } },
|
||||
);
|
||||
}
|
||||
const workflowId = fullExecutionData.workflowData.id!;
|
||||
@@ -150,7 +151,7 @@ export class Worker extends BaseCommand {
|
||||
'Worker execution failed because workflow could not be found in database.',
|
||||
{ workflowId, executionId },
|
||||
);
|
||||
throw new Error(`The workflow with the ID "${workflowId}" could not be found`);
|
||||
throw new ApplicationError('Workflow could not be found', { extra: { workflowId } });
|
||||
}
|
||||
staticData = workflowData.staticData;
|
||||
}
|
||||
@@ -408,7 +409,7 @@ export class Worker extends BaseCommand {
|
||||
try {
|
||||
if (!connection.isInitialized) {
|
||||
// Connection is not active
|
||||
throw new Error('No active database connection!');
|
||||
throw new ApplicationError('No active database connection');
|
||||
}
|
||||
// DB ping
|
||||
await connection.query('SELECT 1');
|
||||
|
||||
Reference in New Issue
Block a user