@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.
- package/build/AzureDevOpsService.d.ts +146 -543
- package/build/AzureDevOpsService.d.ts.map +1 -1
- package/build/AzureDevOpsService.js +203 -1189
- package/build/AzureDevOpsService.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +127 -850
- package/build/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -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
|
-
|
|
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
|
-
//
|
|
729
|
+
// PULL REQUEST OPERATIONS
|
|
743
730
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
744
731
|
/**
|
|
745
|
-
* List all
|
|
732
|
+
* List all Git repositories in a project
|
|
746
733
|
* @param project The project name
|
|
747
|
-
* @returns List of
|
|
734
|
+
* @returns List of repositories with their IDs
|
|
748
735
|
*/
|
|
749
|
-
async
|
|
736
|
+
async listRepositories(project) {
|
|
750
737
|
this.validateProject(project);
|
|
751
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
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
|
-
|
|
756
|
-
id:
|
|
757
|
-
name:
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
763
|
+
async listPullRequests(project, repositoryId, status = 'active', top = 25, creatorId, reviewerId) {
|
|
1260
764
|
this.validateProject(project);
|
|
1261
|
-
|
|
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
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
|
794
|
+
* Get details of a specific pull request
|
|
1294
795
|
* @param project The project name
|
|
1295
|
-
* @param
|
|
1296
|
-
* @
|
|
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
|
|
800
|
+
async getPullRequest(project, repositoryId, pullRequestId) {
|
|
1299
801
|
this.validateProject(project);
|
|
1300
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
802
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`);
|
|
1301
803
|
return {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
type: response.type,
|
|
1305
|
-
url: response.url,
|
|
804
|
+
pullRequestId: response.pullRequestId,
|
|
805
|
+
title: response.title,
|
|
1306
806
|
description: response.description,
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
856
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
857
|
+
* @param pullRequestId The PR ID
|
|
858
|
+
* @returns List of commits
|
|
1759
859
|
*/
|
|
1760
|
-
async
|
|
860
|
+
async getPullRequestCommits(project, repositoryId, pullRequestId) {
|
|
1761
861
|
this.validateProject(project);
|
|
1762
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
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
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
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
|
|
884
|
+
* Get threads/comments on a pull request
|
|
1779
885
|
* @param project The project name
|
|
1780
|
-
* @param
|
|
1781
|
-
* @
|
|
886
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
887
|
+
* @param pullRequestId The PR ID
|
|
888
|
+
* @returns List of discussion threads
|
|
1782
889
|
*/
|
|
1783
|
-
async
|
|
890
|
+
async getPullRequestThreads(project, repositoryId, pullRequestId) {
|
|
1784
891
|
this.validateProject(project);
|
|
1785
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
892
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=${this.apiVersion}`);
|
|
1786
893
|
return {
|
|
1787
|
-
|
|
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
|
-
|
|
1813
|
-
id:
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
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
|
-
*
|
|
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
|
|
1876
|
-
* @
|
|
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
|
|
927
|
+
async getPullRequestChanges(project, repositoryId, pullRequestId, iterationId) {
|
|
1879
928
|
this.validateProject(project);
|
|
1880
|
-
|
|
1881
|
-
|
|
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/
|
|
940
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/iterations/${targetIteration}/changes?api-version=${this.apiVersion}`);
|
|
1884
941
|
return {
|
|
1885
|
-
|
|
942
|
+
pullRequestId,
|
|
943
|
+
iterationId: targetIteration,
|
|
1886
944
|
project,
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
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
|
|
956
|
+
* Add a comment thread to a pull request
|
|
1919
957
|
* @param project The project name
|
|
1920
|
-
* @param
|
|
1921
|
-
* @param
|
|
1922
|
-
* @param
|
|
1923
|
-
* @
|
|
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
|
|
966
|
+
async addPullRequestThread(project, repositoryId, pullRequestId, content, filePath, lineNumber, status = 'active') {
|
|
1926
967
|
this.validateProject(project);
|
|
1927
|
-
if (!this.config.
|
|
1928
|
-
throw new Error('
|
|
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
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
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
|