@mcp-consultant-tools/azure-devops-admin 33.0.0 → 35.0.0-beta.1

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.
Files changed (31) hide show
  1. package/build/cli.js +0 -0
  2. package/build/index.js +0 -0
  3. package/build/tools/agent-pool-tools.d.ts.map +1 -1
  4. package/build/tools/agent-pool-tools.js +9 -7
  5. package/build/tools/agent-pool-tools.js.map +1 -1
  6. package/build/tools/artifact-feed-tools.d.ts.map +1 -1
  7. package/build/tools/artifact-feed-tools.js +2 -2
  8. package/build/tools/artifact-feed-tools.js.map +1 -1
  9. package/build/tools/classification-tools.d.ts.map +1 -1
  10. package/build/tools/classification-tools.js +11 -11
  11. package/build/tools/classification-tools.js.map +1 -1
  12. package/build/tools/environment-tools.d.ts.map +1 -1
  13. package/build/tools/environment-tools.js +10 -10
  14. package/build/tools/environment-tools.js.map +1 -1
  15. package/build/tools/pipeline-tools.d.ts.map +1 -1
  16. package/build/tools/pipeline-tools.js +18 -16
  17. package/build/tools/pipeline-tools.js.map +1 -1
  18. package/build/tools/project-tools.d.ts.map +1 -1
  19. package/build/tools/project-tools.js +6 -6
  20. package/build/tools/project-tools.js.map +1 -1
  21. package/build/tools/service-connection-tools.d.ts.map +1 -1
  22. package/build/tools/service-connection-tools.js +7 -7
  23. package/build/tools/service-connection-tools.js.map +1 -1
  24. package/build/tools/variable-group-tools.d.ts.map +1 -1
  25. package/build/tools/variable-group-tools.js +7 -7
  26. package/build/tools/variable-group-tools.js.map +1 -1
  27. package/package.json +1 -1
  28. package/build/AzureDevOpsAdminService.d.ts +0 -414
  29. package/build/AzureDevOpsAdminService.d.ts.map +0 -1
  30. package/build/AzureDevOpsAdminService.js +0 -1494
  31. package/build/AzureDevOpsAdminService.js.map +0 -1
