mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
ci: Introduce no-plain-errors
lint rule for BE packages (no-changelog) (#7961)
## Summary Require `ApplicationError` or its child classes instead of plain `Error` in BE packages. This ensures the error will be normalized when reported to Sentry, if applicable. Follow-up to: https://github.com/n8n-io/n8n/pulls?q=is%3Apr+is%3Aclosed+applicationerror ... #### How to test the change: 1. ... ## Issues fixed Include links to Github issue or Community forum post or **Linear ticket**: > Important in order to close automatically and provide context to reviewers ... ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. A feature is not complete without tests. > > *(internal)* You can use Slack commands to trigger [e2e tests](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#a39f9e5ba64a48b58a71d81c837e8227) or [deploy test instance](https://www.notion.so/n8n/How-to-use-Test-Instances-d65f49dfc51f441ea44367fb6f67eb0a?pvs=4#f6a177d32bde4b57ae2da0b8e454bfce) or [deploy early access version on Cloud](https://www.notion.so/n8n/Cloudbot-3dbe779836004972b7057bc989526998?pvs=4#fef2d36ab02247e1a0f65a74f6fb534e).
This commit is contained in:
parent
675ec21d33
commit
8cb9c6b3ea
|
@ -10,5 +10,6 @@ module.exports = {
|
|||
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'n8n-local-rules/no-plain-errors': 'off',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
||||
import { ApplicationError, NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
||||
import type {
|
||||
IBinaryData,
|
||||
IDataObject,
|
||||
|
@ -105,7 +105,9 @@ async function getChainPromptTemplate(
|
|||
|
||||
if (!messageClass) {
|
||||
// eslint-disable-next-line n8n-nodes-base/node-execute-block-wrong-error-thrown
|
||||
throw new Error(`Invalid message type "${message.type}"`);
|
||||
throw new ApplicationError('Invalid message type', {
|
||||
extra: { messageType: message.type },
|
||||
});
|
||||
}
|
||||
|
||||
if (messageClass === HumanMessagePromptTemplate && message.messageType !== 'text') {
|
||||
|
|
|
@ -330,7 +330,7 @@ const config = (module.exports = {
|
|||
{
|
||||
selector: 'import',
|
||||
format: ['camelCase', 'PascalCase'],
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
// ----------------------------------
|
||||
|
@ -360,6 +360,7 @@ const config = (module.exports = {
|
|||
// ----------------------------------
|
||||
// eslint-plugin-n8n-local-rules
|
||||
// ----------------------------------
|
||||
|
||||
'n8n-local-rules/no-uncaught-json-parse': 'error',
|
||||
|
||||
'n8n-local-rules/no-json-parse-json-stringify': 'error',
|
||||
|
@ -370,6 +371,8 @@ const config = (module.exports = {
|
|||
|
||||
'n8n-local-rules/no-unused-param-in-catch-clause': 'error',
|
||||
|
||||
'n8n-local-rules/no-plain-errors': 'error',
|
||||
|
||||
// ******************************************************************
|
||||
// overrides to base ruleset
|
||||
// ******************************************************************
|
||||
|
@ -469,6 +472,7 @@ const config = (module.exports = {
|
|||
{
|
||||
files: ['test/**/*.ts', 'src/__tests__/*.ts'],
|
||||
rules: {
|
||||
'n8n-local-rules/no-plain-errors': 'off',
|
||||
'n8n-local-rules/no-skipped-tests':
|
||||
process.env.NODE_ENV === 'development' ? 'warn' : 'error',
|
||||
|
||||
|
|
|
@ -52,5 +52,6 @@ module.exports = {
|
|||
'vue/no-side-effects-in-computed-properties': 'warn',
|
||||
'vue/no-v-text-v-html-on-component': 'warn',
|
||||
'vue/return-in-computed-property': 'warn',
|
||||
'n8n-local-rules/no-plain-errors': 'off',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -353,6 +353,49 @@ module.exports = {
|
|||
};
|
||||
},
|
||||
},
|
||||
|
||||
'no-plain-errors': {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Only `ApplicationError` (from the `workflow` package) or its child classes must be thrown. This ensures the error will be normalized when reported to Sentry, if applicable.',
|
||||
recommended: 'error',
|
||||
},
|
||||
messages: {
|
||||
useApplicationError:
|
||||
'Throw an `ApplicationError` (from the `workflow` package) or its child classes.',
|
||||
},
|
||||
fixable: 'code',
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
ThrowStatement(node) {
|
||||
if (!node.argument) return;
|
||||
|
||||
const isNewError =
|
||||
node.argument.type === 'NewExpression' && node.argument.callee.name === 'Error';
|
||||
|
||||
const isNewlessError =
|
||||
node.argument.type === 'CallExpression' && node.argument.callee.name === 'Error';
|
||||
|
||||
if (isNewError || isNewlessError) {
|
||||
return context.report({
|
||||
messageId: 'useApplicationError',
|
||||
node,
|
||||
fix: (fixer) =>
|
||||
fixer.replaceText(
|
||||
node,
|
||||
`throw new ApplicationError(${node.argument.arguments
|
||||
.map((arg) => arg.raw)
|
||||
.join(', ')})`,
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const isJsonParseCall = (node) =>
|
||||
|
|
|
@ -117,7 +117,7 @@ export class SourceControlService {
|
|||
this.gitService.resetService();
|
||||
return this.sourceControlPreferencesService.sourceControlPreferences;
|
||||
} catch (error) {
|
||||
throw Error(`Failed to disconnect from source control: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to disconnect from source control', { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
SOURCE_CONTROL_TAGS_EXPORT_FILE,
|
||||
SOURCE_CONTROL_WORKFLOW_EXPORT_FOLDER,
|
||||
} from './constants';
|
||||
import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||
import { ApplicationError, type ICredentialDataDecryptedObject } from 'n8n-workflow';
|
||||
import { writeFile as fsWriteFile, rm as fsRm } from 'fs/promises';
|
||||
import { rmSync } from 'fs';
|
||||
import { Credentials, InstanceSettings } from 'n8n-core';
|
||||
|
@ -138,7 +138,7 @@ export class SourceControlExportService {
|
|||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
throw Error(`Failed to export workflows to work folder: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to export workflows to work folder', { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,9 @@ export class SourceControlExportService {
|
|||
],
|
||||
};
|
||||
} catch (error) {
|
||||
throw Error(`Failed to export variables to work folder: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to export variables to work folder', {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +210,7 @@ export class SourceControlExportService {
|
|||
],
|
||||
};
|
||||
} catch (error) {
|
||||
throw Error(`Failed to export variables to work folder: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to export variables to work folder', { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +282,7 @@ export class SourceControlExportService {
|
|||
missingIds,
|
||||
};
|
||||
} catch (error) {
|
||||
throw Error(`Failed to export credentials to work folder: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to export credentials to work folder', { cause: error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ export class SourceControlPreferencesService {
|
|||
});
|
||||
await fsWriteFile(this.sshKeyName, keyPair.privateKey, { encoding: 'utf8', mode: 0o600 });
|
||||
} catch (error) {
|
||||
throw Error(`Failed to save key pair: ${(error as Error).message}`);
|
||||
throw new ApplicationError('Failed to save key pair', { cause: error });
|
||||
}
|
||||
}
|
||||
// update preferences only after generating key pair to prevent endless loop
|
||||
|
|
|
@ -12,5 +12,6 @@ module.exports = {
|
|||
rules: {
|
||||
'import/order': 'off', // TODO: remove this
|
||||
'@typescript-eslint/ban-ts-comment': ['warn', { 'ts-ignore': true }],
|
||||
'n8n-local-rules/no-plain-errors': 'off',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -259,11 +259,8 @@ export function findMatches(
|
|||
|
||||
if (disableDotNotation && skipFields.some((field) => field.includes('.'))) {
|
||||
const fieldToSkip = skipFields.find((field) => field.includes('.'));
|
||||
throw new Error(
|
||||
`Dot notation is disabled, but field to skip comparing '${
|
||||
fieldToSkip as string
|
||||
}' contains dot`,
|
||||
);
|
||||
const msg = `Dot notation is disabled, but field to skip comparing '${fieldToSkip}' contains dot`;
|
||||
throw new ApplicationError(msg, { level: 'warning' });
|
||||
}
|
||||
|
||||
const filteredData = {
|
||||
|
@ -403,16 +400,18 @@ export function findMatches(
|
|||
|
||||
export function checkMatchFieldsInput(data: IDataObject[]) {
|
||||
if (data.length === 1 && data[0].field1 === '' && data[0].field2 === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
'You need to define at least one pair of fields in "Fields to Match" to match on',
|
||||
{ level: 'warning' },
|
||||
);
|
||||
}
|
||||
for (const [index, pair] of data.entries()) {
|
||||
if (pair.field1 === '' || pair.field2 === '') {
|
||||
throw new Error(
|
||||
throw new ApplicationError(
|
||||
`You need to define both fields in "Fields to Match" for pair ${index + 1},
|
||||
field 1 = '${pair.field1}'
|
||||
field 2 = '${pair.field2}'`,
|
||||
{ level: 'warning' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -447,7 +446,10 @@ export function checkInputAndThrowError(
|
|||
return get(entry.json, field, undefined) !== undefined;
|
||||
});
|
||||
if (!isPresent) {
|
||||
throw new Error(`Field '${field}' is not present in any of items in '${inputLabel}'`);
|
||||
throw new ApplicationError(
|
||||
`Field '${field}' is not present in any of items in '${inputLabel}'`,
|
||||
{ level: 'warning' },
|
||||
);
|
||||
}
|
||||
}
|
||||
return input;
|
||||
|
|
|
@ -14,7 +14,7 @@ import type {
|
|||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import { ApplicationError, NodeOperationError } from 'n8n-workflow';
|
||||
import { generatePairedItemData } from '../../utils/utilities';
|
||||
|
||||
export class Kafka implements INodeType {
|
||||
|
@ -229,7 +229,10 @@ export class Kafka implements INodeType {
|
|||
};
|
||||
if (credentials.authentication === true) {
|
||||
if (!(credentials.username && credentials.password)) {
|
||||
throw Error('Username and password are required for authentication');
|
||||
// eslint-disable-next-line n8n-nodes-base/node-execute-block-wrong-error-thrown
|
||||
throw new ApplicationError('Username and password are required for authentication', {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
config.sasl = {
|
||||
username: credentials.username as string,
|
||||
|
|
|
@ -119,7 +119,7 @@ export async function prepareCustomFields(
|
|||
|
||||
return customFields;
|
||||
} else if (customFieldsJson) {
|
||||
throw Error('customFieldsJson value is invalid');
|
||||
throw new ApplicationError('customFieldsJson value is invalid', { level: 'warning' });
|
||||
}
|
||||
} else if (additionalFields.customFieldsUi) {
|
||||
// Get Custom Field Types from TheHive
|
||||
|
|
Loading…
Reference in a new issue