@hustleops/n8n-nodes-hustleops 0.1.5

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.
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ENTITY_TAG_OPERATION_OPTIONS = exports.TAG_OPERATION_OPTIONS = exports.TAG_RESOURCE_OPTION = void 0;
4
+ exports.parseTagValues = parseTagValues;
5
+ exports.parseEntityTagValues = parseEntityTagValues;
6
+ exports.sanitizeTagBody = sanitizeTagBody;
7
+ const n8n_workflow_1 = require("n8n-workflow");
8
+ const GenericFunctions_1 = require("./GenericFunctions");
9
+ const TAG_VALUE_PATTERN = /^[\p{L}\p{N} *!@#$:._=-]+$/u;
10
+ const TAG_COLOR_PATTERN = /^#[0-9a-f]{6}$/i;
11
+ const TAG_BODY_FIELDS_BY_OPERATION = {
12
+ list: new Set(),
13
+ search: new Set(),
14
+ create: new Set(['value', 'color']),
15
+ updateColor: new Set(['color']),
16
+ bulkUpdateColor: new Set(['ids', 'color']),
17
+ delete: new Set(),
18
+ bulkDelete: new Set(['ids', 'force']),
19
+ };
20
+ const MAX_TAGS_PER_ENTITY = 20;
21
+ const MAX_TAG_VALUE_LENGTH = 30;
22
+ const MAX_BULK_TAG_IDS = 100;
23
+ const TAG_DEFINITION_NODE = {
24
+ id: 'hustleops-tag-definitions',
25
+ name: 'HustleOps',
26
+ type: 'hustleOps',
27
+ typeVersion: 1,
28
+ position: [0, 0],
29
+ parameters: {},
30
+ };
31
+ exports.TAG_RESOURCE_OPTION = {
32
+ name: 'Tag',
33
+ value: 'tag',
34
+ description: 'HustleOps tags and tag color administration',
35
+ };
36
+ exports.TAG_OPERATION_OPTIONS = [
37
+ {
38
+ name: 'List',
39
+ value: 'list',
40
+ description: 'List tags, optionally including admin-only usage counts',
41
+ action: 'List tags',
42
+ },
43
+ {
44
+ name: 'Search',
45
+ value: 'search',
46
+ description: 'Search tags with a SearchRequest body. Requires Admin.',
47
+ action: 'Search tags',
48
+ },
49
+ {
50
+ name: 'Create',
51
+ value: 'create',
52
+ description: 'Create a tag. Requires Admin.',
53
+ action: 'Create a tag',
54
+ },
55
+ {
56
+ name: 'Update Color',
57
+ value: 'updateColor',
58
+ description: 'Update a tag color. Tag value is immutable. Requires Admin.',
59
+ action: 'Update a tag color',
60
+ },
61
+ {
62
+ name: 'Bulk Update Color',
63
+ value: 'bulkUpdateColor',
64
+ description: 'Update color for multiple tags. Requires Admin.',
65
+ action: 'Bulk update tag color',
66
+ },
67
+ {
68
+ name: 'Delete',
69
+ value: 'delete',
70
+ description: 'Delete one tag. Requires Admin.',
71
+ action: 'Delete a tag',
72
+ },
73
+ {
74
+ name: 'Bulk Delete',
75
+ value: 'bulkDelete',
76
+ description: 'Delete multiple tags. Requires Admin.',
77
+ action: 'Bulk delete tags',
78
+ },
79
+ ];
80
+ exports.ENTITY_TAG_OPERATION_OPTIONS = [
81
+ {
82
+ name: 'Set Tags',
83
+ value: 'setTags',
84
+ description: 'Replace all tags for the selected entity. Empty values clear all tags.',
85
+ action: 'Set entity tags',
86
+ },
87
+ {
88
+ name: 'Add Tags',
89
+ value: 'addTags',
90
+ description: 'Add one or more tags to the selected entity',
91
+ action: 'Add entity tags',
92
+ },
93
+ {
94
+ name: 'Remove Tag',
95
+ value: 'removeTag',
96
+ description: 'Remove one tag from the selected entity by tag ID',
97
+ action: 'Remove an entity tag',
98
+ },
99
+ ];
100
+ function nodeError(context, message, itemIndex) {
101
+ var _a, _b;
102
+ return new n8n_workflow_1.NodeOperationError((_b = (_a = context.getNode) === null || _a === void 0 ? void 0 : _a.call(context)) !== null && _b !== void 0 ? _b : TAG_DEFINITION_NODE, message, {
103
+ itemIndex,
104
+ });
105
+ }
106
+ function assertAllowedKeys(context, operation, body, itemIndex) {
107
+ for (const key of Object.keys(body)) {
108
+ if (!TAG_BODY_FIELDS_BY_OPERATION[operation].has(key)) {
109
+ throw nodeError(context, `Unsupported Tag ${operation} field: ${key}`, itemIndex);
110
+ }
111
+ }
112
+ }
113
+ function tagValue(context, value, itemIndex) {
114
+ if (typeof value !== 'string') {
115
+ throw nodeError(context, 'Tag value must be a string.', itemIndex);
116
+ }
117
+ const normalized = value.trim().replace(/\s+/g, ' ');
118
+ if (normalized === '') {
119
+ throw nodeError(context, 'Tag value is required.', itemIndex);
120
+ }
121
+ if (normalized.length > MAX_TAG_VALUE_LENGTH) {
122
+ throw nodeError(context, `Tag value cannot exceed ${MAX_TAG_VALUE_LENGTH} characters.`, itemIndex);
123
+ }
124
+ if (!TAG_VALUE_PATTERN.test(normalized)) {
125
+ throw nodeError(context, 'Tag values may only contain letters, numbers, spaces, and * ! @ # $ : . - _ =.', itemIndex);
126
+ }
127
+ return normalized;
128
+ }
129
+ function optionalColor(context, value, itemIndex) {
130
+ if (value === undefined || value === null || value === '') {
131
+ return undefined;
132
+ }
133
+ if (typeof value !== 'string' || !TAG_COLOR_PATTERN.test(value)) {
134
+ throw nodeError(context, 'Tag color must be a #RRGGBB hex color.', itemIndex);
135
+ }
136
+ return value;
137
+ }
138
+ function requiredColor(context, value, itemIndex) {
139
+ const color = optionalColor(context, value, itemIndex);
140
+ if (!color) {
141
+ throw nodeError(context, 'Tag color is required.', itemIndex);
142
+ }
143
+ return color;
144
+ }
145
+ function optionalBoolean(context, value, label, itemIndex) {
146
+ if (value === undefined) {
147
+ return undefined;
148
+ }
149
+ if (typeof value !== 'boolean') {
150
+ throw nodeError(context, `${label} must be a boolean.`, itemIndex);
151
+ }
152
+ return value;
153
+ }
154
+ function uuidArray(context, value, label, itemIndex) {
155
+ if (!Array.isArray(value) || value.length === 0) {
156
+ throw nodeError(context, `${label} must contain at least one ID.`, itemIndex);
157
+ }
158
+ if (value.length > MAX_BULK_TAG_IDS) {
159
+ throw nodeError(context, `${label} cannot contain more than ${MAX_BULK_TAG_IDS} IDs.`, itemIndex);
160
+ }
161
+ return value.map((id) => {
162
+ try {
163
+ return (0, GenericFunctions_1.assertUuid)(id, 'Tag ID');
164
+ }
165
+ catch {
166
+ throw nodeError(context, `${label} must contain valid UUIDs.`, itemIndex);
167
+ }
168
+ });
169
+ }
170
+ function parseTagValues(context, value, label, itemIndex, options = {}) {
171
+ var _a;
172
+ const rawValues = (0, GenericFunctions_1.parseJsonArray)(context, value, label, itemIndex);
173
+ if (rawValues.length > MAX_TAGS_PER_ENTITY) {
174
+ throw nodeError(context, `Entity tags cannot contain more than ${MAX_TAGS_PER_ENTITY} values.`, itemIndex);
175
+ }
176
+ const values = rawValues.map((tag) => tagValue(context, tag, itemIndex));
177
+ if (!options.allowEmpty && values.length === 0) {
178
+ throw nodeError(context, (_a = options.emptyMessage) !== null && _a !== void 0 ? _a : `${label} requires at least one tag value.`, itemIndex);
179
+ }
180
+ return values;
181
+ }
182
+ function parseEntityTagValues(context, value, operationLabel, itemIndex) {
183
+ return parseTagValues(context, value, 'Tag Values', itemIndex, {
184
+ allowEmpty: operationLabel === 'Set Tags',
185
+ emptyMessage: 'Add Tags requires at least one tag value.',
186
+ });
187
+ }
188
+ function sanitizeTagBody(context, operation, body, itemIndex) {
189
+ assertAllowedKeys(context, operation, body, itemIndex);
190
+ if (operation === 'search') {
191
+ return body;
192
+ }
193
+ if (operation === 'create') {
194
+ return (0, GenericFunctions_1.compactObject)({
195
+ value: tagValue(context, body.value, itemIndex),
196
+ color: optionalColor(context, body.color, itemIndex),
197
+ });
198
+ }
199
+ if (operation === 'updateColor') {
200
+ return { color: requiredColor(context, body.color, itemIndex) };
201
+ }
202
+ if (operation === 'bulkUpdateColor') {
203
+ return (0, GenericFunctions_1.compactObject)({
204
+ ids: uuidArray(context, body.ids, 'Tag bulk update IDs', itemIndex),
205
+ color: optionalColor(context, body.color, itemIndex),
206
+ });
207
+ }
208
+ if (operation === 'bulkDelete') {
209
+ return (0, GenericFunctions_1.compactObject)({
210
+ ids: uuidArray(context, body.ids, 'Tag bulk delete IDs', itemIndex),
211
+ force: optionalBoolean(context, body.force, 'Tag bulk delete force', itemIndex),
212
+ });
213
+ }
214
+ return {};
215
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@hustleops/n8n-nodes-hustleops",
3
+ "version": "0.1.5",
4
+ "description": "n8n community node package for HustleOps incident response workflows.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Dmytro Kosiuk",
8
+ "email": "misterr.minister@gmail.com"
9
+ },
10
+ "homepage": "https://github.com/HustleOps/hustleops-n8n-plugin#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/HustleOps/hustleops-n8n-plugin.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/HustleOps/hustleops-n8n-plugin/issues"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org/"
21
+ },
22
+ "keywords": [
23
+ "n8n-community-node-package",
24
+ "n8n",
25
+ "hustleops",
26
+ "incident-response",
27
+ "security-operations",
28
+ "alerts",
29
+ "incidents"
30
+ ],
31
+ "scripts": {
32
+ "build": "n8n-node build",
33
+ "build:watch": "tsc --watch",
34
+ "dev": "n8n-node dev",
35
+ "format": "prettier --write .",
36
+ "format:check": "prettier --check .",
37
+ "lint": "n8n-node lint",
38
+ "lint:fix": "n8n-node lint --fix",
39
+ "release": "n8n-node release",
40
+ "prepublishOnly": "n8n-node prerelease",
41
+ "typecheck": "tsc --noEmit -p tsconfig.json",
42
+ "test": "npm run build && npm run test:unit",
43
+ "test:unit": "node --test test/*.test.cjs"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "n8n": {
49
+ "n8nNodesApiVersion": 1,
50
+ "strict": true,
51
+ "credentials": [
52
+ "dist/credentials/HustleOpsApi.credentials.js"
53
+ ],
54
+ "nodes": [
55
+ "dist/nodes/HustleOps/HustleOps.node.js"
56
+ ]
57
+ },
58
+ "devDependencies": {
59
+ "@n8n/node-cli": "0.37.2",
60
+ "auto-changelog": "^2.5.0",
61
+ "eslint": "9.29.0",
62
+ "prettier": "3.6.2",
63
+ "release-it": "^20.2.1",
64
+ "typescript": "5.9.2"
65
+ },
66
+ "peerDependencies": {
67
+ "n8n-workflow": "*"
68
+ }
69
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@hustleops/n8n-nodes-hustleops",
3
+ "version": "0.1.5",
4
+ "description": "n8n community node package for HustleOps incident response workflows.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Dmytro Kosiuk",
8
+ "email": "misterr.minister@gmail.com"
9
+ },
10
+ "homepage": "https://github.com/HustleOps/hustleops-n8n-plugin#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/HustleOps/hustleops-n8n-plugin.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/HustleOps/hustleops-n8n-plugin/issues"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org/"
21
+ },
22
+ "keywords": [
23
+ "n8n-community-node-package",
24
+ "n8n",
25
+ "hustleops",
26
+ "incident-response",
27
+ "security-operations",
28
+ "alerts",
29
+ "incidents"
30
+ ],
31
+ "scripts": {
32
+ "build": "n8n-node build",
33
+ "build:watch": "tsc --watch",
34
+ "dev": "n8n-node dev",
35
+ "format": "prettier --write .",
36
+ "format:check": "prettier --check .",
37
+ "lint": "n8n-node lint",
38
+ "lint:fix": "n8n-node lint --fix",
39
+ "release": "n8n-node release",
40
+ "prepublishOnly": "n8n-node prerelease",
41
+ "typecheck": "tsc --noEmit -p tsconfig.json",
42
+ "test": "npm run build && npm run test:unit",
43
+ "test:unit": "node --test test/*.test.cjs"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "n8n": {
49
+ "n8nNodesApiVersion": 1,
50
+ "strict": true,
51
+ "credentials": [
52
+ "dist/credentials/HustleOpsApi.credentials.js"
53
+ ],
54
+ "nodes": [
55
+ "dist/nodes/HustleOps/HustleOps.node.js"
56
+ ]
57
+ },
58
+ "devDependencies": {
59
+ "@n8n/node-cli": "0.37.2",
60
+ "auto-changelog": "^2.5.0",
61
+ "eslint": "9.29.0",
62
+ "prettier": "3.6.2",
63
+ "release-it": "^20.2.1",
64
+ "typescript": "5.9.2"
65
+ },
66
+ "peerDependencies": {
67
+ "n8n-workflow": "*"
68
+ }
69
+ }