@mcp-consultant-tools/azure-devops 24.0.0 → 25.0.0-beta.4

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.
@@ -11,7 +11,21 @@ export class AzureDevOpsService {
11
11
  apiVersion: config.apiVersion || '7.1',
12
12
  enableWorkItemWrite: config.enableWorkItemWrite ?? false,
13
13
  enableWorkItemDelete: config.enableWorkItemDelete ?? false,
14
- enableWikiWrite: config.enableWikiWrite ?? false
14
+ enableWikiWrite: config.enableWikiWrite ?? false,
15
+ // Tier 1: Read-only (master switch)
16
+ showDevOpsAdminReadonly: config.showDevOpsAdminReadonly ?? false,
17
+ // Tier 2: Upsert (create + update)
18
+ enablePipelineUpsert: config.enablePipelineUpsert ?? false,
19
+ enableServiceConnUpsert: config.enableServiceConnUpsert ?? false,
20
+ enableVariableGroupUpsert: config.enableVariableGroupUpsert ?? false,
21
+ enableAgentPoolUpsert: config.enableAgentPoolUpsert ?? false,
22
+ enableEnvironmentUpsert: config.enableEnvironmentUpsert ?? false,
23
+ // Tier 3: Delete/Disable (destructive operations)
24
+ enablePipelineDelete: config.enablePipelineDelete ?? false,
25
+ enableServiceConnDelete: config.enableServiceConnDelete ?? false,
26
+ enableVariableGroupDelete: config.enableVariableGroupDelete ?? false,
27
+ enableAgentPoolDisable: config.enableAgentPoolDisable ?? false,
28
+ enableEnvironmentDelete: config.enableEnvironmentDelete ?? false,
15
29
  };
16
30
  this.baseUrl = `https://dev.azure.com/${this.config.organization}`;
17
31
  this.searchUrl = `https://almsearch.dev.azure.com/${this.config.organization}`;
