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