@@ -1,1494 +0,0 @@
1
- import axios from 'axios';
2
- export class AzureDevOpsAdminService {
3
- config;
4
- baseUrl;
5
- feedsUrl;
6
- authHeader;
7
- apiVersion;
8
- constructor(config) {
9
- this.config = {
10
- ...config,
11
- apiVersion: config.apiVersion || '7.1',
12
- enablePipelineUpsert: config.enablePipelineUpsert ?? false,
13
- enablePipelineDelete: config.enablePipelineDelete ?? false,
14
- enableServiceConnUpsert: config.enableServiceConnUpsert ?? false,
15
- enableServiceConnDelete: config.enableServiceConnDelete ?? false,
16
- enableVariableGroupUpsert: config.enableVariableGroupUpsert ?? false,
17
- enableVariableGroupDelete: config.enableVariableGroupDelete ?? false,
18
- enableAgentPoolUpsert: config.enableAgentPoolUpsert ?? false,
19
- enableAgentPoolDisable: config.enableAgentPoolDisable ?? false,
20
- enableEnvironmentUpsert: config.enableEnvironmentUpsert ?? false,
21
- enableEnvironmentDelete: config.enableEnvironmentDelete ?? false,
22
- enableClassificationNodeUpsert: config.enableClassificationNodeUpsert ?? false,
23
- enableClassificationNodeDelete: config.enableClassificationNodeDelete ?? false,
24
- };
25
- this.baseUrl = `https://dev.azure.com/${this.config.organization}`;
26
- this.feedsUrl = `https://feeds.dev.azure.com/${this.config.organization}`;
27
- this.apiVersion = this.config.apiVersion;
28
- this.authHeader = `Basic ${Buffer.from(`:${this.config.pat}`).toString('base64')}`;
29
- }
30
- validateProject(project) {
31
- if (!this.config.projects.includes(project)) {
32
- throw new Error(`Project '${project}' is not in the allowed projects list. Allowed projects: ${this.config.projects.join(', ')}`);
33
- }
34
- }
35
- validateFeed(feedName) {
36
- if (this.config.feeds && this.config.feeds.length > 0
37
- && !this.config.feeds.includes(feedName)) {
38
- throw new Error(`Feed '${feedName}' is not in the allowed feeds list. ` +
39
- `Allowed feeds: ${this.config.feeds.join(', ')}`);
40
- }
41
- }
42
- /**
43
- * Converts a date string to ISO 8601 format with time component.
44
- * Azure DevOps Classification Nodes API requires full ISO 8601 format.
45
- * Input: "2026-02-16" → Output: "2026-02-16T00:00:00Z"
46
- */
47
- formatDateForAdo(dateStr) {
48
- // If already has time component, return as-is
49
- if (dateStr.includes('T')) {
50
- return dateStr;
51
- }
52
- // Add time component (midnight UTC)
53
- return `${dateStr}T00:00:00Z`;
54
- }
55
- async makeRequest(endpoint, method = 'GET', data, customHeaders, contentType, baseUrlOverride) {
56
- try {
57
- const url = `${baseUrlOverride || this.baseUrl}/${endpoint}`;
58
- // Determine content type: explicit override > default based on method
59
- // Note: Most PATCH endpoints use json-patch+json, but some (like classification nodes) use regular json
60
- const defaultContentType = method === 'PATCH' ? 'application/json-patch+json' : 'application/json';
61
- const response = await axios({
62
- method,
63
- url,
64
- headers: {
65
- 'Authorization': this.authHeader,
66
- 'Content-Type': contentType || defaultContentType,
67
- 'Accept': 'application/json',
68
- ...customHeaders
69
- },
70
- data
71
- });
72
- return response.data;
73
- }
74
- catch (error) {
75
- const errorDetails = error.response?.data?.message || error.response?.data || error.message;
76
- console.error('Azure DevOps Admin API request failed:', {
77
- endpoint,
78
- method,
79
- status: error.response?.status,
80
- statusText: error.response?.statusText,
81
- error: errorDetails
82
- });
83
- if (error.response?.status === 401) {
84
- throw new Error('Azure DevOps authentication failed. Please check your PAT token and permissions.');
85
- }
86
- if (error.response?.status === 403) {
87
- const detail = typeof errorDetails === 'string' ? errorDetails : '';
88
- throw new Error(`Azure DevOps access denied: ${detail || 'Please check your PAT scopes and project permissions.'}`);
89
- }
90
- if (error.response?.status === 404) {
91
- throw new Error(`Azure DevOps resource not found: ${endpoint}`);
92
- }
93
- throw new Error(`Azure DevOps Admin API request failed: ${error.message} - ${JSON.stringify(errorDetails)}`);
94
- }
95
- }
96
- // ═══════════════════════════════════════════════════════════════════════════════
97
- // PIPELINE OPERATIONS
98
- // ═══════════════════════════════════════════════════════════════════════════════
99
- async listPipelineDefinitions(project) {
100
- this.validateProject(project);
101
- const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`);
102
- return {
103
- project,
104
- totalCount: response.value.length,
105
- pipelines: response.value.map((def) => ({
106
- id: def.id,
107
- name: def.name,
108
- path: def.path,
109
- revision: def.revision,
110
- type: def.type,
111
- queueStatus: def.queueStatus,
112
- createdDate: def.createdDate,
113
- authoredBy: def.authoredBy?.displayName,
114
- repository: def.repository ? {
115
- id: def.repository.id,
116
- name: def.repository.name,
117
- type: def.repository.type
118
- } : null,
119
- process: def.process ? {
120
- type: def.process.type,
121
- yamlFilename: def.process.yamlFilename
122
- } : null,
123
- url: def._links?.web?.href
124
- }))
125
- };
126
- }
127
- async getPipelineDefinition(project, definitionId) {
128
- this.validateProject(project);
129
- const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
130
- return {
131
- id: response.id,
132
- name: response.name,
133
- path: response.path,
134
- revision: response.revision,
135
- type: response.type,
136
- queueStatus: response.queueStatus,
137
- quality: response.quality,
138
- createdDate: response.createdDate,
139
- authoredBy: response.authoredBy?.displayName,
140
- project: response.project?.name,
141
- repository: response.repository ? {
142
- id: response.repository.id,
143
- name: response.repository.name,
144
- type: response.repository.type,
145
- defaultBranch: response.repository.defaultBranch,
146
- url: response.repository.url
147
- } : null,
148
- process: response.process ? {
149
- type: response.process.type,
150
- yamlFilename: response.process.yamlFilename
151
- } : null,
152
- triggers: response.triggers || [],
153
- variables: response.variables ? Object.keys(response.variables).reduce((acc, key) => {
154
- const variable = response.variables[key];
155
- acc[key] = {
156
- value: variable.isSecret ? '***SECRET***' : variable.value,
157
- isSecret: variable.isSecret || false,
158
- allowOverride: variable.allowOverride || false
159
- };
160
- return acc;
161
- }, {}) : {},
162
- queue: response.queue ? {
163
- id: response.queue.id,
164
- name: response.queue.name,
165
- pool: response.queue.pool?.name
166
- } : null,
167
- url: response._links?.web?.href
168
- };
169
- }
170
- async getPipelineYaml(project, definitionId) {
171
- this.validateProject(project);
172
- const definition = await this.getPipelineDefinition(project, definitionId);
173
- if (definition.repository?.type && definition.repository.type !== 'TfsGit') {
174
- return {
175
- definitionId,
176
- project,
177
- pipelineName: definition.name,
178
- yamlLocation: 'external',
179
- repositoryType: definition.repository.type,
180
- repositoryName: definition.repository.name,
181
- repositoryUrl: definition.repository.url,
182
- defaultBranch: definition.repository.defaultBranch,
183
- yamlFilename: definition.process?.yamlFilename,
184
- message: `YAML is stored in external ${definition.repository.type} repository. ` +
185
- `To view the YAML content, access the file "${definition.process?.yamlFilename}" ` +
186
- `at ${definition.repository.url}`
187
- };
188
- }
189
- const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}/yaml?api-version=${this.apiVersion}`);
190
- return {
191
- definitionId,
192
- project,
193
- pipelineName: definition.name,
194
- yamlLocation: 'azureRepos',
195
- yaml: response.yaml || response
196
- };
197
- }
198
- async listPipelineRuns(project, definitionId, top = 10) {
199
- this.validateProject(project);
200
- const response = await this.makeRequest(`${project}/_apis/build/builds?definitions=${definitionId}&$top=${top}&api-version=${this.apiVersion}`);
201
- return {
202
- project,
203
- definitionId,
204
- totalCount: response.value.length,
205
- builds: response.value.map((build) => ({
206
- id: build.id,
207
- buildNumber: build.buildNumber,
208
- status: build.status,
209
- result: build.result,
210
- queueTime: build.queueTime,
211
- startTime: build.startTime,
212
- finishTime: build.finishTime,
213
- sourceBranch: build.sourceBranch,
214
- sourceVersion: build.sourceVersion,
215
- requestedBy: build.requestedBy?.displayName,
216
- requestedFor: build.requestedFor?.displayName,
217
- reason: build.reason,
218
- priority: build.priority,
219
- url: build._links?.web?.href
220
- }))
221
- };
222
- }
223
- // ═══════════════════════════════════════════════════════════════════════════════
224
- // BUILD TROUBLESHOOTING OPERATIONS (Read-only)
225
- // NOTE: These methods are duplicated in azure-devops package.
226
- // If you update these, also update packages/azure-devops/src/AzureDevOpsService.ts
227
- // ═══════════════════════════════════════════════════════════════════════════════
228
- async getBuildStatus(project, buildId, detail = 'summary', timelineScope = 'problems', maxIssues = 5) {
229
- this.validateProject(project);
230
- const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
231
- const result = {
232
- id: response.id,
233
- buildNumber: response.buildNumber,
234
- status: response.status,
235
- result: response.result,
236
- queueTime: response.queueTime,
237
- startTime: response.startTime,
238
- finishTime: response.finishTime,
239
- sourceBranch: response.sourceBranch,
240
- sourceVersion: response.sourceVersion,
241
- definition: response.definition ? {
242
- id: response.definition.id,
243
- name: response.definition.name
244
- } : null,
245
- requestedBy: response.requestedBy?.displayName,
246
- requestedFor: response.requestedFor?.displayName,
247
- reason: response.reason,
248
- priority: response.priority,
249
- project: response.project?.name,
250
- url: response._links?.web?.href
251
- };
252
- if (detail === 'timeline' || detail === 'full') {
253
- const timeline = await this.getBuildTimeline(project, buildId, timelineScope, maxIssues);
254
- result.timeline = timeline;
255
- }
256
- if (detail === 'full') {
257
- const logs = await this.getBuildLogs(project, buildId);
258
- result.logs = logs;
259
- }
260
- return result;
261
- }
262
- async getBuildTimeline(project, buildId, scope = 'problems', maxIssues = 5) {
263
- this.validateProject(project);
264
- const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/timeline?api-version=${this.apiVersion}`);
265
- const allRecords = response.records || [];
266
- const summary = {
267
- total: allRecords.length,
268
- byType: {},
269
- byResult: {},
270
- totalErrors: 0,
271
- totalWarnings: 0,
272
- failed: [],
273
- };
274
- for (const record of allRecords) {
275
- summary.byType[record.type] = (summary.byType[record.type] || 0) + 1;
276
- if (record.result) {
277
- summary.byResult[record.result] = (summary.byResult[record.result] || 0) + 1;
278
- }
279
- summary.totalErrors += record.errorCount || 0;
280
- summary.totalWarnings += record.warningCount || 0;
281
- if (record.result === 'failed' || record.result === 'canceled') {
282
- summary.failed.push(`${record.type}: ${record.name}`);
283
- }
284
- }
285
- let filteredRecords = allRecords;
286
- switch (scope) {
287
- case 'stages':
288
- filteredRecords = allRecords.filter((r) => r.type === 'Stage');
289
- break;
290
- case 'jobs':
291
- filteredRecords = allRecords.filter((r) => r.type === 'Stage' || r.type === 'Job');
292
- break;
293
- case 'problems':
294
- filteredRecords = allRecords.filter((r) => (r.errorCount && r.errorCount > 0) ||
295
- (r.warningCount && r.warningCount > 0) ||
296
- r.result === 'failed' ||
297
- r.result === 'canceled');
298
- break;
299
- case 'all':
300
- default:
301
- break;
302
- }
303
- const truncateIssues = (issues, max) => {
304
- if (!issues || issues.length === 0)
305
- return { items: [], totalCount: 0, truncated: false };
306
- const sorted = [...issues].sort((a, b) => {
307
- const priority = (issue) => {
308
- if (issue.type === 'error')
309
- return 0;
310
- if (issue.type === 'warning')
311
- return 1;
312
- return 2;
313
- };
314
- return priority(a) - priority(b);
315
- });
316
- return {
317
- items: sorted.slice(0, max),
318
- totalCount: issues.length,
319
- truncated: issues.length > max
320
- };
321
- };
322
- const mappedRecords = filteredRecords.map((record) => {
323
- const truncatedIssues = truncateIssues(record.issues, maxIssues);
324
- return {
325
- id: record.id,
326
- parentId: record.parentId,
327
- type: record.type,
328
- name: record.name,
329
- state: record.state,
330
- result: record.result,
331
- startTime: record.startTime,
332
- finishTime: record.finishTime,
333
- order: record.order,
334
- errorCount: record.errorCount,
335
- warningCount: record.warningCount,
336
- log: record.log ? { id: record.log.id } : null,
337
- issues: truncatedIssues.items,
338
- issuesTruncated: truncatedIssues.truncated,
339
- totalIssueCount: truncatedIssues.totalCount
340
- };
341
- });
342
- return {
343
- buildId,
344
- project,
345
- scope,
346
- summary,
347
- recordCount: mappedRecords.length,
348
- records: mappedRecords
349
- };
350
- }
351
- /**
352
- * Filter log content to reduce noise from progress indicators
353
- * @param content Raw log content string
354
- * @param mode Filter mode: 'summary' removes progress, 'errors' shows only errors, 'full' returns everything
355
- */
356
- filterLogContent(content, mode) {
357
- const lines = content.split('\n');
358
- const originalLineCount = lines.length;
359
- if (mode === 'full') {
360
- return { filtered: content, originalLineCount, filteredLineCount: originalLineCount };
361
- }
362
- // Progress indicator patterns to remove in summary mode
363
- const PROGRESS_PATTERNS = [
364
- /remote: Counting objects:\s+\d+%/,
365
- /remote: Compressing objects:\s+\d+%/,
366
- /Receiving objects:\s+\d+%/,
367
- /Resolving deltas:\s+\d+%/,
368
- /Unpacking objects:\s+\d+%/,
369
- /Updating files:\s+\d+%/,
370
- ];
371
- // Error/warning patterns for errors mode
372
- const ERROR_PATTERNS = [
373
- /##\[error\]/i,
374
- /##\[warning\]/i,
375
- /\berror\b.*:/i,
376
- /\bfailed\b/i,
377
- /\bexception\b/i,
378
- /\bfatal\b/i,
379
- ];
380
- const filteredLines = lines.filter(line => {
381
- // Remove timestamp prefix for pattern matching
382
- const trimmedLine = line.replace(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*/, '');
383
- if (mode === 'errors') {
384
- return ERROR_PATTERNS.some(p => p.test(trimmedLine));
385
- }
386
- // summary mode: exclude progress indicators, keep everything else
387
- return !PROGRESS_PATTERNS.some(p => p.test(trimmedLine));
388
- });
389
- return {
390
- filtered: filteredLines.join('\n'),
391
- originalLineCount,
392
- filteredLineCount: filteredLines.length
393
- };
394
- }
395
- async getBuildLogs(project, buildId, logId, mode = 'summary') {
396
- this.validateProject(project);
397
- if (logId !== undefined) {
398
- const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs/${logId}?api-version=${this.apiVersion}`);
399
- const { filtered, originalLineCount, filteredLineCount } = this.filterLogContent(response, mode);
400
- return {
401
- buildId,
402
- logId,
403
- project,
404
- mode,
405
- originalLineCount,
406
- filteredLineCount,
407
- content: filtered
408
- };
409
- }
410
- const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs?api-version=${this.apiVersion}`);
411
- return {
412
- buildId,
413
- project,
414
- totalCount: response.value.length,
415
- logs: response.value.map((log) => ({
416
- id: log.id,
417
- type: log.type,
418
- lineCount: log.lineCount,
419
- createdOn: log.createdOn,
420
- lastChangedOn: log.lastChangedOn,
421
- url: log.url
422
- }))
423
- };
424
- }
425
- async createPipelineDefinition(project, name, repositoryId, yamlPath, folder, repositoryType = 'TfsGit', repositoryUrl, defaultBranch, serviceConnectionId) {
426
- this.validateProject(project);
427
- if (!this.config.enablePipelineUpsert) {
428
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
429
- }
430
- // Validation for external repositories
431
- if (repositoryType !== 'TfsGit') {
432
- if (!repositoryUrl) {
433
- throw new Error(`repositoryUrl is required for ${repositoryType} repositories`);
434
- }
435
- if (!serviceConnectionId) {
436
- throw new Error(`serviceConnectionId is required for ${repositoryType} repositories`);
437
- }
438
- }
439
- // Normalize defaultBranch format
440
- const normalizedBranch = defaultBranch
441
- ? (defaultBranch.startsWith('refs/heads/') ? defaultBranch : `refs/heads/${defaultBranch}`)
442
- : 'refs/heads/main';
443
- // Build repository object based on type
444
- let repository;
445
- if (repositoryType === 'TfsGit') {
446
- // Azure Repos - simple format
447
- repository = {
448
- id: repositoryId,
449
- type: 'TfsGit'
450
- };
451
- }
452
- else {
453
- // GitHub / GitHubEnterprise - extended format
454
- repository = {
455
- id: repositoryId,
456
- name: repositoryId, // Same as id for GitHub
457
- type: repositoryType,
458
- url: repositoryUrl,
459
- defaultBranch: normalizedBranch,
460
- properties: {
461
- connectedServiceId: serviceConnectionId
462
- }
463
- };
464
- }
465
- const definition = {
466
- name,
467
- path: folder || '\\',
468
- type: 'build',
469
- queueStatus: 'enabled',
470
- process: {
471
- type: 2,
472
- yamlFilename: yamlPath
473
- },
474
- repository
475
- };
476
- const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`, 'POST', definition);
477
- return {
478
- id: response.id,
479
- name: response.name,
480
- path: response.path,
481
- revision: response.revision,
482
- project,
483
- repositoryType,
484
- url: response._links?.web?.href
485
- };
486
- }
487
- async updatePipelineDefinition(project, definitionId, updates) {
488
- this.validateProject(project);
489
- if (!this.config.enablePipelineUpsert) {
490
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
491
- }
492
- const current = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
493
- const updated = {
494
- ...current,
495
- ...updates,
496
- revision: current.revision
497
- };
498
- const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'PUT', updated);
499
- return {
500
- id: response.id,
501
- name: response.name,
502
- path: response.path,
503
- revision: response.revision,
504
- project,
505
- url: response._links?.web?.href
506
- };
507
- }
508
- async renamePipelineDefinition(project, definitionId, newName) {
509
- return this.updatePipelineDefinition(project, definitionId, { name: newName });
510
- }
511
- async queueBuild(project, definitionId, branch, variables, parameters) {
512
- this.validateProject(project);
513
- if (!this.config.enablePipelineUpsert) {
514
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
515
- }
516
- const buildRequest = {
517
- definition: { id: definitionId }
518
- };
519
- if (branch) {
520
- buildRequest.sourceBranch = branch;
521
- }
522
- if (variables) {
523
- buildRequest.parameters = JSON.stringify(variables);
524
- }
525
- if (parameters) {
526
- buildRequest.templateParameters = parameters;
527
- }
528
- const response = await this.makeRequest(`${project}/_apis/build/builds?api-version=${this.apiVersion}`, 'POST', buildRequest);
529
- return {
530
- id: response.id,
531
- buildNumber: response.buildNumber,
532
- status: response.status,
533
- queueTime: response.queueTime,
534
- definition: response.definition?.name,
535
- sourceBranch: response.sourceBranch,
536
- project,
537
- url: response._links?.web?.href
538
- };
539
- }
540
- async cancelBuild(project, buildId) {
541
- this.validateProject(project);
542
- if (!this.config.enablePipelineUpsert) {
543
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
544
- }
545
- const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`, 'PATCH', { status: 'cancelling' });
546
- return {
547
- id: response.id,
548
- buildNumber: response.buildNumber,
549
- status: response.status,
550
- project,
551
- message: 'Build cancellation requested'
552
- };
553
- }
554
- async retryBuild(project, buildId) {
555
- this.validateProject(project);
556
- if (!this.config.enablePipelineUpsert) {
557
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
558
- }
559
- const original = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
560
- return this.queueBuild(project, original.definition.id, original.sourceBranch);
561
- }
562
- async deletePipelineDefinition(project, definitionId) {
563
- this.validateProject(project);
564
- if (!this.config.enablePipelineDelete) {
565
- throw new Error('Pipeline delete operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_DELETE=true to enable.');
566
- }
567
- await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'DELETE');
568
- return {
569
- definitionId,
570
- project,
571
- deleted: true
572
- };
573
- }
574
- // ═══════════════════════════════════════════════════════════════════════════════
575
- // PIPELINE APPROVAL OPERATIONS
576
- // ═══════════════════════════════════════════════════════════════════════════════
577
- async listPendingApprovals(project, buildId) {
578
- this.validateProject(project);
579
- // Step 1: Fetch build timeline to find Checkpoint.Approval records
580
- const timeline = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/timeline?api-version=${this.apiVersion}`);
581
- const approvalRecords = (timeline.records || []).filter((r) => r.type === 'Checkpoint.Approval');
582
- if (approvalRecords.length === 0) {
583
- return {
584
- project,
585
- buildId,
586
- totalCount: 0,
587
- approvals: [],
588
- message: 'No approval checkpoints found for this build'
589
- };
590
- }
591
- // Step 2: Extract approval IDs from record identifiers
592
- const approvalIds = approvalRecords.map((r) => r.id);
593
- // Step 3: Query the approvals API with those IDs
594
- const approvalIdsParam = approvalIds.join(',');
595
- const approvalsResponse = await this.makeRequest(`${project}/_apis/pipelines/approvals?approvalIds=${approvalIdsParam}&$expand=steps&api-version=${this.apiVersion}`);
596
- const approvals = (approvalsResponse.value || [approvalsResponse]).map((a) => ({
597
- id: a.id,
598
- status: a.status,
599
- createdOn: a.createdOn,
600
- executionOrder: a.executionOrder,
601
- minRequiredApprovers: a.minRequiredApprovers,
602
- instructions: a.instructions,
603
- blockedApprovers: a.blockedApprovers,
604
- steps: (a.steps || []).map((s) => ({
605
- assignedApprover: s.assignedApprover?.displayName,
606
- assignedApproverId: s.assignedApprover?.uniqueName,
607
- status: s.status,
608
- comment: s.comment,
609
- initiatedOn: s.initiatedOn,
610
- lastModifiedOn: s.lastModifiedOn
611
- }))
612
- }));
613
- return {
614
- project,
615
- buildId,
616
- totalCount: approvals.length,
617
- approvals
618
- };
619
- }
620
- async approveStage(project, approvalId, status, comment) {
621
- this.validateProject(project);
622
- if (!this.config.enablePipelineUpsert) {
623
- throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
624
- }
625
- const body = [{ approvalId, status, comment: comment || '' }];
626
- const response = await this.makeRequest(`${project}/_apis/pipelines/approvals?api-version=${this.apiVersion}`, 'PATCH', body, { 'Content-Type': 'application/json' });
627
- const result = Array.isArray(response.value) ? response.value[0] : response;
628
- return {
629
- id: result.id,
630
- status: result.status,
631
- comment,
632
- project,
633
- message: `Approval ${approvalId} ${status}`
634
- };
635
- }
636
- // ═══════════════════════════════════════════════════════════════════════════════
637
- // SERVICE CONNECTION OPERATIONS
638
- // ═══════════════════════════════════════════════════════════════════════════════
639
- async listServiceConnections(project) {
640
- this.validateProject(project);
641
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`);
642
- return {
643
- project,
644
- totalCount: response.value.length,
645
- serviceConnections: response.value.map((conn) => ({
646
- id: conn.id,
647
- name: conn.name,
648
- type: conn.type,
649
- url: conn.url,
650
- description: conn.description,
651
- isShared: conn.isShared,
652
- isReady: conn.isReady,
653
- owner: conn.owner,
654
- createdBy: conn.createdBy?.displayName,
655
- authorization: conn.authorization ? {
656
- scheme: conn.authorization.scheme,
657
- parameters: conn.authorization.parameters ?
658
- Object.keys(conn.authorization.parameters).reduce((acc, key) => {
659
- acc[key] = key.toLowerCase().includes('password') ||
660
- key.toLowerCase().includes('secret') ||
661
- key.toLowerCase().includes('key') ||
662
- key.toLowerCase().includes('token')
663
- ? '***SECRET***'
664
- : conn.authorization.parameters[key];
665
- return acc;
666
- }, {}) : {}
667
- } : null
668
- }))
669
- };
670
- }
671
- async getServiceConnection(project, connectionId) {
672
- this.validateProject(project);
673
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
674
- return {
675
- id: response.id,
676
- name: response.name,
677
- type: response.type,
678
- url: response.url,
679
- description: response.description,
680
- isShared: response.isShared,
681
- isReady: response.isReady,
682
- owner: response.owner,
683
- createdBy: response.createdBy?.displayName,
684
- project,
685
- authorization: response.authorization ? {
686
- scheme: response.authorization.scheme,
687
- parameters: response.authorization.parameters ?
688
- Object.keys(response.authorization.parameters).reduce((acc, key) => {
689
- acc[key] = key.toLowerCase().includes('password') ||
690
- key.toLowerCase().includes('secret') ||
691
- key.toLowerCase().includes('key') ||
692
- key.toLowerCase().includes('token')
693
- ? '***SECRET***'
694
- : response.authorization.parameters[key];
695
- return acc;
696
- }, {}) : {}
697
- } : null,
698
- data: response.data || {},
699
- serviceEndpointProjectReferences: response.serviceEndpointProjectReferences
700
- };
701
- }
702
- async getServiceConnectionTypes() {
703
- const response = await this.makeRequest(`_apis/serviceendpoint/types?api-version=${this.apiVersion}`);
704
- return {
705
- totalCount: response.value.length,
706
- types: response.value.map((type) => ({
707
- name: type.name,
708
- displayName: type.displayName,
709
- description: type.description,
710
- helpMarkDown: type.helpMarkDown,
711
- authenticationSchemes: type.authenticationSchemes?.map((scheme) => ({
712
- scheme: scheme.scheme,
713
- displayName: scheme.displayName
714
- })) || []
715
- }))
716
- };
717
- }
718
- async createServiceConnection(project, name, type, configuration) {
719
- this.validateProject(project);
720
- if (!this.config.enableServiceConnUpsert) {
721
- throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
722
- }
723
- const endpoint = {
724
- name,
725
- type,
726
- url: configuration.url || '',
727
- description: configuration.description || '',
728
- authorization: configuration.authorization,
729
- data: configuration.data || {},
730
- isShared: false,
731
- isReady: true
732
- };
733
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`, 'POST', endpoint);
734
- return {
735
- id: response.id,
736
- name: response.name,
737
- type: response.type,
738
- isReady: response.isReady,
739
- project
740
- };
741
- }
742
- async updateServiceConnection(project, connectionId, updates) {
743
- this.validateProject(project);
744
- if (!this.config.enableServiceConnUpsert) {
745
- throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
746
- }
747
- const current = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
748
- const updated = {
749
- ...current,
750
- name: updates.name || current.name,
751
- description: updates.description || current.description,
752
- url: updates.url || current.url,
753
- data: updates.data ? { ...current.data, ...updates.data } : current.data
754
- };
755
- const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PUT', updated);
756
- return {
757
- id: response.id,
758
- name: response.name,
759
- type: response.type,
760
- isReady: response.isReady,
761
- project
762
- };
763
- }
764
- async shareServiceConnection(connectionId, projectIds) {
765
- if (!this.config.enableServiceConnUpsert) {
766
- throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
767
- }
768
- const shareRequest = projectIds.map(projectId => ({
769
- projectReference: { id: projectId },
770
- name: ''
771
- }));
772
- const response = await this.makeRequest(`_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PATCH', { serviceEndpointProjectReferences: shareRequest });
773
- return {
774
- id: response.id,
775
- name: response.name,
776
- sharedWith: projectIds
777
- };
778
- }
779
- async deleteServiceConnection(project, connectionId) {
780
- this.validateProject(project);
781
- if (!this.config.enableServiceConnDelete) {
782
- throw new Error('Service connection delete operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_DELETE=true to enable.');
783
- }
784
- await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'DELETE');
785
- return {
786
- connectionId,
787
- project,
788
- deleted: true
789
- };
790
- }
791
- // ═══════════════════════════════════════════════════════════════════════════════
792
- // VARIABLE GROUP OPERATIONS
793
- // ═══════════════════════════════════════════════════════════════════════════════
794
- async getVariableGroups(project) {
795
- this.validateProject(project);
796
- const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups?api-version=${this.apiVersion}`);
797
- return {
798
- project,
799
- totalCount: response.value.length,
800
- variableGroups: response.value.map((group) => ({
801
- id: group.id,
802
- name: group.name,
803
- description: group.description,
804
- type: group.type,
805
- createdBy: group.createdBy?.displayName,
806
- createdOn: group.createdOn,
807
- modifiedBy: group.modifiedBy?.displayName,
808
- modifiedOn: group.modifiedOn,
809
- isShared: group.isShared,
810
- variables: group.variables ? Object.keys(group.variables).reduce((acc, key) => {
811
- const v = group.variables[key];
812
- acc[key] = {
813
- value: v.isSecret ? '***SECRET***' : v.value,
814
- isSecret: v.isSecret || false,
815
- isReadOnly: v.isReadOnly || false
816
- };
817
- return acc;
818
- }, {}) : {}
819
- }))
820
- };
821
- }
822
- async getVariableGroup(project, groupId) {
823
- this.validateProject(project);
824
- const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
825
- return {
826
- id: response.id,
827
- name: response.name,
828
- description: response.description,
829
- type: response.type,
830
- createdBy: response.createdBy?.displayName,
831
- createdOn: response.createdOn,
832
- modifiedBy: response.modifiedBy?.displayName,
833
- modifiedOn: response.modifiedOn,
834
- isShared: response.isShared,
835
- project,
836
- variables: response.variables ? Object.keys(response.variables).reduce((acc, key) => {
837
- const v = response.variables[key];
838
- acc[key] = {
839
- value: v.isSecret ? '***SECRET***' : v.value,
840
- isSecret: v.isSecret || false,
841
- isReadOnly: v.isReadOnly || false
842
- };
843
- return acc;
844
- }, {}) : {},
845
- variableGroupProjectReferences: response.variableGroupProjectReferences
846
- };
847
- }
848
- async createVariableGroup(project, name, description, variables) {
849
- this.validateProject(project);
850
- if (!this.config.enableVariableGroupUpsert) {
851
- throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
852
- }
853
- const variableGroup = {
854
- name,
855
- description: description || '',
856
- type: 'Vsts',
857
- variables: variables || {},
858
- variableGroupProjectReferences: [{
859
- projectReference: { name: project },
860
- name: name
861
- }]
862
- };
863
- const response = await this.makeRequest(`_apis/distributedtask/variablegroups?api-version=${this.apiVersion}`, 'POST', variableGroup);
864
- return {
865
- id: response.id,
866
- name: response.name,
867
- project,
868
- variableCount: Object.keys(response.variables || {}).length
869
- };
870
- }
871
- async updateVariableGroupMetadata(project, groupId, updates) {
872
- this.validateProject(project);
873
- if (!this.config.enableVariableGroupUpsert) {
874
- throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
875
- }
876
- const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
877
- const updated = {
878
- ...current,
879
- name: updates.name || current.name,
880
- description: updates.description || current.description
881
- };
882
- const response = await this.makeRequest(`_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', updated);
883
- return {
884
- id: response.id,
885
- name: response.name,
886
- description: response.description,
887
- project
888
- };
889
- }
890
- async setVariable(project, groupId, variableName, value, isSecret = false) {
891
- this.validateProject(project);
892
- if (!this.config.enableVariableGroupUpsert) {
893
- throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
894
- }
895
- const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
896
- current.variables[variableName] = { value, isSecret };
897
- const response = await this.makeRequest(`_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
898
- return {
899
- id: response.id,
900
- name: response.name,
901
- project,
902
- variableSet: variableName,
903
- isSecret
904
- };
905
- }
906
- async removeVariable(project, groupId, variableName) {
907
- this.validateProject(project);
908
- if (!this.config.enableVariableGroupDelete) {
909
- throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
910
- }
911
- const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
912
- if (current.variables[variableName]) {
913
- delete current.variables[variableName];
914
- }
915
- else {
916
- throw new Error(`Variable '${variableName}' not found in group ${groupId}`);
917
- }
918
- const response = await this.makeRequest(`_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
919
- return {
920
- id: response.id,
921
- name: response.name,
922
- project,
923
- variableRemoved: variableName
924
- };
925
- }
926
- async deleteVariableGroup(project, groupId) {
927
- this.validateProject(project);
928
- if (!this.config.enableVariableGroupDelete) {
929
- throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
930
- }
931
- await this.makeRequest(`_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'DELETE');
932
- return {
933
- groupId,
934
- project,
935
- deleted: true
936
- };
937
- }
938
- // ═══════════════════════════════════════════════════════════════════════════════
939
- // AGENT POOL OPERATIONS
940
- // ═══════════════════════════════════════════════════════════════════════════════
941
- async listAgentPools(poolType) {
942
- let url = `_apis/distributedtask/pools?api-version=${this.apiVersion}`;
943
- if (poolType) {
944
- url += `&poolType=${poolType}`;
945
- }
946
- const response = await this.makeRequest(url);
947
- return {
948
- totalCount: response.value.length,
949
- pools: response.value.map((pool) => ({
950
- id: pool.id,
951
- name: pool.name,
952
- size: pool.size,
953
- isHosted: pool.isHosted,
954
- poolType: pool.poolType,
955
- createdBy: pool.createdBy?.displayName,
956
- createdOn: pool.createdOn,
957
- autoProvision: pool.autoProvision,
958
- autoUpdate: pool.autoUpdate,
959
- autoSize: pool.autoSize,
960
- targetSize: pool.targetSize
961
- }))
962
- };
963
- }
964
- async getAgentPool(poolId) {
965
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`);
966
- return {
967
- id: response.id,
968
- name: response.name,
969
- size: response.size,
970
- isHosted: response.isHosted,
971
- poolType: response.poolType,
972
- createdBy: response.createdBy?.displayName,
973
- createdOn: response.createdOn,
974
- autoProvision: response.autoProvision,
975
- autoUpdate: response.autoUpdate,
976
- autoSize: response.autoSize,
977
- targetSize: response.targetSize,
978
- owner: response.owner?.displayName,
979
- agentCloudId: response.agentCloudId,
980
- properties: response.properties
981
- };
982
- }
983
- async listAgents(poolId, includeCapabilities = false) {
984
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents?includeCapabilities=${includeCapabilities}&api-version=${this.apiVersion}`);
985
- return {
986
- poolId,
987
- totalCount: response.value.length,
988
- agents: response.value.map((agent) => ({
989
- id: agent.id,
990
- name: agent.name,
991
- version: agent.version,
992
- osDescription: agent.osDescription,
993
- enabled: agent.enabled,
994
- status: agent.status,
995
- provisioningState: agent.provisioningState,
996
- createdOn: agent.createdOn,
997
- maxParallelism: agent.maxParallelism,
998
- systemCapabilities: includeCapabilities ? agent.systemCapabilities : undefined,
999
- userCapabilities: includeCapabilities ? agent.userCapabilities : undefined
1000
- }))
1001
- };
1002
- }
1003
- async getAgent(poolId, agentId) {
1004
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?includeCapabilities=true&api-version=${this.apiVersion}`);
1005
- return {
1006
- id: response.id,
1007
- name: response.name,
1008
- version: response.version,
1009
- osDescription: response.osDescription,
1010
- enabled: response.enabled,
1011
- status: response.status,
1012
- provisioningState: response.provisioningState,
1013
- accessPoint: response.accessPoint,
1014
- createdOn: response.createdOn,
1015
- maxParallelism: response.maxParallelism,
1016
- systemCapabilities: response.systemCapabilities,
1017
- userCapabilities: response.userCapabilities,
1018
- assignedRequest: response.assignedRequest,
1019
- lastCompletedRequest: response.lastCompletedRequest
1020
- };
1021
- }
1022
- async updateAgentPool(poolId, updates) {
1023
- if (!this.config.enableAgentPoolUpsert) {
1024
- throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
1025
- }
1026
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`, 'PATCH', updates);
1027
- return {
1028
- id: response.id,
1029
- name: response.name,
1030
- autoProvision: response.autoProvision,
1031
- autoUpdate: response.autoUpdate,
1032
- autoSize: response.autoSize,
1033
- targetSize: response.targetSize
1034
- };
1035
- }
1036
- async enableAgent(poolId, agentId) {
1037
- if (!this.config.enableAgentPoolUpsert) {
1038
- throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
1039
- }
1040
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: true });
1041
- return {
1042
- id: response.id,
1043
- name: response.name,
1044
- enabled: response.enabled,
1045
- status: response.status
1046
- };
1047
- }
1048
- async disableAgent(poolId, agentId) {
1049
- if (!this.config.enableAgentPoolDisable) {
1050
- throw new Error('Agent pool disable operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_DISABLE=true to enable.');
1051
- }
1052
- const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: false });
1053
- return {
1054
- id: response.id,
1055
- name: response.name,
1056
- enabled: response.enabled,
1057
- status: response.status,
1058
- message: 'Agent disabled - will complete current job then stop accepting new jobs'
1059
- };
1060
- }
1061
- // ═══════════════════════════════════════════════════════════════════════════════
1062
- // ENVIRONMENT OPERATIONS
1063
- // ═══════════════════════════════════════════════════════════════════════════════
1064
- async listEnvironments(project) {
1065
- this.validateProject(project);
1066
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`);
1067
- return {
1068
- project,
1069
- totalCount: response.value.length,
1070
- environments: response.value.map((env) => ({
1071
- id: env.id,
1072
- name: env.name,
1073
- description: env.description,
1074
- createdBy: env.createdBy?.displayName,
1075
- createdOn: env.createdOn,
1076
- lastModifiedBy: env.lastModifiedBy?.displayName,
1077
- lastModifiedOn: env.lastModifiedOn
1078
- }))
1079
- };
1080
- }
1081
- async getEnvironment(project, environmentId) {
1082
- this.validateProject(project);
1083
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?expands=resourceReferences&api-version=${this.apiVersion}`);
1084
- return {
1085
- id: response.id,
1086
- name: response.name,
1087
- description: response.description,
1088
- createdBy: response.createdBy?.displayName,
1089
- createdOn: response.createdOn,
1090
- lastModifiedBy: response.lastModifiedBy?.displayName,
1091
- lastModifiedOn: response.lastModifiedOn,
1092
- project,
1093
- resources: response.resources || []
1094
- };
1095
- }
1096
- async getEnvironmentDeployments(project, environmentId, top = 10) {
1097
- this.validateProject(project);
1098
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}/environmentdeploymentrecords?top=${top}&api-version=${this.apiVersion}`);
1099
- return {
1100
- project,
1101
- environmentId,
1102
- totalCount: response.value.length,
1103
- deployments: response.value.map((dep) => ({
1104
- id: dep.id,
1105
- environmentId: dep.environmentId,
1106
- definition: dep.definition ? {
1107
- id: dep.definition.id,
1108
- name: dep.definition.name
1109
- } : null,
1110
- owner: dep.owner?.displayName,
1111
- planType: dep.planType,
1112
- startTime: dep.startTime,
1113
- finishTime: dep.finishTime,
1114
- result: dep.result,
1115
- queueTime: dep.queueTime
1116
- }))
1117
- };
1118
- }
1119
- async createEnvironment(project, name, description) {
1120
- this.validateProject(project);
1121
- if (!this.config.enableEnvironmentUpsert) {
1122
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1123
- }
1124
- const environment = {
1125
- name,
1126
- description: description || ''
1127
- };
1128
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`, 'POST', environment);
1129
- return {
1130
- id: response.id,
1131
- name: response.name,
1132
- description: response.description,
1133
- project
1134
- };
1135
- }
1136
- async updateEnvironment(project, environmentId, updates) {
1137
- this.validateProject(project);
1138
- if (!this.config.enableEnvironmentUpsert) {
1139
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1140
- }
1141
- const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'PATCH', updates);
1142
- return {
1143
- id: response.id,
1144
- name: response.name,
1145
- description: response.description,
1146
- project
1147
- };
1148
- }
1149
- async deleteEnvironment(project, environmentId) {
1150
- this.validateProject(project);
1151
- if (!this.config.enableEnvironmentDelete) {
1152
- throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
1153
- }
1154
- await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'DELETE');
1155
- return {
1156
- environmentId,
1157
- project,
1158
- deleted: true
1159
- };
1160
- }
1161
- async getEnvironmentChecks(project, environmentId) {
1162
- this.validateProject(project);
1163
- const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?resourceType=environment&resourceId=${environmentId}&api-version=7.1-preview.1`);
1164
- return {
1165
- project,
1166
- environmentId,
1167
- totalCount: response.value.length,
1168
- checks: response.value.map((check) => ({
1169
- id: check.id,
1170
- type: check.type?.name || check.type?.id,
1171
- settings: check.settings,
1172
- timeout: check.timeout,
1173
- retryInterval: check.retryInterval,
1174
- createdBy: check.createdBy?.displayName,
1175
- createdOn: check.createdOn,
1176
- modifiedBy: check.modifiedBy?.displayName,
1177
- modifiedOn: check.modifiedOn,
1178
- resource: check.resource
1179
- }))
1180
- };
1181
- }
1182
- async addEnvironmentCheck(project, environmentId, checkType, configuration) {
1183
- this.validateProject(project);
1184
- if (!this.config.enableEnvironmentUpsert) {
1185
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1186
- }
1187
- const check = {
1188
- type: { name: checkType },
1189
- settings: configuration,
1190
- resource: {
1191
- type: 'environment',
1192
- id: String(environmentId)
1193
- }
1194
- };
1195
- const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?api-version=7.1-preview.1`, 'POST', check);
1196
- return {
1197
- id: response.id,
1198
- type: response.type?.name,
1199
- environmentId,
1200
- project
1201
- };
1202
- }
1203
- async updateEnvironmentCheck(project, checkId, updates) {
1204
- this.validateProject(project);
1205
- if (!this.config.enableEnvironmentUpsert) {
1206
- throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1207
- }
1208
- const current = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`);
1209
- const updated = {
1210
- ...current,
1211
- settings: updates.settings !== undefined ? updates.settings : current.settings,
1212
- timeout: updates.timeout !== undefined ? updates.timeout : current.timeout
1213
- };
1214
- const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'PATCH', updated);
1215
- return {
1216
- id: response.id,
1217
- type: response.type?.name,
1218
- settings: response.settings,
1219
- timeout: response.timeout,
1220
- project
1221
- };
1222
- }
1223
- async removeEnvironmentCheck(project, checkId) {
1224
- this.validateProject(project);
1225
- if (!this.config.enableEnvironmentDelete) {
1226
- throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
1227
- }
1228
- await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'DELETE');
1229
- return {
1230
- checkId,
1231
- project,
1232
- deleted: true
1233
- };
1234
- }
1235
- // ═══════════════════════════════════════════════════════════════════════════════
1236
- // CLASSIFICATION NODE OPERATIONS (Iterations & Areas)
1237
- // ═══════════════════════════════════════════════════════════════════════════════
1238
- /**
1239
- * Flatten classification node hierarchy into a list
1240
- */
1241
- flattenClassificationNodes(node, parentPath = '') {
1242
- const result = [];
1243
- const currentPath = parentPath ? `${parentPath}\\${node.name}` : node.name;
1244
- result.push({
1245
- id: node.id,
1246
- identifier: node.identifier,
1247
- name: node.name,
1248
- path: currentPath,
1249
- structureType: node.structureType,
1250
- hasChildren: node.hasChildren || false,
1251
- attributes: node.attributes || {},
1252
- url: node.url
1253
- });
1254
- if (node.children && Array.isArray(node.children)) {
1255
- for (const child of node.children) {
1256
- result.push(...this.flattenClassificationNodes(child, currentPath));
1257
- }
1258
- }
1259
- return result;
1260
- }
1261
- async listClassificationNodes(project, structureType, depth = 10) {
1262
- this.validateProject(project);
1263
- const response = await this.makeRequest(`${project}/_apis/wit/classificationnodes/${structureType}?$depth=${depth}&api-version=${this.apiVersion}`);
1264
- const nodes = this.flattenClassificationNodes(response);
1265
- return {
1266
- project,
1267
- structureType,
1268
- totalCount: nodes.length,
1269
- nodes: nodes.map((node) => ({
1270
- id: node.id,
1271
- identifier: node.identifier,
1272
- name: node.name,
1273
- path: node.path,
1274
- hasChildren: node.hasChildren,
1275
- ...(structureType === 'iterations' && node.attributes ? {
1276
- startDate: node.attributes.startDate,
1277
- finishDate: node.attributes.finishDate,
1278
- timeFrame: node.attributes.timeFrame
1279
- } : {})
1280
- }))
1281
- };
1282
- }
1283
- async getClassificationNode(project, structureType, path) {
1284
- this.validateProject(project);
1285
- // Path should be URL encoded and without leading backslash
1286
- const cleanPath = path.replace(/^\\/, '').replace(/\\/g, '/');
1287
- const encodedPath = encodeURIComponent(cleanPath).replace(/%2F/g, '/');
1288
- const response = await this.makeRequest(`${project}/_apis/wit/classificationnodes/${structureType}/${encodedPath}?api-version=${this.apiVersion}`);
1289
- return {
1290
- id: response.id,
1291
- identifier: response.identifier,
1292
- name: response.name,
1293
- structureType: response.structureType,
1294
- hasChildren: response.hasChildren || false,
1295
- path: response.path,
1296
- project,
1297
- ...(structureType === 'iterations' && response.attributes ? {
1298
- startDate: response.attributes.startDate,
1299
- finishDate: response.attributes.finishDate,
1300
- timeFrame: response.attributes.timeFrame
1301
- } : {}),
1302
- url: response.url
1303
- };
1304
- }
1305
- async createClassificationNode(project, structureType, name, parentPath, attributes) {
1306
- this.validateProject(project);
1307
- if (!this.config.enableClassificationNodeUpsert) {
1308
- throw new Error('Classification node upsert operations are disabled. Set AZUREDEVOPS_ENABLE_CLASSIFICATION_NODE_UPSERT=true to enable.');
1309
- }
1310
- const body = { name };
1311
- if (structureType === 'iterations' && attributes) {
1312
- body.attributes = {};
1313
- // Azure DevOps requires ISO 8601 format with time component
1314
- if (attributes.startDate)
1315
- body.attributes.startDate = this.formatDateForAdo(attributes.startDate);
1316
- if (attributes.finishDate)
1317
- body.attributes.finishDate = this.formatDateForAdo(attributes.finishDate);
1318
- }
1319
- // Build the endpoint - if parentPath is provided, create under that path
1320
- let endpoint = `${project}/_apis/wit/classificationnodes/${structureType}`;
1321
- if (parentPath) {
1322
- const cleanPath = parentPath.replace(/^\\/, '').replace(/\\/g, '/');
1323
- const encodedPath = encodeURIComponent(cleanPath).replace(/%2F/g, '/');
1324
- endpoint = `${project}/_apis/wit/classificationnodes/${structureType}/${encodedPath}`;
1325
- }
1326
- const response = await this.makeRequest(`${endpoint}?api-version=${this.apiVersion}`, 'POST', body);
1327
- return {
1328
- id: response.id,
1329
- identifier: response.identifier,
1330
- name: response.name,
1331
- path: response.path,
1332
- structureType,
1333
- project,
1334
- ...(structureType === 'iterations' && response.attributes ? {
1335
- startDate: response.attributes.startDate,
1336
- finishDate: response.attributes.finishDate
1337
- } : {}),
1338
- url: response.url
1339
- };
1340
- }
1341
- async addIterationToTeam(project, team, iterationId) {
1342
- this.validateProject(project);
1343
- if (!this.config.enableClassificationNodeUpsert) {
1344
- throw new Error('Classification node upsert operations are disabled. Set AZUREDEVOPS_ENABLE_CLASSIFICATION_NODE_UPSERT=true to enable.');
1345
- }
1346
- const encodedTeam = encodeURIComponent(team);
1347
- const endpoint = `${project}/${encodedTeam}/_apis/work/teamsettings/iterations?api-version=${this.apiVersion}`;
1348
- const response = await this.makeRequest(endpoint, 'POST', { id: iterationId });
1349
- return {
1350
- id: response.id,
1351
- name: response.name,
1352
- path: response.path,
1353
- url: response.url,
1354
- attributes: response.attributes ? {
1355
- startDate: response.attributes.startDate,
1356
- finishDate: response.attributes.finishDate,
1357
- timeFrame: response.attributes.timeFrame,
1358
- } : undefined,
1359
- };
1360
- }
1361
- async updateClassificationNode(project, structureType, path, updates) {
1362
- this.validateProject(project);
1363
- if (!this.config.enableClassificationNodeUpsert) {
1364
- throw new Error('Classification node upsert operations are disabled. Set AZUREDEVOPS_ENABLE_CLASSIFICATION_NODE_UPSERT=true to enable.');
1365
- }
1366
- const cleanPath = path.replace(/^\\/, '').replace(/\\/g, '/');
1367
- const encodedPath = encodeURIComponent(cleanPath).replace(/%2F/g, '/');
1368
- const body = {};
1369
- if (updates.name)
1370
- body.name = updates.name;
1371
- if (structureType === 'iterations') {
1372
- if (updates.startDate !== undefined || updates.finishDate !== undefined) {
1373
- body.attributes = {};
1374
- // Azure DevOps requires ISO 8601 format with time component
1375
- if (updates.startDate !== undefined)
1376
- body.attributes.startDate = this.formatDateForAdo(updates.startDate);
1377
- if (updates.finishDate !== undefined)
1378
- body.attributes.finishDate = this.formatDateForAdo(updates.finishDate);
1379
- }
1380
- }
1381
- // Classification Nodes API uses regular JSON, not JSON Patch format
1382
- const response = await this.makeRequest(`${project}/_apis/wit/classificationnodes/${structureType}/${encodedPath}?api-version=${this.apiVersion}`, 'PATCH', body, undefined, 'application/json' // Override default json-patch+json content type
1383
- );
1384
- return {
1385
- id: response.id,
1386
- identifier: response.identifier,
1387
- name: response.name,
1388
- path: response.path,
1389
- structureType,
1390
- project,
1391
- ...(structureType === 'iterations' && response.attributes ? {
1392
- startDate: response.attributes.startDate,
1393
- finishDate: response.attributes.finishDate
1394
- } : {}),
1395
- url: response.url
1396
- };
1397
- }
1398
- async deleteClassificationNode(project, structureType, path, reclassifyId) {
1399
- this.validateProject(project);
1400
- if (!this.config.enableClassificationNodeDelete) {
1401
- throw new Error('Classification node delete operations are disabled. Set AZUREDEVOPS_ENABLE_CLASSIFICATION_NODE_DELETE=true to enable.');
1402
- }
1403
- const cleanPath = path.replace(/^\\/, '').replace(/\\/g, '/');
1404
- const encodedPath = encodeURIComponent(cleanPath).replace(/%2F/g, '/');
1405
- await this.makeRequest(`${project}/_apis/wit/classificationnodes/${structureType}/${encodedPath}?$reclassifyId=${reclassifyId}&api-version=${this.apiVersion}`, 'DELETE');
1406
- return {
1407
- path,
1408
- structureType,
1409
- project,
1410
- reclassifyId,
1411
- deleted: true
1412
- };
1413
- }
1414
- // ========================================
1415
- // ARTIFACT FEED METHODS
1416
- // ========================================
1417
- async listFeedPackages(feedName, options) {
1418
- this.validateFeed(feedName);
1419
- if (options?.project) {
1420
- this.validateProject(options.project);
1421
- }
1422
- const projectPrefix = options?.project ? `${options.project}/` : '';
1423
- const queryParams = new URLSearchParams();
1424
- queryParams.append('api-version', this.apiVersion);
1425
- if (options?.namePrefix)
1426
- queryParams.append('packageNameQuery', options.namePrefix);
1427
- if (options?.packageType)
1428
- queryParams.append('protocolType', options.packageType);
1429
- if (options?.top)
1430
- queryParams.append('$top', String(options.top));
1431
- const endpoint = `${projectPrefix}_apis/packaging/Feeds/${encodeURIComponent(feedName)}/packages?${queryParams.toString()}`;
1432
- const response = await this.makeRequest(endpoint, 'GET', undefined, undefined, undefined, this.feedsUrl);
1433
- return {
1434
- feed: feedName,
1435
- project: options?.project || '(org-scoped)',
1436
- totalCount: response.value.length,
1437
- packages: response.value.map((pkg) => ({
1438
- id: pkg.id,
1439
- name: pkg.name,
1440
- protocolType: pkg.protocolType,
1441
- latestVersion: pkg.versions?.[0]?.version,
1442
- publishDate: pkg.versions?.[0]?.publishDate,
1443
- description: pkg.description,
1444
- })),
1445
- };
1446
- }
1447
- async getPackageVersions(feedName, packageName, options) {
1448
- this.validateFeed(feedName);
1449
- if (options?.project) {
1450
- this.validateProject(options.project);
1451
- }
1452
- // Step 1: Resolve package name → package ID
1453
- const projectPrefix = options?.project ? `${options.project}/` : '';
1454
- const listParams = new URLSearchParams();
1455
- listParams.append('api-version', this.apiVersion);
1456
- listParams.append('packageNameQuery', packageName);
1457
- if (options?.packageType)
1458
- listParams.append('protocolType', options.packageType);
1459
- const listEndpoint = `${projectPrefix}_apis/packaging/Feeds/${encodeURIComponent(feedName)}/packages?${listParams.toString()}`;
1460
- const listResponse = await this.makeRequest(listEndpoint, 'GET', undefined, undefined, undefined, this.feedsUrl);
1461
- const exactMatch = listResponse.value.find((pkg) => pkg.name.toLowerCase() === packageName.toLowerCase());
1462
- if (!exactMatch) {
1463
- const available = listResponse.value.map((pkg) => pkg.name).slice(0, 10);
1464
- throw new Error(`Package '${packageName}' not found in feed '${feedName}'. ` +
1465
- (available.length > 0
1466
- ? `Similar packages: ${available.join(', ')}`
1467
- : 'No matching packages found.'));
1468
- }
1469
- // Step 2: Get versions for the resolved package ID
1470
- const versionParams = new URLSearchParams();
1471
- versionParams.append('api-version', this.apiVersion);
1472
- if (options?.top)
1473
- versionParams.append('$top', String(options.top));
1474
- if (options?.includeDelisted)
1475
- versionParams.append('includeDelisted', 'true');
1476
- const versionEndpoint = `${projectPrefix}_apis/packaging/Feeds/${encodeURIComponent(feedName)}/packages/${exactMatch.id}/versions?${versionParams.toString()}`;
1477
- const versionResponse = await this.makeRequest(versionEndpoint, 'GET', undefined, undefined, undefined, this.feedsUrl);
1478
- return {
1479
- feed: feedName,
1480
- project: options?.project || '(org-scoped)',
1481
- packageName: exactMatch.name,
1482
- packageId: exactMatch.id,
1483
- protocolType: exactMatch.protocolType,
1484
- versions: versionResponse.value.map((v) => ({
1485
- version: v.version,
1486
- publishDate: v.publishDate,
1487
- isLatest: v.isLatest ?? false,
1488
- isListed: v.isListed ?? true,
1489
- description: v.description,
1490
- })),
1491
- };
1492
- }
1493
- }
1494
- //# sourceMappingURL=AzureDevOpsAdminService.js.map