@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.
- package/build/cli.js +0 -0
- package/build/index.js +0 -0
- package/build/tools/agent-pool-tools.d.ts.map +1 -1
- package/build/tools/agent-pool-tools.js +9 -7
- package/build/tools/agent-pool-tools.js.map +1 -1
- package/build/tools/artifact-feed-tools.d.ts.map +1 -1
- package/build/tools/artifact-feed-tools.js +2 -2
- package/build/tools/artifact-feed-tools.js.map +1 -1
- package/build/tools/classification-tools.d.ts.map +1 -1
- package/build/tools/classification-tools.js +11 -11
- package/build/tools/classification-tools.js.map +1 -1
- package/build/tools/environment-tools.d.ts.map +1 -1
- package/build/tools/environment-tools.js +10 -10
- package/build/tools/environment-tools.js.map +1 -1
- package/build/tools/pipeline-tools.d.ts.map +1 -1
- package/build/tools/pipeline-tools.js +18 -16
- package/build/tools/pipeline-tools.js.map +1 -1
- package/build/tools/project-tools.d.ts.map +1 -1
- package/build/tools/project-tools.js +6 -6
- package/build/tools/project-tools.js.map +1 -1
- package/build/tools/service-connection-tools.d.ts.map +1 -1
- package/build/tools/service-connection-tools.js +7 -7
- package/build/tools/service-connection-tools.js.map +1 -1
- package/build/tools/variable-group-tools.d.ts.map +1 -1
- package/build/tools/variable-group-tools.js +7 -7
- package/build/tools/variable-group-tools.js.map +1 -1
- package/package.json +1 -1
- package/build/AzureDevOpsAdminService.d.ts +0 -414
- package/build/AzureDevOpsAdminService.d.ts.map +0 -1
- package/build/AzureDevOpsAdminService.js +0 -1494
- 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
|