n8n-nodes-azuredevops-advanced 0.1.2 → 0.3.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.
@@ -3,7 +3,7 @@ import { ICredentialType, INodeProperties } from 'n8n-workflow';
3
3
  export class AzureDevOpsAdvancedApi implements ICredentialType {
4
4
  name = 'azureDevOpsAdvancedApi';
5
5
  displayName = 'Azure DevOps Advanced API';
6
- // Belgelendirme linkine doğrudan PAT nasıl alınır yönlendirilebilir
6
+ // Documentation link explaining how to obtain a Personal Access Token
7
7
  documentationUrl = 'https://n8n.io/integrations/azure-devops';
8
8
  properties: INodeProperties[] = [
9
9
  {
@@ -21,7 +21,7 @@ export class AzureDevOpsAdvancedApi implements ICredentialType {
21
21
  typeOptions: { password: true },
22
22
  default: '',
23
23
  required: true,
24
- description: 'Tüm repolara Read/Write yetkilendirmesi olan Azure DevOps PAT anahtarınız',
24
+ description: 'Your Azure DevOps Personal Access Token with Read/Write access to all required resources',
25
25
  },
26
26
  ];
27
27
  }
@@ -5,7 +5,7 @@ class AzureDevOpsAdvancedApi {
5
5
  constructor() {
6
6
  this.name = 'azureDevOpsAdvancedApi';
7
7
  this.displayName = 'Azure DevOps Advanced API';
8
- // Belgelendirme linkine doğrudan PAT nasıl alınır yönlendirilebilir
8
+ // Documentation link explaining how to obtain a Personal Access Token
9
9
  this.documentationUrl = 'https://n8n.io/integrations/azure-devops';
10
10
  this.properties = [
11
11
  {
@@ -23,7 +23,7 @@ class AzureDevOpsAdvancedApi {
23
23
  typeOptions: { password: true },
24
24
  default: '',
25
25
  required: true,
26
- description: 'Tüm repolara Read/Write yetkilendirmesi olan Azure DevOps PAT anahtarınız',
26
+ description: 'Your Azure DevOps Personal Access Token with Read/Write access to all required resources',
27
27
  },
28
28
  ];
29
29
  }
@@ -21,7 +21,7 @@ class AzureDevOpsAdvanced {
21
21
  group: ['transform'],
22
22
  version: 1,
23
23
  subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
24
- description: 'Kapsamlı yerel Azure DevOps eklentisi',
24
+ description: 'Comprehensive Azure DevOps integration node',
25
25
  defaults: { name: 'Azure DevOps Advanced' },
26
26
  inputs: ['main'],
27
27
  outputs: ['main'],
@@ -70,7 +70,7 @@ class AzureDevOpsAdvanced {
70
70
  };
71
71
  }
72
72
  async execute() {
73
- var _a, _b, _c, _d;
73
+ var _a, _b, _c, _d, _e, _f;
74
74
  const items = this.getInputData();
75
75
  const returnData = [];
76
76
  const resource = this.getNodeParameter('resource', 0);
@@ -95,25 +95,45 @@ class AzureDevOpsAdvanced {
95
95
  else if (operation === 'createBranch') {
96
96
  const repoId = this.getNodeParameter('repositoryId', i);
97
97
  const branchName = this.getNodeParameter('branchName', i);
98
- // To create a branch, we need the oldObjectID from the repo (usually main/master)
99
- // Simplified implementation for the boilerplate. Requires fetching refs first.
100
- const endpoint = `${project}/_apis/git/repositories/${repoId}/refs?filter=heads/main&api-version=7.1`;
101
- const refResponse = await GenericFunctions_1.azureApiRequest.call(this, 'GET', endpoint);
102
- const oldObjectId = ((_b = (_a = refResponse === null || refResponse === void 0 ? void 0 : refResponse.value) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.objectId) || "0000000000000000000000000000000000000000";
98
+ // Try main first, fall back to master
99
+ let oldObjectId = '0000000000000000000000000000000000000000';
100
+ for (const baseBranch of ['main', 'master']) {
101
+ const refResponse = await GenericFunctions_1.azureApiRequest.call(this, 'GET', `${project}/_apis/git/repositories/${repoId}/refs?filter=heads/${baseBranch}&api-version=7.1`);
102
+ if ((_b = (_a = refResponse === null || refResponse === void 0 ? void 0 : refResponse.value) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.objectId) {
103
+ oldObjectId = refResponse.value[0].objectId;
104
+ break;
105
+ }
106
+ }
103
107
  const createEndpoint = `${project}/_apis/git/repositories/${repoId}/refs?api-version=7.1`;
104
- const body = [{ name: branchName, newObjectId: oldObjectId, oldObjectId: "0000000000000000000000000000000000000000" }];
108
+ const body = [{ name: branchName, newObjectId: oldObjectId, oldObjectId: '0000000000000000000000000000000000000000' }];
105
109
  responseData = await GenericFunctions_1.azureApiRequest.call(this, 'POST', createEndpoint, body);
110
+ responseData = (responseData === null || responseData === void 0 ? void 0 : responseData.value) || responseData;
106
111
  }
107
112
  else if (operation === 'pushCommit') {
108
113
  const repoId = this.getNodeParameter('repositoryId', i);
109
114
  const branchName = this.getNodeParameter('branchName', i);
110
115
  const commitMessage = this.getNodeParameter('commitMessage', i);
111
- // Requires complex PUSH structure with base64 encoded strings for files.
112
- // Simplified placeholder structure
116
+ const pushFilePath = this.getNodeParameter('pushFilePath', i);
117
+ const fileContent = this.getNodeParameter('fileContent', i);
118
+ const changeType = this.getNodeParameter('changeType', i);
119
+ // Get current branch HEAD to use as oldObjectId
120
+ const branchFilter = branchName.replace('refs/heads/', '');
121
+ const refResponse = await GenericFunctions_1.azureApiRequest.call(this, 'GET', `${project}/_apis/git/repositories/${repoId}/refs?filter=heads/${branchFilter}&api-version=7.1`);
122
+ const oldObjectId = ((_d = (_c = refResponse === null || refResponse === void 0 ? void 0 : refResponse.value) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.objectId) || '0000000000000000000000000000000000000000';
113
123
  const endpoint = `${project}/_apis/git/repositories/${repoId}/pushes?api-version=7.1`;
114
124
  const body = {
115
- refUpdates: [{ name: branchName, oldObjectId: "0000000000000000000000000000000000000000" }],
116
- commits: [{ comment: commitMessage, changes: [] }]
125
+ refUpdates: [{ name: branchName, oldObjectId }],
126
+ commits: [{
127
+ comment: commitMessage,
128
+ changes: [{
129
+ changeType,
130
+ item: { path: pushFilePath.startsWith('/') ? pushFilePath : `/${pushFilePath}` },
131
+ newContent: {
132
+ content: Buffer.from(fileContent).toString('base64'),
133
+ contentType: 'base64Encoded',
134
+ },
135
+ }],
136
+ }],
117
137
  };
118
138
  responseData = await GenericFunctions_1.azureApiRequest.call(this, 'POST', endpoint, body);
119
139
  }
@@ -169,7 +189,7 @@ class AzureDevOpsAdvanced {
169
189
  const endpoint = `${project}/_apis/wit/wiql?api-version=7.1`;
170
190
  const wiqlQuery = { query: `Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.TeamProject] = '${project}'` };
171
191
  const idsResponse = await GenericFunctions_1.azureApiRequest.call(this, 'POST', endpoint, wiqlQuery);
172
- const idsArray = (_c = idsResponse === null || idsResponse === void 0 ? void 0 : idsResponse.workItems) === null || _c === void 0 ? void 0 : _c.map((wi) => wi.id);
192
+ const idsArray = (_e = idsResponse === null || idsResponse === void 0 ? void 0 : idsResponse.workItems) === null || _e === void 0 ? void 0 : _e.map((wi) => wi.id);
173
193
  if (idsArray && idsArray.length > 0) {
174
194
  const workItemIds = idsArray.join(',');
175
195
  const bulkEndpoint = `${project}/_apis/wit/workitems?ids=${workItemIds}&api-version=7.1`;
@@ -195,14 +215,14 @@ class AzureDevOpsAdvanced {
195
215
  if (!isUpdate) {
196
216
  mapPropertyToPatch('System.Title', this.getNodeParameter('title', i));
197
217
  }
198
- const additionalFields = this.getNodeParameter('additionalFields', i);
199
- if (Object.keys(additionalFields).length) {
218
+ const additionalFields = this.getNodeParameter('additionalFields', i, {});
219
+ if (additionalFields && Object.keys(additionalFields).length) {
200
220
  mapPropertyToPatch('System.Description', additionalFields.description);
201
221
  mapPropertyToPatch('System.AssignedTo', additionalFields.assignedTo);
202
222
  mapPropertyToPatch('System.State', additionalFields.state);
203
223
  mapPropertyToPatch('Microsoft.VSTS.Common.Priority', additionalFields.priority);
204
224
  mapPropertyToPatch('System.Tags', additionalFields.tags);
205
- if ((_d = additionalFields.customFieldsUi) === null || _d === void 0 ? void 0 : _d.customFieldsValues) {
225
+ if ((_f = additionalFields.customFieldsUi) === null || _f === void 0 ? void 0 : _f.customFieldsValues) {
206
226
  for (const customField of additionalFields.customFieldsUi.customFieldsValues) {
207
227
  mapPropertyToPatch(customField.fieldId, customField.fieldValue);
208
228
  }
@@ -223,7 +243,7 @@ class AzureDevOpsAdvanced {
223
243
  });
224
244
  }
225
245
  else if (operation === 'listUsers') {
226
- // Azure Graph API / Core API üzerinden user listesi çekilir
246
+ // Users are fetched via the Azure Graph API (vssps endpoint)
227
247
  const endpoint = `_apis/graph/users?api-version=7.1-preview.1`;
228
248
  const credentials = await this.getCredentials('azureDevOpsAdvancedApi');
229
249
  const baseUrl = `https://vssps.dev.azure.com/${credentials.organization}`;
@@ -240,7 +260,7 @@ class AzureDevOpsAdvanced {
240
260
  responseData = (responseData === null || responseData === void 0 ? void 0 : responseData.value) || responseData;
241
261
  }
242
262
  else if (operation === 'listTags') {
243
- // Project bazlı tag listesi
263
+ // Retrieve all work item tags scoped to the project
244
264
  const endpoint = `${project}/_apis/wit/tags?api-version=7.1-preview.1`;
245
265
  responseData = await GenericFunctions_1.azureApiRequest.call(this, 'GET', endpoint);
246
266
  responseData = (responseData === null || responseData === void 0 ? void 0 : responseData.value) || responseData;
@@ -256,7 +276,7 @@ class AzureDevOpsAdvanced {
256
276
  }
257
277
  else if (operation === 'getComments') {
258
278
  const pullRequestId = this.getNodeParameter('pullRequestId', i);
259
- const endpoint = `${project}/_apis/git/repositories/${repositoryId}/pullRequests/${pullRequestId}/threads?api-version=7.1`;
279
+ const endpoint = `${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=7.1`;
260
280
  responseData = await GenericFunctions_1.azureApiRequest.call(this, 'GET', endpoint);
261
281
  responseData = (responseData === null || responseData === void 0 ? void 0 : responseData.value) || responseData;
262
282
  }
@@ -264,12 +284,9 @@ class AzureDevOpsAdvanced {
264
284
  const listOptions = this.getNodeParameter('listOptions', i);
265
285
  let endpoint = `${project}/_apis/git/repositories/${repositoryId}/pullrequests?api-version=7.1`;
266
286
  if (listOptions) {
267
- if (listOptions.status && listOptions.status !== 'all') {
287
+ if (listOptions.status) {
268
288
  endpoint += `&searchCriteria.status=${listOptions.status}`;
269
289
  }
270
- else if (listOptions.status === 'all') {
271
- endpoint += `&searchCriteria.status=all`;
272
- }
273
290
  if (listOptions.creatorId)
274
291
  endpoint += `&searchCriteria.creatorId=${listOptions.creatorId}`;
275
292
  if (listOptions.reviewerId)
@@ -505,7 +522,7 @@ class AzureDevOpsAdvanced {
505
522
  });
506
523
  }
507
524
  }
508
- // Sonuçların işlenmesi
525
+ // Process and return results
509
526
  if (Array.isArray(responseData)) {
510
527
  returnData.push.apply(returnData, responseData.map(item => ({ json: item })));
511
528
  }
@@ -19,7 +19,7 @@ async function azureApiRequest(method, endpoint, body = {}, query = {}) {
19
19
  },
20
20
  json: true,
21
21
  };
22
- if (Object.keys(body).length === 0) {
22
+ if (!Array.isArray(body) && Object.keys(body).length === 0) {
23
23
  delete options.body;
24
24
  }
25
25
  return this.helpers.request(options);
@@ -90,6 +90,36 @@ exports.gitFields = [
90
90
  },
91
91
  },
92
92
  },
93
+ {
94
+ displayName: 'File Path',
95
+ name: 'pushFilePath',
96
+ type: 'string',
97
+ default: '',
98
+ required: true,
99
+ displayOptions: {
100
+ show: {
101
+ resource: ['git'],
102
+ operation: ['pushCommit'],
103
+ },
104
+ },
105
+ description: 'Path of the file to create or update (e.g. /src/app.ts)',
106
+ },
107
+ {
108
+ displayName: 'Change Type',
109
+ name: 'changeType',
110
+ type: 'options',
111
+ options: [
112
+ { name: 'Edit (update existing file)', value: 'edit' },
113
+ { name: 'Add (create new file)', value: 'add' },
114
+ ],
115
+ default: 'edit',
116
+ displayOptions: {
117
+ show: {
118
+ resource: ['git'],
119
+ operation: ['pushCommit'],
120
+ },
121
+ },
122
+ },
93
123
  {
94
124
  displayName: 'File Content',
95
125
  name: 'fileContent',
@@ -41,7 +41,7 @@ exports.pipelineFields = [
41
41
  displayOptions: {
42
42
  show: {
43
43
  resource: ['pipeline'],
44
- operation: ['run', 'getLogs'],
44
+ operation: ['run', 'getLogs', 'cancelRun'],
45
45
  },
46
46
  },
47
47
  default: 0,
@@ -19,7 +19,7 @@ export class AzureDevOpsAdvanced implements INodeType {
19
19
  group: ['transform'],
20
20
  version: 1,
21
21
  subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
22
- description: 'Kapsamlı yerel Azure DevOps eklentisi',
22
+ description: 'Comprehensive Azure DevOps integration node',
23
23
  defaults: { name: 'Azure DevOps Advanced' },
24
24
  inputs: ['main'],
25
25
  outputs: ['main'],
@@ -94,26 +94,49 @@ export class AzureDevOpsAdvanced implements INodeType {
94
94
  else if (operation === 'createBranch') {
95
95
  const repoId = this.getNodeParameter('repositoryId', i) as string;
96
96
  const branchName = this.getNodeParameter('branchName', i) as string;
97
- // To create a branch, we need the oldObjectID from the repo (usually main/master)
98
- // Simplified implementation for the boilerplate. Requires fetching refs first.
99
- const endpoint = `${project}/_apis/git/repositories/${repoId}/refs?filter=heads/main&api-version=7.1`;
100
- const refResponse = await azureApiRequest.call(this, 'GET', endpoint);
101
- const oldObjectId = refResponse?.value?.[0]?.objectId || "0000000000000000000000000000000000000000";
97
+
98
+ // Try main first, fall back to master
99
+ let oldObjectId = '0000000000000000000000000000000000000000';
100
+ for (const baseBranch of ['main', 'master']) {
101
+ const refResponse = await azureApiRequest.call(this, 'GET', `${project}/_apis/git/repositories/${repoId}/refs?filter=heads/${baseBranch}&api-version=7.1`);
102
+ if (refResponse?.value?.[0]?.objectId) {
103
+ oldObjectId = refResponse.value[0].objectId;
104
+ break;
105
+ }
106
+ }
102
107
 
103
108
  const createEndpoint = `${project}/_apis/git/repositories/${repoId}/refs?api-version=7.1`;
104
- const body = [{ name: branchName, newObjectId: oldObjectId, oldObjectId: "0000000000000000000000000000000000000000" }];
109
+ const body = [{ name: branchName, newObjectId: oldObjectId, oldObjectId: '0000000000000000000000000000000000000000' }];
105
110
  responseData = await azureApiRequest.call(this, 'POST', createEndpoint, body);
111
+ responseData = responseData?.value || responseData;
106
112
  }
107
113
  else if (operation === 'pushCommit') {
108
114
  const repoId = this.getNodeParameter('repositoryId', i) as string;
109
115
  const branchName = this.getNodeParameter('branchName', i) as string;
110
116
  const commitMessage = this.getNodeParameter('commitMessage', i) as string;
111
- // Requires complex PUSH structure with base64 encoded strings for files.
112
- // Simplified placeholder structure
117
+ const pushFilePath = this.getNodeParameter('pushFilePath', i) as string;
118
+ const fileContent = this.getNodeParameter('fileContent', i) as string;
119
+ const changeType = this.getNodeParameter('changeType', i) as string;
120
+
121
+ // Get current branch HEAD to use as oldObjectId
122
+ const branchFilter = branchName.replace('refs/heads/', '');
123
+ const refResponse = await azureApiRequest.call(this, 'GET', `${project}/_apis/git/repositories/${repoId}/refs?filter=heads/${branchFilter}&api-version=7.1`);
124
+ const oldObjectId = refResponse?.value?.[0]?.objectId || '0000000000000000000000000000000000000000';
125
+
113
126
  const endpoint = `${project}/_apis/git/repositories/${repoId}/pushes?api-version=7.1`;
114
127
  const body = {
115
- refUpdates: [{ name: branchName, oldObjectId: "0000000000000000000000000000000000000000" }],
116
- commits: [{ comment: commitMessage, changes: [] }]
128
+ refUpdates: [{ name: branchName, oldObjectId }],
129
+ commits: [{
130
+ comment: commitMessage,
131
+ changes: [{
132
+ changeType,
133
+ item: { path: pushFilePath.startsWith('/') ? pushFilePath : `/${pushFilePath}` },
134
+ newContent: {
135
+ content: Buffer.from(fileContent).toString('base64'),
136
+ contentType: 'base64Encoded',
137
+ },
138
+ }],
139
+ }],
117
140
  };
118
141
  responseData = await azureApiRequest.call(this, 'POST', endpoint, body);
119
142
  }
@@ -203,8 +226,8 @@ export class AzureDevOpsAdvanced implements INodeType {
203
226
  mapPropertyToPatch('System.Title', this.getNodeParameter('title', i));
204
227
  }
205
228
 
206
- const additionalFields = this.getNodeParameter('additionalFields', i) as any;
207
- if (Object.keys(additionalFields).length) {
229
+ const additionalFields = this.getNodeParameter('additionalFields', i, {}) as any;
230
+ if (additionalFields && Object.keys(additionalFields).length) {
208
231
  mapPropertyToPatch('System.Description', additionalFields.description);
209
232
  mapPropertyToPatch('System.AssignedTo', additionalFields.assignedTo);
210
233
  mapPropertyToPatch('System.State', additionalFields.state);
@@ -234,7 +257,7 @@ export class AzureDevOpsAdvanced implements INodeType {
234
257
  });
235
258
  }
236
259
  else if (operation === 'listUsers') {
237
- // Azure Graph API / Core API üzerinden user listesi çekilir
260
+ // Users are fetched via the Azure Graph API (vssps endpoint)
238
261
  const endpoint = `_apis/graph/users?api-version=7.1-preview.1`;
239
262
  const credentials = await this.getCredentials('azureDevOpsAdvancedApi');
240
263
  const baseUrl = `https://vssps.dev.azure.com/${(credentials as any).organization}`;
@@ -252,7 +275,7 @@ export class AzureDevOpsAdvanced implements INodeType {
252
275
  responseData = responseData?.value || responseData;
253
276
  }
254
277
  else if (operation === 'listTags') {
255
- // Project bazlı tag listesi
278
+ // Retrieve all work item tags scoped to the project
256
279
  const endpoint = `${project}/_apis/wit/tags?api-version=7.1-preview.1`;
257
280
  responseData = await azureApiRequest.call(this, 'GET', endpoint);
258
281
  responseData = responseData?.value || responseData;
@@ -270,7 +293,7 @@ export class AzureDevOpsAdvanced implements INodeType {
270
293
  }
271
294
  else if (operation === 'getComments') {
272
295
  const pullRequestId = this.getNodeParameter('pullRequestId', i) as number;
273
- const endpoint = `${project}/_apis/git/repositories/${repositoryId}/pullRequests/${pullRequestId}/threads?api-version=7.1`;
296
+ const endpoint = `${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=7.1`;
274
297
  responseData = await azureApiRequest.call(this, 'GET', endpoint);
275
298
  responseData = responseData?.value || responseData;
276
299
  }
@@ -279,10 +302,8 @@ export class AzureDevOpsAdvanced implements INodeType {
279
302
  let endpoint = `${project}/_apis/git/repositories/${repositoryId}/pullrequests?api-version=7.1`;
280
303
 
281
304
  if (listOptions) {
282
- if (listOptions.status && listOptions.status !== 'all') {
305
+ if (listOptions.status) {
283
306
  endpoint += `&searchCriteria.status=${listOptions.status}`;
284
- } else if (listOptions.status === 'all') {
285
- endpoint += `&searchCriteria.status=all`;
286
307
  }
287
308
  if (listOptions.creatorId) endpoint += `&searchCriteria.creatorId=${listOptions.creatorId}`;
288
309
  if (listOptions.reviewerId) endpoint += `&searchCriteria.reviewerId=${listOptions.reviewerId}`;
@@ -519,7 +540,7 @@ export class AzureDevOpsAdvanced implements INodeType {
519
540
  }
520
541
  }
521
542
 
522
- // Sonuçların işlenmesi
543
+ // Process and return results
523
544
  if (Array.isArray(responseData)) {
524
545
  returnData.push.apply(returnData, responseData.map(item => ({ json: item })));
525
546
  } else {
@@ -27,7 +27,7 @@ export async function azureApiRequest(
27
27
  json: true,
28
28
  };
29
29
 
30
- if (Object.keys(body).length === 0) {
30
+ if (!Array.isArray(body) && Object.keys(body).length === 0) {
31
31
  delete options.body;
32
32
  }
33
33
 
@@ -91,6 +91,36 @@ export const gitFields: INodeProperties[] = [
91
91
  },
92
92
  },
93
93
  },
94
+ {
95
+ displayName: 'File Path',
96
+ name: 'pushFilePath',
97
+ type: 'string',
98
+ default: '',
99
+ required: true,
100
+ displayOptions: {
101
+ show: {
102
+ resource: ['git'],
103
+ operation: ['pushCommit'],
104
+ },
105
+ },
106
+ description: 'Path of the file to create or update (e.g. /src/app.ts)',
107
+ },
108
+ {
109
+ displayName: 'Change Type',
110
+ name: 'changeType',
111
+ type: 'options',
112
+ options: [
113
+ { name: 'Edit (update existing file)', value: 'edit' },
114
+ { name: 'Add (create new file)', value: 'add' },
115
+ ],
116
+ default: 'edit',
117
+ displayOptions: {
118
+ show: {
119
+ resource: ['git'],
120
+ operation: ['pushCommit'],
121
+ },
122
+ },
123
+ },
94
124
  {
95
125
  displayName: 'File Content',
96
126
  name: 'fileContent',
@@ -41,7 +41,7 @@ export const pipelineFields: INodeProperties[] = [
41
41
  displayOptions: {
42
42
  show: {
43
43
  resource: ['pipeline'],
44
- operation: ['run', 'getLogs'],
44
+ operation: ['run', 'getLogs', 'cancelRun'],
45
45
  },
46
46
  },
47
47
  default: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-azuredevops-advanced",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Comprehensive Azure DevOps custom node for n8n",
5
5
  "main": "dist/nodes/AzureDevOpsAdvanced/AzureDevOpsAdvanced.node.js",
6
6
  "license": "MIT",
@@ -38,14 +38,18 @@
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc",
41
- "dev": "tsc --watch"
41
+ "dev": "tsc --watch",
42
+ "test": "jest --forceExit"
42
43
  },
43
44
  "dependencies": {
44
45
  "n8n-core": "^1.x",
45
46
  "n8n-workflow": "^1.x"
46
47
  },
47
48
  "devDependencies": {
49
+ "@types/jest": "^30.0.0",
48
50
  "@types/node": "^18.x",
51
+ "jest": "^30.4.2",
52
+ "ts-jest": "^29.4.11",
49
53
  "typescript": "^5.x"
50
54
  },
51
55
  "publishConfig": {