@@ -512,11 +526,13 @@ export class AzureDevOpsService {
512
526
  this.validateProject(project);
513
527
  // Comments API requires -preview suffix (not GA in 7.1)
514
528
  const response = await this.makeRequest(`${project}/_apis/wit/workItems/${workItemId}/comments?api-version=7.1-preview`);
529
+ // Handle case where response.value may be undefined or empty
530
+ const comments = response.value || [];
515
531
  return {
516
532
  workItemId,
517
533
  project,
518
- totalCount: response.totalCount || response.value.length,
519
- comments: response.value.map((comment) => ({
534
+ totalCount: response.totalCount ?? comments.length,
535
+ comments: comments.map((comment) => ({
520
536
  id: comment.id,
521
537
  text: comment.text,
522
538
  createdBy: comment.createdBy?.displayName,
@@ -713,5 +729,1181 @@ export class AzureDevOpsService {
713
729
  }, {})
714
730
  };
715
731
  }
732
+ // ═══════════════════════════════════════════════════════════════════════════════
733
+ // PIPELINE/BUILD OPERATIONS (DevOps Admin Tools)
734
+ // ═══════════════════════════════════════════════════════════════════════════════
735
+ /**
736
+ * List all pipeline definitions in a project
737
+ * @param project The project name
738
+ * @returns List of pipeline definitions
739
+ */
740
+ async listPipelineDefinitions(project) {
741
+ this.validateProject(project);
742
+ const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`);
743
+ return {
744
+ project,
745
+ totalCount: response.value.length,
746
+ pipelines: response.value.map((def) => ({
747
+ id: def.id,
748
+ name: def.name,
749
+ path: def.path,
750
+ revision: def.revision,
751
+ type: def.type,
752
+ queueStatus: def.queueStatus,
753
+ createdDate: def.createdDate,
754
+ authoredBy: def.authoredBy?.displayName,
755
+ repository: def.repository ? {
756
+ id: def.repository.id,
757
+ name: def.repository.name,
758
+ type: def.repository.type
759
+ } : null,
760
+ process: def.process ? {
761
+ type: def.process.type,
762
+ yamlFilename: def.process.yamlFilename
763
+ } : null,
764
+ url: def._links?.web?.href
765
+ }))
766
+ };
767
+ }
768
+ /**
769
+ * Get a specific pipeline definition with full details
770
+ * @param project The project name
771
+ * @param definitionId The pipeline definition ID
772
+ * @returns Full pipeline definition
773
+ */
774
+ async getPipelineDefinition(project, definitionId) {
775
+ this.validateProject(project);
776
+ const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
777
+ return {
778
+ id: response.id,
779
+ name: response.name,
780
+ path: response.path,
781
+ revision: response.revision,
782
+ type: response.type,
783
+ queueStatus: response.queueStatus,
784
+ quality: response.quality,
785
+ createdDate: response.createdDate,
786
+ authoredBy: response.authoredBy?.displayName,
787
+ project: response.project?.name,
788
+ repository: response.repository ? {
789
+ id: response.repository.id,
790
+ name: response.repository.name,
791
+ type: response.repository.type,
792
+ defaultBranch: response.repository.defaultBranch,
793
+ url: response.repository.url
794
+ } : null,
795
+ process: response.process ? {
796
+ type: response.process.type,
797
+ yamlFilename: response.process.yamlFilename
798
+ } : null,
799
+ triggers: response.triggers || [],
800
+ variables: response.variables ? Object.keys(response.variables).reduce((acc, key) => {
801
+ const variable = response.variables[key];
802
+ acc[key] = {
803
+ value: variable.isSecret ? '***SECRET***' : variable.value,
804
+ isSecret: variable.isSecret || false,
805
+ allowOverride: variable.allowOverride || false
806
+ };
807
+ return acc;
808
+ }, {}) : {},
809
+ queue: response.queue ? {
810
+ id: response.queue.id,
811
+ name: response.queue.name,
812
+ pool: response.queue.pool?.name
813
+ } : null,
814
+ url: response._links?.web?.href
815
+ };
816
+ }
817
+ /**
818
+ * Get the YAML content for a pipeline definition
819
+ * @param project The project name
820
+ * @param definitionId The pipeline definition ID
821
+ * @returns YAML content or location info for external repos
822
+ */
823
+ async getPipelineYaml(project, definitionId) {
824
+ this.validateProject(project);
825
+ // First get the pipeline definition to check repo type
826
+ const definition = await this.getPipelineDefinition(project, definitionId);
827
+ // If external repo (GitHub, Bitbucket, etc.), return helpful info instead of failing
828
+ if (definition.repository?.type && definition.repository.type !== 'TfsGit') {
829
+ return {
830
+ definitionId,
831
+ project,
832
+ pipelineName: definition.name,
833
+ yamlLocation: 'external',
834
+ repositoryType: definition.repository.type,
835
+ repositoryName: definition.repository.name,
836
+ repositoryUrl: definition.repository.url,
837
+ defaultBranch: definition.repository.defaultBranch,
838
+ yamlFilename: definition.process?.yamlFilename,
839
+ message: `YAML is stored in external ${definition.repository.type} repository. ` +
840
+ `To view the YAML content, access the file "${definition.process?.yamlFilename}" ` +
841
+ `at ${definition.repository.url}`
842
+ };
843
+ }
844
+ // For Azure Repos (TfsGit), fetch YAML content directly
845
+ const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}/yaml?api-version=${this.apiVersion}`);
846
+ return {
847
+ definitionId,
848
+ project,
849
+ pipelineName: definition.name,
850
+ yamlLocation: 'azureRepos',
851
+ yaml: response.yaml || response
852
+ };
853
+ }
854
+ /**
855
+ * List recent pipeline runs for a definition
856
+ * @param project The project name
857
+ * @param definitionId The pipeline definition ID
858
+ * @param top Maximum number of results (default: 10)
859
+ * @returns List of builds/runs
860
+ */
861
+ async listPipelineRuns(project, definitionId, top = 10) {
862
+ this.validateProject(project);
863
+ const response = await this.makeRequest(`${project}/_apis/build/builds?definitions=${definitionId}&$top=${top}&api-version=${this.apiVersion}`);
864
+ return {
865
+ project,
866
+ definitionId,
867
+ totalCount: response.value.length,
868
+ builds: response.value.map((build) => ({
869
+ id: build.id,
870
+ buildNumber: build.buildNumber,
871
+ status: build.status,
872
+ result: build.result,
873
+ queueTime: build.queueTime,
874
+ startTime: build.startTime,
875
+ finishTime: build.finishTime,
876
+ sourceBranch: build.sourceBranch,
877
+ sourceVersion: build.sourceVersion,
878
+ requestedBy: build.requestedBy?.displayName,
879
+ requestedFor: build.requestedFor?.displayName,
880
+ reason: build.reason,
881
+ priority: build.priority,
882
+ url: build._links?.web?.href
883
+ }))
884
+ };
885
+ }
886
+ /**
887
+ * Get build status and details
888
+ * @param project The project name
889
+ * @param buildId The build ID
890
+ * @param detail Level of detail: "summary" | "timeline" | "full"
891
+ * @returns Build details
892
+ */
893
+ async getBuildStatus(project, buildId, detail = 'summary') {
894
+ this.validateProject(project);
895
+ const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
896
+ const result = {
897
+ id: response.id,
898
+ buildNumber: response.buildNumber,
899
+ status: response.status,
900
+ result: response.result,
901
+ queueTime: response.queueTime,
902
+ startTime: response.startTime,
903
+ finishTime: response.finishTime,
904
+ sourceBranch: response.sourceBranch,
905
+ sourceVersion: response.sourceVersion,
906
+ definition: response.definition ? {
907
+ id: response.definition.id,
908
+ name: response.definition.name
909
+ } : null,
910
+ requestedBy: response.requestedBy?.displayName,
911
+ requestedFor: response.requestedFor?.displayName,
912
+ reason: response.reason,
913
+ priority: response.priority,
914
+ project: response.project?.name,
915
+ url: response._links?.web?.href
916
+ };
917
+ // Add timeline if requested
918
+ if (detail === 'timeline' || detail === 'full') {
919
+ const timeline = await this.getBuildTimeline(project, buildId);
920
+ result.timeline = timeline;
921
+ }
922
+ // Add logs if full detail requested
923
+ if (detail === 'full') {
924
+ const logs = await this.getBuildLogs(project, buildId);
925
+ result.logs = logs;
926
+ }
927
+ return result;
928
+ }
929
+ /**
930
+ * Get build timeline (step-by-step breakdown)
931
+ * @param project The project name
932
+ * @param buildId The build ID
933
+ * @returns Build timeline with records
934
+ */
935
+ async getBuildTimeline(project, buildId) {
936
+ this.validateProject(project);
937
+ const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/timeline?api-version=${this.apiVersion}`);
938
+ return {
939
+ buildId,
940
+ project,
941
+ changeId: response.changeId,
942
+ lastChangedBy: response.lastChangedBy,
943
+ lastChangedOn: response.lastChangedOn,
944
+ records: (response.records || []).map((record) => ({
945
+ id: record.id,
946
+ parentId: record.parentId,
947
+ type: record.type,
948
+ name: record.name,
949
+ state: record.state,
950
+ result: record.result,
951
+ startTime: record.startTime,
952
+ finishTime: record.finishTime,
953
+ order: record.order,
954
+ errorCount: record.errorCount,
955
+ warningCount: record.warningCount,
956
+ log: record.log ? { id: record.log.id, url: record.log.url } : null,
957
+ issues: record.issues || []
958
+ }))
959
+ };
960
+ }
961
+ /**
962
+ * Get build logs
963
+ * @param project The project name
964
+ * @param buildId The build ID
965
+ * @param logId Optional specific log ID
966
+ * @returns Build logs
967
+ */
968
+ async getBuildLogs(project, buildId, logId) {
969
+ this.validateProject(project);
970
+ if (logId !== undefined) {
971
+ // Get specific log content
972
+ const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs/${logId}?api-version=${this.apiVersion}`);
973
+ return {
974
+ buildId,
975
+ logId,
976
+ project,
977
+ content: response
978
+ };
979
+ }
980
+ // Get list of all logs
981
+ const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs?api-version=${this.apiVersion}`);
982
+ return {
983
+ buildId,
984
+ project,
985
+ totalCount: response.value.length,
986
+ logs: response.value.map((log) => ({
987
+ id: log.id,
988
+ type: log.type,
989
+ lineCount: log.lineCount,
990
+ createdOn: log.createdOn,
991
+ lastChangedOn: log.lastChangedOn,
992
+ url: log.url
993
+ }))
994
+ };
995
+ }
996
+ /**
997
+ * Create a new pipeline definition
998
+ * @param project The project name
999
+ * @param name Pipeline name
1000
+ * @param repositoryId Repository ID
1001
+ * @param yamlPath Path to YAML file in repository
1002
+ * @param folder Optional folder path
1003
+ * @returns Created pipeline definition
1004
+ */
1005
+ async createPipelineDefinition(project, name, repositoryId, yamlPath, folder) {
1006
+ this.validateProject(project);
1007
+ if (!this.config.enablePipelineUpsert) {
1008
+ throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1009
+ }
1010
+ const definition = {
1011
+ name,
1012
+ path: folder || '\\',
1013
+ type: 'build',
1014
+ queueStatus: 'enabled',
1015
+ process: {
1016
+ type: 2, // YAML process type
1017
+ yamlFilename: yamlPath
1018
+ },
1019
+ repository: {
1020
+ id: repositoryId,
1021
+ type: 'TfsGit'
1022
+ }
1023
+ };
1024
+ const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`, 'POST', definition);
1025
+ return {
1026
+ id: response.id,
1027
+ name: response.name,
1028
+ path: response.path,
1029
+ revision: response.revision,
1030
+ project,
1031
+ url: response._links?.web?.href
1032
+ };
1033
+ }
1034
+ /**
1035
+ * Update a pipeline definition
1036
+ * @param project The project name
1037
+ * @param definitionId The pipeline definition ID
1038
+ * @param updates Object with updated properties
1039
+ * @returns Updated pipeline definition
1040
+ */
1041
+ async updatePipelineDefinition(project, definitionId, updates) {
1042
+ this.validateProject(project);
1043
+ if (!this.config.enablePipelineUpsert) {
1044
+ throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1045
+ }
1046
+ // Get current definition first
1047
+ const current = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`);
1048
+ // Merge updates
1049
+ const updated = {
1050
+ ...current,
1051
+ ...updates,
1052
+ revision: current.revision // Must include current revision
1053
+ };
1054
+ const response = await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'PUT', updated);
1055
+ return {
1056
+ id: response.id,
1057
+ name: response.name,
1058
+ path: response.path,
1059
+ revision: response.revision,
1060
+ project,
1061
+ url: response._links?.web?.href
1062
+ };
1063
+ }
1064
+ /**
1065
+ * Rename a pipeline definition
1066
+ * @param project The project name
1067
+ * @param definitionId The pipeline definition ID
1068
+ * @param newName The new name
1069
+ * @returns Updated pipeline definition
1070
+ */
1071
+ async renamePipelineDefinition(project, definitionId, newName) {
1072
+ return this.updatePipelineDefinition(project, definitionId, { name: newName });
1073
+ }
1074
+ /**
1075
+ * Queue a new build
1076
+ * @param project The project name
1077
+ * @param definitionId The pipeline definition ID
1078
+ * @param branch Optional source branch
1079
+ * @param variables Optional variables to pass
1080
+ * @param parameters Optional pipeline parameters
1081
+ * @returns Queued build
1082
+ */
1083
+ async queueBuild(project, definitionId, branch, variables, parameters) {
1084
+ this.validateProject(project);
1085
+ if (!this.config.enablePipelineUpsert) {
1086
+ throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1087
+ }
1088
+ const buildRequest = {
1089
+ definition: { id: definitionId }
1090
+ };
1091
+ if (branch) {
1092
+ buildRequest.sourceBranch = branch;
1093
+ }
1094
+ if (variables) {
1095
+ buildRequest.parameters = JSON.stringify(variables);
1096
+ }
1097
+ if (parameters) {
1098
+ buildRequest.templateParameters = parameters;
1099
+ }
1100
+ const response = await this.makeRequest(`${project}/_apis/build/builds?api-version=${this.apiVersion}`, 'POST', buildRequest);
1101
+ return {
1102
+ id: response.id,
1103
+ buildNumber: response.buildNumber,
1104
+ status: response.status,
1105
+ queueTime: response.queueTime,
1106
+ definition: response.definition?.name,
1107
+ sourceBranch: response.sourceBranch,
1108
+ project,
1109
+ url: response._links?.web?.href
1110
+ };
1111
+ }
1112
+ /**
1113
+ * Cancel a running build
1114
+ * @param project The project name
1115
+ * @param buildId The build ID
1116
+ * @returns Cancelled build
1117
+ */
1118
+ async cancelBuild(project, buildId) {
1119
+ this.validateProject(project);
1120
+ if (!this.config.enablePipelineUpsert) {
1121
+ throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1122
+ }
1123
+ const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`, 'PATCH', { status: 'cancelling' });
1124
+ return {
1125
+ id: response.id,
1126
+ buildNumber: response.buildNumber,
1127
+ status: response.status,
1128
+ project,
1129
+ message: 'Build cancellation requested'
1130
+ };
1131
+ }
1132
+ /**
1133
+ * Retry a failed build
1134
+ * @param project The project name
1135
+ * @param buildId The build ID to retry
1136
+ * @returns New build
1137
+ */
1138
+ async retryBuild(project, buildId) {
1139
+ this.validateProject(project);
1140
+ if (!this.config.enablePipelineUpsert) {
1141
+ throw new Error('Pipeline upsert operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_UPSERT=true to enable.');
1142
+ }
1143
+ // Get the original build to get definition and source info
1144
+ const original = await this.makeRequest(`${project}/_apis/build/builds/${buildId}?api-version=${this.apiVersion}`);
1145
+ // Queue a new build with the same parameters
1146
+ return this.queueBuild(project, original.definition.id, original.sourceBranch);
1147
+ }
1148
+ /**
1149
+ * Delete a pipeline definition
1150
+ * @param project The project name
1151
+ * @param definitionId The pipeline definition ID
1152
+ * @returns Deletion confirmation
1153
+ */
1154
+ async deletePipelineDefinition(project, definitionId) {
1155
+ this.validateProject(project);
1156
+ if (!this.config.enablePipelineDelete) {
1157
+ throw new Error('Pipeline delete operations are disabled. Set AZUREDEVOPS_ENABLE_PIPELINE_DELETE=true to enable.');
1158
+ }
1159
+ await this.makeRequest(`${project}/_apis/build/definitions/${definitionId}?api-version=${this.apiVersion}`, 'DELETE');
1160
+ return {
1161
+ definitionId,
1162
+ project,
1163
+ deleted: true
1164
+ };
1165
+ }
1166
+ // ═══════════════════════════════════════════════════════════════════════════════
1167
+ // SERVICE CONNECTION OPERATIONS (DevOps Admin Tools)
1168
+ // ═══════════════════════════════════════════════════════════════════════════════
1169
+ /**
1170
+ * List all service connections in a project
1171
+ * @param project The project name
1172
+ * @returns List of service connections
1173
+ */
1174
+ async listServiceConnections(project) {
1175
+ this.validateProject(project);
1176
+ const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`);
1177
+ return {
1178
+ project,
1179
+ totalCount: response.value.length,
1180
+ serviceConnections: response.value.map((conn) => ({
1181
+ id: conn.id,
1182
+ name: conn.name,
1183
+ type: conn.type,
1184
+ url: conn.url,
1185
+ description: conn.description,
1186
+ isShared: conn.isShared,
1187
+ isReady: conn.isReady,
1188
+ owner: conn.owner,
1189
+ createdBy: conn.createdBy?.displayName,
1190
+ authorization: conn.authorization ? {
1191
+ scheme: conn.authorization.scheme,
1192
+ // Mask any sensitive parameters
1193
+ parameters: conn.authorization.parameters ?
1194
+ Object.keys(conn.authorization.parameters).reduce((acc, key) => {
1195
+ acc[key] = key.toLowerCase().includes('password') ||
1196
+ key.toLowerCase().includes('secret') ||
1197
+ key.toLowerCase().includes('key') ||
1198
+ key.toLowerCase().includes('token')
1199
+ ? '***SECRET***'
1200
+ : conn.authorization.parameters[key];
1201
+ return acc;
1202
+ }, {}) : {}
1203
+ } : null
1204
+ }))
1205
+ };
1206
+ }
1207
+ /**
1208
+ * Get a specific service connection
1209
+ * @param project The project name
1210
+ * @param connectionId The service connection ID
1211
+ * @returns Service connection details
1212
+ */
1213
+ async getServiceConnection(project, connectionId) {
1214
+ this.validateProject(project);
1215
+ const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
1216
+ return {
1217
+ id: response.id,
1218
+ name: response.name,
1219
+ type: response.type,
1220
+ url: response.url,
1221
+ description: response.description,
1222
+ isShared: response.isShared,
1223
+ isReady: response.isReady,
1224
+ owner: response.owner,
1225
+ createdBy: response.createdBy?.displayName,
1226
+ project,
1227
+ authorization: response.authorization ? {
1228
+ scheme: response.authorization.scheme,
1229
+ // Mask any sensitive parameters
1230
+ parameters: response.authorization.parameters ?
1231
+ Object.keys(response.authorization.parameters).reduce((acc, key) => {
1232
+ acc[key] = key.toLowerCase().includes('password') ||
1233
+ key.toLowerCase().includes('secret') ||
1234
+ key.toLowerCase().includes('key') ||
1235
+ key.toLowerCase().includes('token')
1236
+ ? '***SECRET***'
1237
+ : response.authorization.parameters[key];
1238
+ return acc;
1239
+ }, {}) : {}
1240
+ } : null,
1241
+ data: response.data || {},
1242
+ serviceEndpointProjectReferences: response.serviceEndpointProjectReferences
1243
+ };
1244
+ }
1245
+ /**
1246
+ * Get available service connection types
1247
+ * @returns List of service connection types
1248
+ */
1249
+ async getServiceConnectionTypes() {
1250
+ const response = await this.makeRequest(`_apis/serviceendpoint/types?api-version=${this.apiVersion}`);
1251
+ return {
1252
+ totalCount: response.value.length,
1253
+ types: response.value.map((type) => ({
1254
+ name: type.name,
1255
+ displayName: type.displayName,
1256
+ description: type.description,
1257
+ helpMarkDown: type.helpMarkDown,
1258
+ authenticationSchemes: type.authenticationSchemes?.map((scheme) => ({
1259
+ scheme: scheme.scheme,
1260
+ displayName: scheme.displayName
1261
+ })) || []
1262
+ }))
1263
+ };
1264
+ }
1265
+ /**
1266
+ * Create a new service connection
1267
+ * @param project The project name
1268
+ * @param name Connection name
1269
+ * @param type Connection type
1270
+ * @param configuration Type-specific configuration
1271
+ * @returns Created service connection
1272
+ */
1273
+ async createServiceConnection(project, name, type, configuration) {
1274
+ this.validateProject(project);
1275
+ if (!this.config.enableServiceConnUpsert) {
1276
+ throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
1277
+ }
1278
+ const endpoint = {
1279
+ name,
1280
+ type,
1281
+ url: configuration.url || '',
1282
+ description: configuration.description || '',
1283
+ authorization: configuration.authorization,
1284
+ data: configuration.data || {},
1285
+ isShared: false,
1286
+ isReady: true
1287
+ };
1288
+ const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints?api-version=${this.apiVersion}`, 'POST', endpoint);
1289
+ return {
1290
+ id: response.id,
1291
+ name: response.name,
1292
+ type: response.type,
1293
+ isReady: response.isReady,
1294
+ project
1295
+ };
1296
+ }
1297
+ /**
1298
+ * Update a service connection
1299
+ * @param project The project name
1300
+ * @param connectionId The service connection ID
1301
+ * @param updates Updated properties (not credentials)
1302
+ * @returns Updated service connection
1303
+ */
1304
+ async updateServiceConnection(project, connectionId, updates) {
1305
+ this.validateProject(project);
1306
+ if (!this.config.enableServiceConnUpsert) {
1307
+ throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
1308
+ }
1309
+ // Get current connection first
1310
+ const current = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`);
1311
+ // Merge updates (but don't allow overwriting authorization for security)
1312
+ const updated = {
1313
+ ...current,
1314
+ name: updates.name || current.name,
1315
+ description: updates.description || current.description,
1316
+ url: updates.url || current.url,
1317
+ data: updates.data ? { ...current.data, ...updates.data } : current.data
1318
+ };
1319
+ const response = await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PUT', updated);
1320
+ return {
1321
+ id: response.id,
1322
+ name: response.name,
1323
+ type: response.type,
1324
+ isReady: response.isReady,
1325
+ project
1326
+ };
1327
+ }
1328
+ /**
1329
+ * Share a service connection across projects
1330
+ * @param connectionId The service connection ID
1331
+ * @param projectIds Array of project IDs to share with
1332
+ * @returns Updated service connection
1333
+ */
1334
+ async shareServiceConnection(connectionId, projectIds) {
1335
+ if (!this.config.enableServiceConnUpsert) {
1336
+ throw new Error('Service connection upsert operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_UPSERT=true to enable.');
1337
+ }
1338
+ // This requires fetching the current endpoint and updating its project references
1339
+ // Note: This is a simplified implementation - full implementation would need project name lookup
1340
+ const shareRequest = projectIds.map(projectId => ({
1341
+ projectReference: { id: projectId },
1342
+ name: '' // Will be set by API
1343
+ }));
1344
+ const response = await this.makeRequest(`_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'PATCH', { serviceEndpointProjectReferences: shareRequest });
1345
+ return {
1346
+ id: response.id,
1347
+ name: response.name,
1348
+ sharedWith: projectIds
1349
+ };
1350
+ }
1351
+ /**
1352
+ * Delete a service connection
1353
+ * @param project The project name
1354
+ * @param connectionId The service connection ID
1355
+ * @returns Deletion confirmation
1356
+ */
1357
+ async deleteServiceConnection(project, connectionId) {
1358
+ this.validateProject(project);
1359
+ if (!this.config.enableServiceConnDelete) {
1360
+ throw new Error('Service connection delete operations are disabled. Set AZUREDEVOPS_ENABLE_SERVICE_CONN_DELETE=true to enable.');
1361
+ }
1362
+ await this.makeRequest(`${project}/_apis/serviceendpoint/endpoints/${connectionId}?api-version=${this.apiVersion}`, 'DELETE');
1363
+ return {
1364
+ connectionId,
1365
+ project,
1366
+ deleted: true
1367
+ };
1368
+ }
1369
+ // ═══════════════════════════════════════════════════════════════════════════════
1370
+ // VARIABLE GROUP UPSERT/DELETE OPERATIONS (DevOps Admin Tools)
1371
+ // ═══════════════════════════════════════════════════════════════════════════════
1372
+ /**
1373
+ * Create a new variable group
1374
+ * @param project The project name
1375
+ * @param name Variable group name
1376
+ * @param description Optional description
1377
+ * @param variables Initial variables
1378
+ * @returns Created variable group
1379
+ */
1380
+ async createVariableGroup(project, name, description, variables) {
1381
+ this.validateProject(project);
1382
+ if (!this.config.enableVariableGroupUpsert) {
1383
+ throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
1384
+ }
1385
+ const variableGroup = {
1386
+ name,
1387
+ description: description || '',
1388
+ type: 'Vsts',
1389
+ variables: variables || {},
1390
+ variableGroupProjectReferences: [{
1391
+ projectReference: { name: project },
1392
+ name: name
1393
+ }]
1394
+ };
1395
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups?api-version=${this.apiVersion}`, 'POST', variableGroup);
1396
+ return {
1397
+ id: response.id,
1398
+ name: response.name,
1399
+ project,
1400
+ variableCount: Object.keys(response.variables || {}).length
1401
+ };
1402
+ }
1403
+ /**
1404
+ * Update a variable group's metadata
1405
+ * @param project The project name
1406
+ * @param groupId The variable group ID
1407
+ * @param updates Updated properties
1408
+ * @returns Updated variable group
1409
+ */
1410
+ async updateVariableGroupMetadata(project, groupId, updates) {
1411
+ this.validateProject(project);
1412
+ if (!this.config.enableVariableGroupUpsert) {
1413
+ throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
1414
+ }
1415
+ // Get current group first
1416
+ const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
1417
+ const updated = {
1418
+ ...current,
1419
+ name: updates.name || current.name,
1420
+ description: updates.description || current.description
1421
+ };
1422
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', updated);
1423
+ return {
1424
+ id: response.id,
1425
+ name: response.name,
1426
+ description: response.description,
1427
+ project
1428
+ };
1429
+ }
1430
+ /**
1431
+ * Set or update a variable in a variable group
1432
+ * @param project The project name
1433
+ * @param groupId The variable group ID
1434
+ * @param variableName Variable name
1435
+ * @param value Variable value
1436
+ * @param isSecret Whether the variable is secret
1437
+ * @returns Updated variable group
1438
+ */
1439
+ async setVariable(project, groupId, variableName, value, isSecret = false) {
1440
+ this.validateProject(project);
1441
+ if (!this.config.enableVariableGroupUpsert) {
1442
+ throw new Error('Variable group upsert operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_UPSERT=true to enable.');
1443
+ }
1444
+ // Get current group first
1445
+ const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
1446
+ // Update the variable
1447
+ current.variables[variableName] = { value, isSecret };
1448
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
1449
+ return {
1450
+ id: response.id,
1451
+ name: response.name,
1452
+ project,
1453
+ variableSet: variableName,
1454
+ isSecret
1455
+ };
1456
+ }
1457
+ /**
1458
+ * Remove a variable from a variable group
1459
+ * @param project The project name
1460
+ * @param groupId The variable group ID
1461
+ * @param variableName Variable name to remove
1462
+ * @returns Updated variable group
1463
+ */
1464
+ async removeVariable(project, groupId, variableName) {
1465
+ this.validateProject(project);
1466
+ if (!this.config.enableVariableGroupDelete) {
1467
+ throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
1468
+ }
1469
+ // Get current group first
1470
+ const current = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`);
1471
+ // Remove the variable
1472
+ if (current.variables[variableName]) {
1473
+ delete current.variables[variableName];
1474
+ }
1475
+ else {
1476
+ throw new Error(`Variable '${variableName}' not found in group ${groupId}`);
1477
+ }
1478
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'PUT', current);
1479
+ return {
1480
+ id: response.id,
1481
+ name: response.name,
1482
+ project,
1483
+ variableRemoved: variableName
1484
+ };
1485
+ }
1486
+ /**
1487
+ * Delete a variable group
1488
+ * @param project The project name
1489
+ * @param groupId The variable group ID
1490
+ * @returns Deletion confirmation
1491
+ */
1492
+ async deleteVariableGroup(project, groupId) {
1493
+ this.validateProject(project);
1494
+ if (!this.config.enableVariableGroupDelete) {
1495
+ throw new Error('Variable group delete operations are disabled. Set AZUREDEVOPS_ENABLE_VARIABLE_GROUP_DELETE=true to enable.');
1496
+ }
1497
+ await this.makeRequest(`${project}/_apis/distributedtask/variablegroups/${groupId}?api-version=${this.apiVersion}`, 'DELETE');
1498
+ return {
1499
+ groupId,
1500
+ project,
1501
+ deleted: true
1502
+ };
1503
+ }
1504
+ // ═══════════════════════════════════════════════════════════════════════════════
1505
+ // AGENT POOL OPERATIONS (DevOps Admin Tools)
1506
+ // ═══════════════════════════════════════════════════════════════════════════════
1507
+ /**
1508
+ * List all agent pools
1509
+ * @param poolType Optional filter: "automation" or "deployment"
1510
+ * @returns List of agent pools
1511
+ */
1512
+ async listAgentPools(poolType) {
1513
+ let url = `_apis/distributedtask/pools?api-version=${this.apiVersion}`;
1514
+ if (poolType) {
1515
+ url += `&poolType=${poolType}`;
1516
+ }
1517
+ const response = await this.makeRequest(url);
1518
+ return {
1519
+ totalCount: response.value.length,
1520
+ pools: response.value.map((pool) => ({
1521
+ id: pool.id,
1522
+ name: pool.name,
1523
+ size: pool.size,
1524
+ isHosted: pool.isHosted,
1525
+ poolType: pool.poolType,
1526
+ createdBy: pool.createdBy?.displayName,
1527
+ createdOn: pool.createdOn,
1528
+ autoProvision: pool.autoProvision,
1529
+ autoUpdate: pool.autoUpdate,
1530
+ autoSize: pool.autoSize,
1531
+ targetSize: pool.targetSize
1532
+ }))
1533
+ };
1534
+ }
1535
+ /**
1536
+ * Get a specific agent pool
1537
+ * @param poolId The pool ID
1538
+ * @returns Agent pool details
1539
+ */
1540
+ async getAgentPool(poolId) {
1541
+ const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`);
1542
+ return {
1543
+ id: response.id,
1544
+ name: response.name,
1545
+ size: response.size,
1546
+ isHosted: response.isHosted,
1547
+ poolType: response.poolType,
1548
+ createdBy: response.createdBy?.displayName,
1549
+ createdOn: response.createdOn,
1550
+ autoProvision: response.autoProvision,
1551
+ autoUpdate: response.autoUpdate,
1552
+ autoSize: response.autoSize,
1553
+ targetSize: response.targetSize,
1554
+ owner: response.owner?.displayName,
1555
+ agentCloudId: response.agentCloudId,
1556
+ properties: response.properties
1557
+ };
1558
+ }
1559
+ /**
1560
+ * List agents in a pool
1561
+ * @param poolId The pool ID
1562
+ * @param includeCapabilities Include agent capabilities
1563
+ * @returns List of agents
1564
+ */
1565
+ async listAgents(poolId, includeCapabilities = false) {
1566
+ const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents?includeCapabilities=${includeCapabilities}&api-version=${this.apiVersion}`);
1567
+ return {
1568
+ poolId,
1569
+ totalCount: response.value.length,
1570
+ agents: response.value.map((agent) => ({
1571
+ id: agent.id,
1572
+ name: agent.name,
1573
+ version: agent.version,
1574
+ osDescription: agent.osDescription,
1575
+ enabled: agent.enabled,
1576
+ status: agent.status,
1577
+ provisioningState: agent.provisioningState,
1578
+ createdOn: agent.createdOn,
1579
+ maxParallelism: agent.maxParallelism,
1580
+ systemCapabilities: includeCapabilities ? agent.systemCapabilities : undefined,
1581
+ userCapabilities: includeCapabilities ? agent.userCapabilities : undefined
1582
+ }))
1583
+ };
1584
+ }
1585
+ /**
1586
+ * Get a specific agent
1587
+ * @param poolId The pool ID
1588
+ * @param agentId The agent ID
1589
+ * @returns Agent details
1590
+ */
1591
+ async getAgent(poolId, agentId) {
1592
+ const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?includeCapabilities=true&api-version=${this.apiVersion}`);
1593
+ return {
1594
+ id: response.id,
1595
+ name: response.name,
1596
+ version: response.version,
1597
+ osDescription: response.osDescription,
1598
+ enabled: response.enabled,
1599
+ status: response.status,
1600
+ provisioningState: response.provisioningState,
1601
+ accessPoint: response.accessPoint,
1602
+ createdOn: response.createdOn,
1603
+ maxParallelism: response.maxParallelism,
1604
+ systemCapabilities: response.systemCapabilities,
1605
+ userCapabilities: response.userCapabilities,
1606
+ assignedRequest: response.assignedRequest,
1607
+ lastCompletedRequest: response.lastCompletedRequest
1608
+ };
1609
+ }
1610
+ /**
1611
+ * Update an agent pool
1612
+ * @param poolId The pool ID
1613
+ * @param updates Updated settings
1614
+ * @returns Updated agent pool
1615
+ */
1616
+ async updateAgentPool(poolId, updates) {
1617
+ if (!this.config.enableAgentPoolUpsert) {
1618
+ throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
1619
+ }
1620
+ const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}?api-version=${this.apiVersion}`, 'PATCH', updates);
1621
+ return {
1622
+ id: response.id,
1623
+ name: response.name,
1624
+ autoProvision: response.autoProvision,
1625
+ autoUpdate: response.autoUpdate,
1626
+ autoSize: response.autoSize,
1627
+ targetSize: response.targetSize
1628
+ };
1629
+ }
1630
+ /**
1631
+ * Enable a disabled agent
1632
+ * @param poolId The pool ID
1633
+ * @param agentId The agent ID
1634
+ * @returns Updated agent
1635
+ */
1636
+ async enableAgent(poolId, agentId) {
1637
+ if (!this.config.enableAgentPoolUpsert) {
1638
+ throw new Error('Agent pool upsert operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_UPSERT=true to enable.');
1639
+ }
1640
+ const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: true });
1641
+ return {
1642
+ id: response.id,
1643
+ name: response.name,
1644
+ enabled: response.enabled,
1645
+ status: response.status
1646
+ };
1647
+ }
1648
+ /**
1649
+ * Disable an agent
1650
+ * @param poolId The pool ID
1651
+ * @param agentId The agent ID
1652
+ * @returns Updated agent
1653
+ */
1654
+ async disableAgent(poolId, agentId) {
1655
+ if (!this.config.enableAgentPoolDisable) {
1656
+ throw new Error('Agent pool disable operations are disabled. Set AZUREDEVOPS_ENABLE_AGENT_POOL_DISABLE=true to enable.');
1657
+ }
1658
+ const response = await this.makeRequest(`_apis/distributedtask/pools/${poolId}/agents/${agentId}?api-version=${this.apiVersion}`, 'PATCH', { enabled: false });
1659
+ return {
1660
+ id: response.id,
1661
+ name: response.name,
1662
+ enabled: response.enabled,
1663
+ status: response.status,
1664
+ message: 'Agent disabled - will complete current job then stop accepting new jobs'
1665
+ };
1666
+ }
1667
+ // ═══════════════════════════════════════════════════════════════════════════════
1668
+ // ENVIRONMENT OPERATIONS (DevOps Admin Tools)
1669
+ // ═══════════════════════════════════════════════════════════════════════════════
1670
+ /**
1671
+ * List all environments in a project
1672
+ * @param project The project name
1673
+ * @returns List of environments
1674
+ */
1675
+ async listEnvironments(project) {
1676
+ this.validateProject(project);
1677
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`);
1678
+ return {
1679
+ project,
1680
+ totalCount: response.value.length,
1681
+ environments: response.value.map((env) => ({
1682
+ id: env.id,
1683
+ name: env.name,
1684
+ description: env.description,
1685
+ createdBy: env.createdBy?.displayName,
1686
+ createdOn: env.createdOn,
1687
+ lastModifiedBy: env.lastModifiedBy?.displayName,
1688
+ lastModifiedOn: env.lastModifiedOn
1689
+ }))
1690
+ };
1691
+ }
1692
+ /**
1693
+ * Get a specific environment
1694
+ * @param project The project name
1695
+ * @param environmentId The environment ID
1696
+ * @returns Environment details
1697
+ */
1698
+ async getEnvironment(project, environmentId) {
1699
+ this.validateProject(project);
1700
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?expands=resourceReferences&api-version=${this.apiVersion}`);
1701
+ return {
1702
+ id: response.id,
1703
+ name: response.name,
1704
+ description: response.description,
1705
+ createdBy: response.createdBy?.displayName,
1706
+ createdOn: response.createdOn,
1707
+ lastModifiedBy: response.lastModifiedBy?.displayName,
1708
+ lastModifiedOn: response.lastModifiedOn,
1709
+ project,
1710
+ resources: response.resources || []
1711
+ };
1712
+ }
1713
+ /**
1714
+ * Get deployment history for an environment
1715
+ * @param project The project name
1716
+ * @param environmentId The environment ID
1717
+ * @param top Maximum number of results
1718
+ * @returns Deployment records
1719
+ */
1720
+ async getEnvironmentDeployments(project, environmentId, top = 10) {
1721
+ this.validateProject(project);
1722
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}/environmentdeploymentrecords?top=${top}&api-version=${this.apiVersion}`);
1723
+ return {
1724
+ project,
1725
+ environmentId,
1726
+ totalCount: response.value.length,
1727
+ deployments: response.value.map((dep) => ({
1728
+ id: dep.id,
1729
+ environmentId: dep.environmentId,
1730
+ definition: dep.definition ? {
1731
+ id: dep.definition.id,
1732
+ name: dep.definition.name
1733
+ } : null,
1734
+ owner: dep.owner?.displayName,
1735
+ planType: dep.planType,
1736
+ startTime: dep.startTime,
1737
+ finishTime: dep.finishTime,
1738
+ result: dep.result,
1739
+ queueTime: dep.queueTime
1740
+ }))
1741
+ };
1742
+ }
1743
+ /**
1744
+ * Create a new environment
1745
+ * @param project The project name
1746
+ * @param name Environment name
1747
+ * @param description Optional description
1748
+ * @returns Created environment
1749
+ */
1750
+ async createEnvironment(project, name, description) {
1751
+ this.validateProject(project);
1752
+ if (!this.config.enableEnvironmentUpsert) {
1753
+ throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1754
+ }
1755
+ const environment = {
1756
+ name,
1757
+ description: description || ''
1758
+ };
1759
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`, 'POST', environment);
1760
+ return {
1761
+ id: response.id,
1762
+ name: response.name,
1763
+ description: response.description,
1764
+ project
1765
+ };
1766
+ }
1767
+ /**
1768
+ * Update an environment
1769
+ * @param project The project name
1770
+ * @param environmentId The environment ID
1771
+ * @param updates Updated properties
1772
+ * @returns Updated environment
1773
+ */
1774
+ async updateEnvironment(project, environmentId, updates) {
1775
+ this.validateProject(project);
1776
+ if (!this.config.enableEnvironmentUpsert) {
1777
+ throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1778
+ }
1779
+ const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'PATCH', updates);
1780
+ return {
1781
+ id: response.id,
1782
+ name: response.name,
1783
+ description: response.description,
1784
+ project
1785
+ };
1786
+ }
1787
+ /**
1788
+ * Delete an environment
1789
+ * @param project The project name
1790
+ * @param environmentId The environment ID
1791
+ * @returns Deletion confirmation
1792
+ */
1793
+ async deleteEnvironment(project, environmentId) {
1794
+ this.validateProject(project);
1795
+ if (!this.config.enableEnvironmentDelete) {
1796
+ throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
1797
+ }
1798
+ await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?api-version=${this.apiVersion}`, 'DELETE');
1799
+ return {
1800
+ environmentId,
1801
+ project,
1802
+ deleted: true
1803
+ };
1804
+ }
1805
+ /**
1806
+ * Get all checks configured for an environment
1807
+ * @param project The project name
1808
+ * @param environmentId The environment ID
1809
+ * @returns List of check configurations
1810
+ */
1811
+ async getEnvironmentChecks(project, environmentId) {
1812
+ this.validateProject(project);
1813
+ const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?resourceType=environment&resourceId=${environmentId}&api-version=7.1-preview.1`);
1814
+ return {
1815
+ project,
1816
+ environmentId,
1817
+ totalCount: response.value.length,
1818
+ checks: response.value.map((check) => ({
1819
+ id: check.id,
1820
+ type: check.type?.name || check.type?.id,
1821
+ settings: check.settings,
1822
+ timeout: check.timeout,
1823
+ retryInterval: check.retryInterval,
1824
+ createdBy: check.createdBy?.displayName,
1825
+ createdOn: check.createdOn,
1826
+ modifiedBy: check.modifiedBy?.displayName,
1827
+ modifiedOn: check.modifiedOn,
1828
+ resource: check.resource
1829
+ }))
1830
+ };
1831
+ }
1832
+ /**
1833
+ * Add a check to an environment
1834
+ * @param project The project name
1835
+ * @param environmentId The environment ID
1836
+ * @param checkType The type of check (e.g., "Approval", "ExclusiveLock")
1837
+ * @param configuration Check configuration
1838
+ * @returns Created check
1839
+ */
1840
+ async addEnvironmentCheck(project, environmentId, checkType, configuration) {
1841
+ this.validateProject(project);
1842
+ if (!this.config.enableEnvironmentUpsert) {
1843
+ throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1844
+ }
1845
+ const check = {
1846
+ type: { name: checkType },
1847
+ settings: configuration,
1848
+ resource: {
1849
+ type: 'environment',
1850
+ id: String(environmentId)
1851
+ }
1852
+ };
1853
+ const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations?api-version=7.1-preview.1`, 'POST', check);
1854
+ return {
1855
+ id: response.id,
1856
+ type: response.type?.name,
1857
+ environmentId,
1858
+ project
1859
+ };
1860
+ }
1861
+ /**
1862
+ * Update an existing environment check
1863
+ * @param project The project name
1864
+ * @param checkId The check configuration ID
1865
+ * @param updates Updated settings and/or timeout
1866
+ * @returns Updated check
1867
+ */
1868
+ async updateEnvironmentCheck(project, checkId, updates) {
1869
+ this.validateProject(project);
1870
+ if (!this.config.enableEnvironmentUpsert) {
1871
+ throw new Error('Environment upsert operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_UPSERT=true to enable.');
1872
+ }
1873
+ // Get current check configuration first
1874
+ const current = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`);
1875
+ // Merge updates
1876
+ const updated = {
1877
+ ...current,
1878
+ settings: updates.settings !== undefined ? updates.settings : current.settings,
1879
+ timeout: updates.timeout !== undefined ? updates.timeout : current.timeout
1880
+ };
1881
+ const response = await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'PATCH', updated);
1882
+ return {
1883
+ id: response.id,
1884
+ type: response.type?.name,
1885
+ settings: response.settings,
1886
+ timeout: response.timeout,
1887
+ project
1888
+ };
1889
+ }
1890
+ /**
1891
+ * Remove a check from an environment
1892
+ * @param project The project name
1893
+ * @param checkId The check configuration ID
1894
+ * @returns Deletion confirmation
1895
+ */
1896
+ async removeEnvironmentCheck(project, checkId) {
1897
+ this.validateProject(project);
1898
+ if (!this.config.enableEnvironmentDelete) {
1899
+ throw new Error('Environment delete operations are disabled. Set AZUREDEVOPS_ENABLE_ENVIRONMENT_DELETE=true to enable.');
1900
+ }
1901
+ await this.makeRequest(`${project}/_apis/pipelines/checks/configurations/${checkId}?api-version=7.1-preview.1`, 'DELETE');
1902
+ return {
1903
+ checkId,
1904
+ project,
1905
+ deleted: true
1906
+ };
1907
+ }
716
1908
  }
717
1909
  //# sourceMappingURL=AzureDevOpsService.js.map