@n8n/eslint-plugin-community-nodes 0.24.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/README.md +39 -52
- package/dist/plugin.d.ts +0 -78
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +0 -26
- package/dist/plugin.js.map +1 -1
- package/dist/rules/index.d.ts +0 -13
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +0 -26
- package/dist/rules/index.js.map +1 -1
- package/dist/typecheck.tsbuildinfo +1 -1
- package/docs/rules/no-forbidden-lifecycle-scripts.md +1 -1
- package/package.json +4 -4
- package/src/plugin.ts +0 -26
- package/src/rules/index.ts +0 -26
- package/dist/rules/cred-class-name-field-conventions.d.ts +0 -2
- package/dist/rules/cred-class-name-field-conventions.d.ts.map +0 -1
- package/dist/rules/cred-class-name-field-conventions.js +0 -89
- package/dist/rules/cred-class-name-field-conventions.js.map +0 -1
- package/dist/rules/cred-class-name-suffix.d.ts +0 -2
- package/dist/rules/cred-class-name-suffix.d.ts.map +0 -1
- package/dist/rules/cred-class-name-suffix.js +0 -53
- package/dist/rules/cred-class-name-suffix.js.map +0 -1
- package/dist/rules/cred-filename-against-convention.d.ts +0 -2
- package/dist/rules/cred-filename-against-convention.d.ts.map +0 -1
- package/dist/rules/cred-filename-against-convention.js +0 -42
- package/dist/rules/cred-filename-against-convention.js.map +0 -1
- package/dist/rules/no-asterisk-in-option-names.d.ts +0 -2
- package/dist/rules/no-asterisk-in-option-names.d.ts.map +0 -1
- package/dist/rules/no-asterisk-in-option-names.js +0 -90
- package/dist/rules/no-asterisk-in-option-names.js.map +0 -1
- package/dist/rules/no-duplicate-param-options.d.ts +0 -2
- package/dist/rules/no-duplicate-param-options.d.ts.map +0 -1
- package/dist/rules/no-duplicate-param-options.js +0 -97
- package/dist/rules/no-duplicate-param-options.js.map +0 -1
- package/dist/rules/node-class-description-name-camelcase.d.ts +0 -2
- package/dist/rules/node-class-description-name-camelcase.d.ts.map +0 -1
- package/dist/rules/node-class-description-name-camelcase.js +0 -92
- package/dist/rules/node-class-description-name-camelcase.js.map +0 -1
- package/dist/rules/node-filename-against-convention.d.ts +0 -2
- package/dist/rules/node-filename-against-convention.d.ts.map +0 -1
- package/dist/rules/node-filename-against-convention.js +0 -61
- package/dist/rules/node-filename-against-convention.js.map +0 -1
- package/dist/rules/node-operation-error-itemindex.d.ts +0 -12
- package/dist/rules/node-operation-error-itemindex.d.ts.map +0 -1
- package/dist/rules/node-operation-error-itemindex.js +0 -184
- package/dist/rules/node-operation-error-itemindex.js.map +0 -1
- package/dist/rules/options-sorted-alphabetically.d.ts +0 -2
- package/dist/rules/options-sorted-alphabetically.d.ts.map +0 -1
- package/dist/rules/options-sorted-alphabetically.js +0 -95
- package/dist/rules/options-sorted-alphabetically.js.map +0 -1
- package/dist/rules/require-community-node-keyword.d.ts +0 -2
- package/dist/rules/require-community-node-keyword.d.ts.map +0 -1
- package/dist/rules/require-community-node-keyword.js +0 -65
- package/dist/rules/require-community-node-keyword.js.map +0 -1
- package/dist/rules/require-continue-on-fail.d.ts +0 -2
- package/dist/rules/require-continue-on-fail.d.ts.map +0 -1
- package/dist/rules/require-continue-on-fail.js +0 -76
- package/dist/rules/require-continue-on-fail.js.map +0 -1
- package/dist/rules/require-mit-license.d.ts +0 -2
- package/dist/rules/require-mit-license.d.ts.map +0 -1
- package/dist/rules/require-mit-license.js +0 -57
- package/dist/rules/require-mit-license.js.map +0 -1
- package/dist/rules/require-param-default.d.ts +0 -15
- package/dist/rules/require-param-default.d.ts.map +0 -1
- package/dist/rules/require-param-default.js +0 -75
- package/dist/rules/require-param-default.js.map +0 -1
- package/docs/rules/cred-class-name-field-conventions.md +0 -38
- package/docs/rules/cred-class-name-suffix.md +0 -46
- package/docs/rules/cred-filename-against-convention.md +0 -42
- package/docs/rules/no-asterisk-in-option-names.md +0 -61
- package/docs/rules/no-duplicate-param-options.md +0 -46
- package/docs/rules/node-class-description-name-camelcase.md +0 -41
- package/docs/rules/node-filename-against-convention.md +0 -50
- package/docs/rules/node-operation-error-itemindex.md +0 -81
- package/docs/rules/options-sorted-alphabetically.md +0 -63
- package/docs/rules/require-community-node-keyword.md +0 -45
- package/docs/rules/require-continue-on-fail.md +0 -56
- package/docs/rules/require-mit-license.md +0 -38
- package/docs/rules/require-param-default.md +0 -60
- package/src/rules/cred-class-name-field-conventions.test.ts +0 -121
- package/src/rules/cred-class-name-field-conventions.ts +0 -102
- package/src/rules/cred-class-name-suffix.test.ts +0 -74
- package/src/rules/cred-class-name-suffix.ts +0 -57
- package/src/rules/cred-filename-against-convention.test.ts +0 -72
- package/src/rules/cred-filename-against-convention.ts +0 -48
- package/src/rules/no-asterisk-in-option-names.test.ts +0 -185
- package/src/rules/no-asterisk-in-option-names.ts +0 -111
- package/src/rules/no-duplicate-param-options.test.ts +0 -125
- package/src/rules/no-duplicate-param-options.ts +0 -125
- package/src/rules/node-class-description-name-camelcase.test.ts +0 -121
- package/src/rules/node-class-description-name-camelcase.ts +0 -114
- package/src/rules/node-filename-against-convention.test.ts +0 -115
- package/src/rules/node-filename-against-convention.ts +0 -76
- package/src/rules/node-operation-error-itemindex.test.ts +0 -280
- package/src/rules/node-operation-error-itemindex.ts +0 -223
- package/src/rules/options-sorted-alphabetically.test.ts +0 -468
- package/src/rules/options-sorted-alphabetically.ts +0 -129
- package/src/rules/require-community-node-keyword.test.ts +0 -82
- package/src/rules/require-community-node-keyword.ts +0 -78
- package/src/rules/require-continue-on-fail.test.ts +0 -129
- package/src/rules/require-continue-on-fail.ts +0 -88
- package/src/rules/require-mit-license.test.ts +0 -67
- package/src/rules/require-mit-license.ts +0 -65
- package/src/rules/require-param-default.test.ts +0 -227
- package/src/rules/require-param-default.ts +0 -89
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Enforce alphabetical ordering of options arrays in n8n node properties (`@n8n/community-nodes/options-sorted-alphabetically`)
|
|
2
|
-
|
|
3
|
-
⚠️ This rule _warns_ in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
-
|
|
5
|
-
<!-- end auto-generated rule header -->
|
|
6
|
-
|
|
7
|
-
## Rule Details
|
|
8
|
-
|
|
9
|
-
Warns when an `options`-type parameter has its options array not sorted alphabetically by name. Applies to all `type: 'options'` parameters — including `resource`, `operation`, and any other dropdowns.
|
|
10
|
-
|
|
11
|
-
Alphabetical ordering is an [official n8n UI design requirement](https://docs.n8n.io/integrations/creating-nodes/plan/node-ui-design/#lists) and the most frequently flagged issue in community node reviews.
|
|
12
|
-
|
|
13
|
-
Comparison is case-insensitive and locale-aware (handles non-ASCII names such as Spanish or Portuguese labels).
|
|
14
|
-
|
|
15
|
-
## Examples
|
|
16
|
-
|
|
17
|
-
### ❌ Incorrect
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
export class MyNode implements INodeType {
|
|
21
|
-
description: INodeTypeDescription = {
|
|
22
|
-
displayName: 'My Service',
|
|
23
|
-
name: 'myService',
|
|
24
|
-
properties: [
|
|
25
|
-
{
|
|
26
|
-
displayName: 'Resource',
|
|
27
|
-
name: 'resource',
|
|
28
|
-
type: 'options',
|
|
29
|
-
options: [
|
|
30
|
-
{ name: 'User', value: 'user' },
|
|
31
|
-
{ name: 'Contact', value: 'contact' }, // out of order
|
|
32
|
-
{ name: 'Project', value: 'project' },
|
|
33
|
-
],
|
|
34
|
-
default: 'user',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### ✅ Correct
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
export class MyNode implements INodeType {
|
|
45
|
-
description: INodeTypeDescription = {
|
|
46
|
-
displayName: 'My Service',
|
|
47
|
-
name: 'myService',
|
|
48
|
-
properties: [
|
|
49
|
-
{
|
|
50
|
-
displayName: 'Resource',
|
|
51
|
-
name: 'resource',
|
|
52
|
-
type: 'options',
|
|
53
|
-
options: [
|
|
54
|
-
{ name: 'Contact', value: 'contact' },
|
|
55
|
-
{ name: 'Project', value: 'project' },
|
|
56
|
-
{ name: 'User', value: 'user' },
|
|
57
|
-
],
|
|
58
|
-
default: 'contact',
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
```
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Require the "n8n-community-node-package" keyword in community node package.json (`@n8n/community-nodes/require-community-node-keyword`)
|
|
2
|
-
|
|
3
|
-
⚠️ This rule _warns_ in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
-
|
|
5
|
-
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
|
|
6
|
-
|
|
7
|
-
<!-- end auto-generated rule header -->
|
|
8
|
-
|
|
9
|
-
## Rule Details
|
|
10
|
-
|
|
11
|
-
Validates that the `package.json` of a community node package includes `"n8n-community-node-package"` in its `keywords` array. This keyword is required for n8n to discover and list community node packages.
|
|
12
|
-
|
|
13
|
-
## Examples
|
|
14
|
-
|
|
15
|
-
### ❌ Incorrect
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"name": "n8n-nodes-my-service",
|
|
20
|
-
"version": "1.0.0"
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```json
|
|
25
|
-
{
|
|
26
|
-
"name": "n8n-nodes-my-service",
|
|
27
|
-
"keywords": ["n8n", "automation"]
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### ✅ Correct
|
|
32
|
-
|
|
33
|
-
```json
|
|
34
|
-
{
|
|
35
|
-
"name": "n8n-nodes-my-service",
|
|
36
|
-
"keywords": ["n8n-community-node-package"]
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```json
|
|
41
|
-
{
|
|
42
|
-
"name": "n8n-nodes-my-service",
|
|
43
|
-
"keywords": ["n8n", "automation", "n8n-community-node-package"]
|
|
44
|
-
}
|
|
45
|
-
```
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# Require continueOnFail() handling in execute() methods of node classes (`@n8n/community-nodes/require-continue-on-fail`)
|
|
2
|
-
|
|
3
|
-
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
-
|
|
5
|
-
<!-- end auto-generated rule header -->
|
|
6
|
-
|
|
7
|
-
## Rule Details
|
|
8
|
-
|
|
9
|
-
Ensures that `execute()` methods in node classes include a `this.continueOnFail()` check. Without this, a single item error will abort the entire workflow instead of allowing execution to continue past the failing item.
|
|
10
|
-
|
|
11
|
-
## Examples
|
|
12
|
-
|
|
13
|
-
### ❌ Incorrect
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
export class MyNode implements INodeType {
|
|
17
|
-
description: INodeTypeDescription = { /* ... */ };
|
|
18
|
-
|
|
19
|
-
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
20
|
-
const items = this.getInputData();
|
|
21
|
-
const returnData: INodeExecutionData[] = [];
|
|
22
|
-
for (let i = 0; i < items.length; i++) {
|
|
23
|
-
// No error handling — one bad item kills the whole workflow
|
|
24
|
-
const result = await someApiCall(items[i]);
|
|
25
|
-
returnData.push({ json: result });
|
|
26
|
-
}
|
|
27
|
-
return [returnData];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### ✅ Correct
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
export class MyNode implements INodeType {
|
|
36
|
-
description: INodeTypeDescription = { /* ... */ };
|
|
37
|
-
|
|
38
|
-
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
39
|
-
const items = this.getInputData();
|
|
40
|
-
const returnData: INodeExecutionData[] = [];
|
|
41
|
-
for (let i = 0; i < items.length; i++) {
|
|
42
|
-
try {
|
|
43
|
-
const result = await someApiCall(items[i]);
|
|
44
|
-
returnData.push({ json: result });
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (this.continueOnFail()) {
|
|
47
|
-
returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return [returnData];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Require the "license" field in community node package.json to be "MIT" (`@n8n/community-nodes/require-mit-license`)
|
|
2
|
-
|
|
3
|
-
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
-
|
|
5
|
-
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
|
|
6
|
-
|
|
7
|
-
<!-- end auto-generated rule header -->
|
|
8
|
-
|
|
9
|
-
## Rule Details
|
|
10
|
-
|
|
11
|
-
Validates that the `package.json` of a community node package declares its `license` as `"MIT"`. Community node packages must be MIT licensed to be distributed through n8n.
|
|
12
|
-
|
|
13
|
-
## Examples
|
|
14
|
-
|
|
15
|
-
### ❌ Incorrect
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"name": "n8n-nodes-my-service",
|
|
20
|
-
"version": "1.0.0"
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```json
|
|
25
|
-
{
|
|
26
|
-
"name": "n8n-nodes-my-service",
|
|
27
|
-
"license": "Apache-2.0"
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### ✅ Correct
|
|
32
|
-
|
|
33
|
-
```json
|
|
34
|
-
{
|
|
35
|
-
"name": "n8n-nodes-my-service",
|
|
36
|
-
"license": "MIT"
|
|
37
|
-
}
|
|
38
|
-
```
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Require every node parameter to declare a default value (`@n8n/community-nodes/require-param-default`)
|
|
2
|
-
|
|
3
|
-
💼 This rule is enabled in the following configs: ✅ `recommended`, ☑️ `recommendedWithoutN8nCloudSupport`.
|
|
4
|
-
|
|
5
|
-
<!-- end auto-generated rule header -->
|
|
6
|
-
|
|
7
|
-
## Rule Details
|
|
8
|
-
|
|
9
|
-
Every node parameter must declare a `default` property. A parameter is detected
|
|
10
|
-
by its shape: an object inside the node's `description` with `displayName`,
|
|
11
|
-
`name`, and `type` all set to string literals — the shape every
|
|
12
|
-
`INodeProperties` entry has. Entries in an `options` array (which carry
|
|
13
|
-
`name`/`value` but no `displayName`/`type`) are not treated as parameters.
|
|
14
|
-
|
|
15
|
-
Without a `default`, n8n cannot reliably initialise the parameter's value. This
|
|
16
|
-
leads to inconsistent behaviour in the editor (empty or `undefined` fields) and
|
|
17
|
-
at execution time. Even non-input types such as `notice` are expected to declare
|
|
18
|
-
`default: ''`.
|
|
19
|
-
|
|
20
|
-
This rule also checks parameters nested inside `options` of `collection` and
|
|
21
|
-
`fixedCollection` parameters.
|
|
22
|
-
|
|
23
|
-
## Examples
|
|
24
|
-
|
|
25
|
-
### Incorrect
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
export class MyNode implements INodeType {
|
|
29
|
-
description: INodeTypeDescription = {
|
|
30
|
-
displayName: 'My Node',
|
|
31
|
-
name: 'myNode',
|
|
32
|
-
properties: [
|
|
33
|
-
{
|
|
34
|
-
displayName: 'Field',
|
|
35
|
-
name: 'field',
|
|
36
|
-
type: 'string',
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Correct
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
export class MyNode implements INodeType {
|
|
47
|
-
description: INodeTypeDescription = {
|
|
48
|
-
displayName: 'My Node',
|
|
49
|
-
name: 'myNode',
|
|
50
|
-
properties: [
|
|
51
|
-
{
|
|
52
|
-
displayName: 'Field',
|
|
53
|
-
name: 'field',
|
|
54
|
-
type: 'string',
|
|
55
|
-
default: '',
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
```
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
-
|
|
3
|
-
import { CredClassNameFieldConventionsRule } from './cred-class-name-field-conventions.js';
|
|
4
|
-
|
|
5
|
-
const ruleTester = new RuleTester();
|
|
6
|
-
|
|
7
|
-
const credFilePath = '/tmp/TestCredential.credentials.ts';
|
|
8
|
-
const nonCredFilePath = '/tmp/SomeHelper.ts';
|
|
9
|
-
|
|
10
|
-
function createCredentialCode(name: string): string {
|
|
11
|
-
return `
|
|
12
|
-
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
13
|
-
|
|
14
|
-
export class TestApi implements ICredentialType {
|
|
15
|
-
name = '${name}';
|
|
16
|
-
displayName = 'Test API';
|
|
17
|
-
properties: INodeProperties[] = [];
|
|
18
|
-
}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function createRegularClass(name: string): string {
|
|
22
|
-
return `
|
|
23
|
-
export class SomeHelper {
|
|
24
|
-
name = '${name}';
|
|
25
|
-
}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Embeds the raw literal text (including its quotes) verbatim, so tests can
|
|
29
|
-
// exercise names containing quote characters that need escaping.
|
|
30
|
-
function createCredentialCodeWithLiteral(literal: string): string {
|
|
31
|
-
return `
|
|
32
|
-
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
33
|
-
|
|
34
|
-
export class TestApi implements ICredentialType {
|
|
35
|
-
name = ${literal};
|
|
36
|
-
displayName = 'Test API';
|
|
37
|
-
properties: INodeProperties[] = [];
|
|
38
|
-
}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
ruleTester.run('cred-class-name-field-conventions', CredClassNameFieldConventionsRule, {
|
|
42
|
-
valid: [
|
|
43
|
-
{
|
|
44
|
-
name: 'name field with Api suffix and lowercase first char',
|
|
45
|
-
filename: credFilePath,
|
|
46
|
-
code: createCredentialCode('githubApi'),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: 'OAuth2 name field is also valid',
|
|
50
|
-
filename: credFilePath,
|
|
51
|
-
code: createCredentialCode('githubOAuth2Api'),
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'class not implementing ICredentialType is ignored',
|
|
55
|
-
filename: credFilePath,
|
|
56
|
-
code: createRegularClass('Github'),
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'non-.credentials.ts file is ignored',
|
|
60
|
-
filename: nonCredFilePath,
|
|
61
|
-
code: createCredentialCode('Github'),
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
invalid: [
|
|
65
|
-
{
|
|
66
|
-
name: 'name field missing Api suffix',
|
|
67
|
-
filename: credFilePath,
|
|
68
|
-
code: createCredentialCode('github'),
|
|
69
|
-
errors: [{ messageId: 'missingSuffix', data: { value: 'github' } }],
|
|
70
|
-
output: createCredentialCode('githubApi'),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'name field with uppercase first char',
|
|
74
|
-
filename: credFilePath,
|
|
75
|
-
code: createCredentialCode('GithubApi'),
|
|
76
|
-
errors: [{ messageId: 'uppercaseFirstChar', data: { value: 'GithubApi' } }],
|
|
77
|
-
output: createCredentialCode('githubApi'),
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: 'name field with both uppercase first char and missing suffix',
|
|
81
|
-
filename: credFilePath,
|
|
82
|
-
code: createCredentialCode('Github'),
|
|
83
|
-
errors: [
|
|
84
|
-
{ messageId: 'uppercaseFirstChar', data: { value: 'Github' } },
|
|
85
|
-
{ messageId: 'missingSuffix', data: { value: 'Github' } },
|
|
86
|
-
],
|
|
87
|
-
output: createCredentialCode('githubApi'),
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: 'name field ending in Ap',
|
|
91
|
-
filename: credFilePath,
|
|
92
|
-
code: createCredentialCode('githubAp'),
|
|
93
|
-
errors: [{ messageId: 'missingSuffix', data: { value: 'githubAp' } }],
|
|
94
|
-
output: createCredentialCode('githubApi'),
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: 'name field ending in A',
|
|
98
|
-
filename: credFilePath,
|
|
99
|
-
code: createCredentialCode('githubA'),
|
|
100
|
-
errors: [{ messageId: 'missingSuffix', data: { value: 'githubA' } }],
|
|
101
|
-
output: createCredentialCode('githubApi'),
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
name: 'autofix escapes single quotes in the name value',
|
|
105
|
-
filename: credFilePath,
|
|
106
|
-
code: createCredentialCodeWithLiteral("'git\\'hub'"),
|
|
107
|
-
errors: [{ messageId: 'missingSuffix', data: { value: "git'hub" } }],
|
|
108
|
-
output: createCredentialCodeWithLiteral("'git\\'hubApi'"),
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: 'autofix preserves double quotes and escapes them in the name value',
|
|
112
|
-
filename: credFilePath,
|
|
113
|
-
code: createCredentialCodeWithLiteral('"Git\\"hub"'),
|
|
114
|
-
errors: [
|
|
115
|
-
{ messageId: 'uppercaseFirstChar', data: { value: 'Git"hub' } },
|
|
116
|
-
{ messageId: 'missingSuffix', data: { value: 'Git"hub' } },
|
|
117
|
-
],
|
|
118
|
-
output: createCredentialCodeWithLiteral('"git\\"hubApi"'),
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createRule,
|
|
3
|
-
findClassProperty,
|
|
4
|
-
getStringLiteralValue,
|
|
5
|
-
isCredentialTypeClass,
|
|
6
|
-
isFileType,
|
|
7
|
-
} from '../utils/index.js';
|
|
8
|
-
|
|
9
|
-
function lowercaseFirstChar(name: string): string {
|
|
10
|
-
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function addApiSuffix(name: string): string {
|
|
14
|
-
if (name.endsWith('Api')) return name;
|
|
15
|
-
if (name.endsWith('Ap')) return `${name}i`;
|
|
16
|
-
if (name.endsWith('A')) return `${name}pi`;
|
|
17
|
-
return `${name}Api`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Serialize a value as a string literal using the original quote character,
|
|
21
|
-
// escaping any characters that would otherwise break the literal so the
|
|
22
|
-
// autofix never emits invalid code (e.g. names containing quotes).
|
|
23
|
-
function toStringLiteral(value: string, quote: string): string {
|
|
24
|
-
const escaped = value
|
|
25
|
-
.replace(/\\/g, '\\\\')
|
|
26
|
-
.replace(/\n/g, '\\n')
|
|
27
|
-
.replace(/\r/g, '\\r')
|
|
28
|
-
.split(quote)
|
|
29
|
-
.join(`\\${quote}`);
|
|
30
|
-
return `${quote}${escaped}${quote}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const CredClassNameFieldConventionsRule = createRule({
|
|
34
|
-
name: 'cred-class-name-field-conventions',
|
|
35
|
-
meta: {
|
|
36
|
-
type: 'problem',
|
|
37
|
-
docs: {
|
|
38
|
-
description: 'Credential `name` field must end with `Api` and start with a lowercase letter',
|
|
39
|
-
},
|
|
40
|
-
messages: {
|
|
41
|
-
missingSuffix: "Credential `name` field '{{value}}' must end with 'Api'",
|
|
42
|
-
uppercaseFirstChar: "Credential `name` field '{{value}}' must start with a lowercase letter",
|
|
43
|
-
},
|
|
44
|
-
fixable: 'code',
|
|
45
|
-
schema: [],
|
|
46
|
-
},
|
|
47
|
-
defaultOptions: [],
|
|
48
|
-
create(context) {
|
|
49
|
-
if (!isFileType(context.filename, '.credentials.ts')) {
|
|
50
|
-
return {};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
ClassDeclaration(node) {
|
|
55
|
-
if (!isCredentialTypeClass(node)) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const nameProperty = findClassProperty(node, 'name');
|
|
60
|
-
if (!nameProperty?.value) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const nameValue = getStringLiteralValue(nameProperty.value);
|
|
65
|
-
if (nameValue === null) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const startsLowercase = !/^[A-Z]/.test(nameValue);
|
|
70
|
-
const endsWithApi = nameValue.endsWith('Api');
|
|
71
|
-
if (startsLowercase && endsWithApi) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Compute the fully-corrected value once so each fix yields the
|
|
76
|
-
// same result in a single pass, regardless of which one applies.
|
|
77
|
-
const fixedValue = addApiSuffix(lowercaseFirstChar(nameValue));
|
|
78
|
-
const valueNode = nameProperty.value;
|
|
79
|
-
const quote = context.sourceCode.getText(valueNode).charAt(0);
|
|
80
|
-
const replacement = toStringLiteral(fixedValue, quote);
|
|
81
|
-
|
|
82
|
-
if (!startsLowercase) {
|
|
83
|
-
context.report({
|
|
84
|
-
node: valueNode,
|
|
85
|
-
messageId: 'uppercaseFirstChar',
|
|
86
|
-
data: { value: nameValue },
|
|
87
|
-
fix: (fixer) => fixer.replaceText(valueNode, replacement),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!endsWithApi) {
|
|
92
|
-
context.report({
|
|
93
|
-
node: valueNode,
|
|
94
|
-
messageId: 'missingSuffix',
|
|
95
|
-
data: { value: nameValue },
|
|
96
|
-
fix: (fixer) => fixer.replaceText(valueNode, replacement),
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
-
|
|
3
|
-
import { CredClassNameSuffixRule } from './cred-class-name-suffix.js';
|
|
4
|
-
|
|
5
|
-
const ruleTester = new RuleTester();
|
|
6
|
-
|
|
7
|
-
const credFilePath = '/tmp/TestCredential.credentials.ts';
|
|
8
|
-
const nonCredFilePath = '/tmp/SomeHelper.ts';
|
|
9
|
-
|
|
10
|
-
function createCredentialCode(className: string): string {
|
|
11
|
-
return `
|
|
12
|
-
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
13
|
-
|
|
14
|
-
export class ${className} implements ICredentialType {
|
|
15
|
-
name = 'testApi';
|
|
16
|
-
displayName = 'Test API';
|
|
17
|
-
properties: INodeProperties[] = [];
|
|
18
|
-
}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function createRegularClass(className: string): string {
|
|
22
|
-
return `
|
|
23
|
-
export class ${className} {
|
|
24
|
-
name = 'test';
|
|
25
|
-
}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
ruleTester.run('cred-class-name-suffix', CredClassNameSuffixRule, {
|
|
29
|
-
valid: [
|
|
30
|
-
{
|
|
31
|
-
name: 'credential class with Api suffix',
|
|
32
|
-
filename: credFilePath,
|
|
33
|
-
code: createCredentialCode('TestApi'),
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: 'credential class with OAuth2Api suffix',
|
|
37
|
-
filename: credFilePath,
|
|
38
|
-
code: createCredentialCode('TestOAuth2Api'),
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'class not implementing ICredentialType is ignored',
|
|
42
|
-
filename: credFilePath,
|
|
43
|
-
code: createRegularClass('SomeHelper'),
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'non-.credentials.ts file is ignored',
|
|
47
|
-
filename: nonCredFilePath,
|
|
48
|
-
code: createCredentialCode('TestCredential'),
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
invalid: [
|
|
52
|
-
{
|
|
53
|
-
name: 'credential class missing Api suffix',
|
|
54
|
-
filename: credFilePath,
|
|
55
|
-
code: createCredentialCode('TestCredential'),
|
|
56
|
-
errors: [{ messageId: 'missingSuffix', data: { name: 'TestCredential' } }],
|
|
57
|
-
output: createCredentialCode('TestCredentialApi'),
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: 'credential class name ending in Ap',
|
|
61
|
-
filename: credFilePath,
|
|
62
|
-
code: createCredentialCode('TestAp'),
|
|
63
|
-
errors: [{ messageId: 'missingSuffix', data: { name: 'TestAp' } }],
|
|
64
|
-
output: createCredentialCode('TestApi'),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'credential class name ending in A',
|
|
68
|
-
filename: credFilePath,
|
|
69
|
-
code: createCredentialCode('TestA'),
|
|
70
|
-
errors: [{ messageId: 'missingSuffix', data: { name: 'TestA' } }],
|
|
71
|
-
output: createCredentialCode('TestApi'),
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { isCredentialTypeClass, isFileType, createRule } from '../utils/index.js';
|
|
2
|
-
|
|
3
|
-
function addApiSuffix(name: string): string {
|
|
4
|
-
if (name.endsWith('Ap')) return `${name}i`;
|
|
5
|
-
if (name.endsWith('A')) return `${name}pi`;
|
|
6
|
-
return `${name}Api`;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const CredClassNameSuffixRule = createRule({
|
|
10
|
-
name: 'cred-class-name-suffix',
|
|
11
|
-
meta: {
|
|
12
|
-
type: 'problem',
|
|
13
|
-
docs: {
|
|
14
|
-
description: 'Credential class names must be suffixed with `Api`',
|
|
15
|
-
},
|
|
16
|
-
messages: {
|
|
17
|
-
missingSuffix: "Credential class name '{{name}}' must end with 'Api'",
|
|
18
|
-
},
|
|
19
|
-
fixable: 'code',
|
|
20
|
-
schema: [],
|
|
21
|
-
},
|
|
22
|
-
defaultOptions: [],
|
|
23
|
-
create(context) {
|
|
24
|
-
if (!isFileType(context.filename, '.credentials.ts')) {
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
ClassDeclaration(node) {
|
|
30
|
-
if (!isCredentialTypeClass(node)) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const classNameNode = node.id;
|
|
35
|
-
if (!classNameNode) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const className = classNameNode.name;
|
|
40
|
-
if (className.endsWith('Api')) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const fixedName = addApiSuffix(className);
|
|
45
|
-
|
|
46
|
-
context.report({
|
|
47
|
-
node: classNameNode,
|
|
48
|
-
messageId: 'missingSuffix',
|
|
49
|
-
data: { name: className },
|
|
50
|
-
fix(fixer) {
|
|
51
|
-
return fixer.replaceText(classNameNode, fixedName);
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
},
|
|
57
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
-
|
|
3
|
-
import { CredFilenameAgainstConventionRule } from './cred-filename-against-convention.js';
|
|
4
|
-
|
|
5
|
-
const ruleTester = new RuleTester();
|
|
6
|
-
|
|
7
|
-
function createCredentialCode(className: string): string {
|
|
8
|
-
return `
|
|
9
|
-
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
10
|
-
|
|
11
|
-
export class ${className} implements ICredentialType {
|
|
12
|
-
name = '${className.charAt(0).toLowerCase() + className.slice(1)}';
|
|
13
|
-
displayName = '${className}';
|
|
14
|
-
properties: INodeProperties[] = [];
|
|
15
|
-
}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function createRegularClass(): string {
|
|
19
|
-
return `
|
|
20
|
-
export class SomeHelper {
|
|
21
|
-
name = 'githubApi';
|
|
22
|
-
}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
ruleTester.run('cred-filename-against-convention', CredFilenameAgainstConventionRule, {
|
|
26
|
-
valid: [
|
|
27
|
-
{
|
|
28
|
-
name: 'filename matches credential class name',
|
|
29
|
-
filename: '/tmp/GithubApi.credentials.ts',
|
|
30
|
-
code: createCredentialCode('GithubApi'),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: 'multi-word class name matches filename',
|
|
34
|
-
filename: '/tmp/GoogleSheetsOAuth2Api.credentials.ts',
|
|
35
|
-
code: createCredentialCode('GoogleSheetsOAuth2Api'),
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'class not implementing ICredentialType is ignored',
|
|
39
|
-
filename: '/tmp/Github.credentials.ts',
|
|
40
|
-
code: createRegularClass(),
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: 'non-.credentials.ts file is ignored',
|
|
44
|
-
filename: '/tmp/GithubApi.ts',
|
|
45
|
-
code: createCredentialCode('Mismatch'),
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
invalid: [
|
|
49
|
-
{
|
|
50
|
-
name: 'filename does not match class name',
|
|
51
|
-
filename: '/tmp/GithubApi.credentials.ts',
|
|
52
|
-
code: createCredentialCode('GitlabApi'),
|
|
53
|
-
errors: [
|
|
54
|
-
{
|
|
55
|
-
messageId: 'renameFile',
|
|
56
|
-
data: { className: 'GitlabApi', expected: 'GitlabApi.credentials.ts' },
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'filename has wrong casing',
|
|
62
|
-
filename: '/tmp/githubApi.credentials.ts',
|
|
63
|
-
code: createCredentialCode('GithubApi'),
|
|
64
|
-
errors: [
|
|
65
|
-
{
|
|
66
|
-
messageId: 'renameFile',
|
|
67
|
-
data: { className: 'GithubApi', expected: 'GithubApi.credentials.ts' },
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
});
|