@mcp-consultant-tools/azure-devops 26.0.0-beta.1 → 27.0.0-beta.10
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 +179 -514
- package/build/AzureDevOpsService.d.ts.map +1 -1
- package/build/AzureDevOpsService.js +401 -994
- package/build/AzureDevOpsService.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1038 -844
- package/build/index.js.map +1 -1
- package/build/sync/file-utils.d.ts +86 -0
- package/build/sync/file-utils.d.ts.map +1 -0
- package/build/sync/file-utils.js +224 -0
- package/build/sync/file-utils.js.map +1 -0
- package/build/sync/git-utils.d.ts +31 -0
- package/build/sync/git-utils.d.ts.map +1 -0
- package/build/sync/git-utils.js +116 -0
- package/build/sync/git-utils.js.map +1 -0
- package/build/sync/html-converter.d.ts +32 -0
- package/build/sync/html-converter.d.ts.map +1 -0
- package/build/sync/html-converter.js +91 -0
- package/build/sync/html-converter.js.map +1 -0
- package/build/sync/html-detection.d.ts +93 -0
- package/build/sync/html-detection.d.ts.map +1 -0
- package/build/sync/html-detection.js +169 -0
- package/build/sync/html-detection.js.map +1 -0
- package/build/sync/index.d.ts +12 -0
- package/build/sync/index.d.ts.map +1 -0
- package/build/sync/index.js +12 -0
- package/build/sync/index.js.map +1 -0
- package/build/sync/markdown-serializer.d.ts +136 -0
- package/build/sync/markdown-serializer.d.ts.map +1 -0
- package/build/sync/markdown-serializer.js +646 -0
- package/build/sync/markdown-serializer.js.map +1 -0
- package/build/sync/task-serializer.d.ts +93 -0
- package/build/sync/task-serializer.d.ts.map +1 -0
- package/build/sync/task-serializer.js +395 -0
- package/build/sync/task-serializer.js.map +1 -0
- package/build/tool-examples.d.ts +56 -0
- package/build/tool-examples.d.ts.map +1 -0
- package/build/tool-examples.js +142 -0
- package/build/tool-examples.js.map +1 -0
- package/package.json +5 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { marked } from 'marked';
|
|
3
|
+
import { getAllLargeTextFields } from './sync/html-detection.js';
|
|
3
4
|
export class AzureDevOpsService {
|
|
4
5
|
config;
|
|
5
6
|
baseUrl;
|
|
@@ -13,20 +14,7 @@ export class AzureDevOpsService {
|
|
|
13
14
|
enableWorkItemWrite: config.enableWorkItemWrite ?? false,
|
|
14
15
|
enableWorkItemDelete: config.enableWorkItemDelete ?? false,
|
|
15
16
|
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,
|
|
17
|
+
enablePullRequestWrite: config.enablePullRequestWrite ?? false,
|
|
30
18
|
};
|
|
31
19
|
this.baseUrl = `https://dev.azure.com/${this.config.organization}`;
|
|
32
20
|
this.searchUrl = `https://almsearch.dev.azure.com/${this.config.organization}`;
|
|
@@ -527,8 +515,8 @@ export class AzureDevOpsService {
|
|
|
527
515
|
this.validateProject(project);
|
|
528
516
|
// Comments API requires -preview suffix (not GA in 7.1)
|
|
529
517
|
const response = await this.makeRequest(`${project}/_apis/wit/workItems/${workItemId}/comments?api-version=7.1-preview`);
|
|
530
|
-
//
|
|
531
|
-
const comments = response.value || [];
|
|
518
|
+
// v7.1 Comments API returns array in response.comments, not response.value
|
|
519
|
+
const comments = response.comments || response.value || [];
|
|
532
520
|
return {
|
|
533
521
|
workItemId,
|
|
534
522
|
project,
|
|
@@ -595,6 +583,25 @@ export class AzureDevOpsService {
|
|
|
595
583
|
project
|
|
596
584
|
};
|
|
597
585
|
}
|
|
586
|
+
/**
|
|
587
|
+
* Set work item field(s) to markdown format.
|
|
588
|
+
* This is IRREVERSIBLE - once set to markdown, cannot revert to HTML.
|
|
589
|
+
*
|
|
590
|
+
* @param project - Project name
|
|
591
|
+
* @param workItemId - Work item ID
|
|
592
|
+
* @param fields - Array of field names to set to markdown format (e.g., ['System.Description'])
|
|
593
|
+
*/
|
|
594
|
+
async setFieldsToMarkdownFormat(project, workItemId, fields) {
|
|
595
|
+
if (!this.config.enableWorkItemWrite) {
|
|
596
|
+
throw new Error('Work item write operations are disabled. Set AZUREDEVOPS_ENABLE_WORK_ITEM_WRITE=true to enable.');
|
|
597
|
+
}
|
|
598
|
+
const patchOperations = fields.map(field => ({
|
|
599
|
+
op: 'add',
|
|
600
|
+
path: `/multilineFieldsFormat/${field}`,
|
|
601
|
+
value: 'Markdown'
|
|
602
|
+
}));
|
|
603
|
+
await this.updateWorkItem(project, workItemId, patchOperations);
|
|
604
|
+
}
|
|
598
605
|
/**
|
|
599
606
|
* Create a new work item
|
|
600
607
|
* @param project The project name
|
|
@@ -619,6 +626,19 @@ export class AzureDevOpsService {
|
|
|
619
626
|
value: fields[field]
|
|
620
627
|
});
|
|
621
628
|
});
|
|
629
|
+
// Auto-set markdown format for large text fields that are being set
|
|
630
|
+
// This ensures all new work items use markdown format (irreversible)
|
|
631
|
+
// Uses getAllLargeTextFields() to include custom fields like Custom.Howtotest
|
|
632
|
+
const allLargeTextFields = getAllLargeTextFields();
|
|
633
|
+
for (const field of allLargeTextFields) {
|
|
634
|
+
if (fields[field] !== undefined) {
|
|
635
|
+
patchOperations.push({
|
|
636
|
+
op: 'add',
|
|
637
|
+
path: `/multilineFieldsFormat/${field}`,
|
|
638
|
+
value: 'Markdown'
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
622
642
|
// Handle parentId parameter (simplified parent relationship)
|
|
623
643
|
if (parentId !== undefined) {
|
|
624
644
|
const parentUrl = `${this.baseUrl}/${encodeURIComponent(project)}/_apis/wit/workItems/${parentId}`;
|
|
@@ -739,167 +759,17 @@ export class AzureDevOpsService {
|
|
|
739
759
|
};
|
|
740
760
|
}
|
|
741
761
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
742
|
-
//
|
|
762
|
+
// BUILD TROUBLESHOOTING OPERATIONS (Read-only)
|
|
763
|
+
// NOTE: These methods are duplicated in azure-devops-admin package.
|
|
764
|
+
// If you update these, also update packages/azure-devops-admin/src/AzureDevOpsAdminService.ts
|
|
743
765
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
744
766
|
/**
|
|
745
|
-
*
|
|
746
|
-
* @param project The project name
|
|
747
|
-
* @returns List of pipeline definitions
|
|
748
|
-
*/
|
|
749
|
-
async listPipelineDefinitions(project) {
|
|
750
|
-
this.validateProject(project);
|
|
751
|
-
const response = await this.makeRequest(`${project}/_apis/build/definitions?api-version=${this.apiVersion}`);
|
|
752
|
-
return {
|
|
753
|
-
project,
|
|
754
|
-
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
|
|
767
|
+
* Get build status with optional timeline and logs
|
|
897
768
|
* @param project The project name
|
|
898
769
|
* @param buildId The build ID
|
|
899
|
-
* @param detail Level of detail:
|
|
900
|
-
* @param timelineScope
|
|
901
|
-
* @param maxIssues Maximum issues per
|
|
902
|
-
* @returns Build details
|
|
770
|
+
* @param detail Level of detail: 'summary', 'timeline', or 'full'
|
|
771
|
+
* @param timelineScope Scope for timeline: 'stages', 'jobs', 'all', 'problems'
|
|
772
|
+
* @param maxIssues Maximum issues per record
|
|
903
773
|
*/
|
|
904
774
|
async getBuildStatus(project, buildId, detail = 'summary', timelineScope = 'problems', maxIssues = 5) {
|
|
905
775
|
this.validateProject(project);
|
|
@@ -925,12 +795,10 @@ export class AzureDevOpsService {
|
|
|
925
795
|
project: response.project?.name,
|
|
926
796
|
url: response._links?.web?.href
|
|
927
797
|
};
|
|
928
|
-
// Add timeline if requested
|
|
929
798
|
if (detail === 'timeline' || detail === 'full') {
|
|
930
799
|
const timeline = await this.getBuildTimeline(project, buildId, timelineScope, maxIssues);
|
|
931
800
|
result.timeline = timeline;
|
|
932
801
|
}
|
|
933
|
-
// Add logs if full detail requested
|
|
934
802
|
if (detail === 'full') {
|
|
935
803
|
const logs = await this.getBuildLogs(project, buildId);
|
|
936
804
|
result.logs = logs;
|
|
@@ -938,42 +806,35 @@ export class AzureDevOpsService {
|
|
|
938
806
|
return result;
|
|
939
807
|
}
|
|
940
808
|
/**
|
|
941
|
-
* Get build timeline
|
|
809
|
+
* Get build timeline with step-by-step breakdown
|
|
942
810
|
* @param project The project name
|
|
943
811
|
* @param buildId The build ID
|
|
944
|
-
* @param scope Filter scope: 'stages'
|
|
945
|
-
* @param maxIssues Maximum issues per record
|
|
946
|
-
* @returns Build timeline with records and summary stats
|
|
812
|
+
* @param scope Filter scope: 'stages', 'jobs', 'all', 'problems'
|
|
813
|
+
* @param maxIssues Maximum issues per record
|
|
947
814
|
*/
|
|
948
815
|
async getBuildTimeline(project, buildId, scope = 'problems', maxIssues = 5) {
|
|
949
816
|
this.validateProject(project);
|
|
950
817
|
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/timeline?api-version=${this.apiVersion}`);
|
|
951
818
|
const allRecords = response.records || [];
|
|
952
|
-
// Calculate summary stats from all records
|
|
953
819
|
const summary = {
|
|
954
820
|
total: allRecords.length,
|
|
955
821
|
byType: {},
|
|
956
822
|
byResult: {},
|
|
957
823
|
totalErrors: 0,
|
|
958
824
|
totalWarnings: 0,
|
|
959
|
-
failed: [],
|
|
825
|
+
failed: [],
|
|
960
826
|
};
|
|
961
827
|
for (const record of allRecords) {
|
|
962
|
-
// Count by type
|
|
963
828
|
summary.byType[record.type] = (summary.byType[record.type] || 0) + 1;
|
|
964
|
-
// Count by result
|
|
965
829
|
if (record.result) {
|
|
966
830
|
summary.byResult[record.result] = (summary.byResult[record.result] || 0) + 1;
|
|
967
831
|
}
|
|
968
|
-
// Sum errors and warnings
|
|
969
832
|
summary.totalErrors += record.errorCount || 0;
|
|
970
833
|
summary.totalWarnings += record.warningCount || 0;
|
|
971
|
-
// Track failed item names
|
|
972
834
|
if (record.result === 'failed' || record.result === 'canceled') {
|
|
973
835
|
summary.failed.push(`${record.type}: ${record.name}`);
|
|
974
836
|
}
|
|
975
837
|
}
|
|
976
|
-
// Filter records based on scope
|
|
977
838
|
let filteredRecords = allRecords;
|
|
978
839
|
switch (scope) {
|
|
979
840
|
case 'stages':
|
|
@@ -990,14 +851,11 @@ export class AzureDevOpsService {
|
|
|
990
851
|
break;
|
|
991
852
|
case 'all':
|
|
992
853
|
default:
|
|
993
|
-
// Keep all records
|
|
994
854
|
break;
|
|
995
855
|
}
|
|
996
|
-
// Map and truncate issues (prioritizing errors over warnings)
|
|
997
856
|
const truncateIssues = (issues, max) => {
|
|
998
857
|
if (!issues || issues.length === 0)
|
|
999
858
|
return { items: [], totalCount: 0, truncated: false };
|
|
1000
|
-
// Sort: errors first, then warnings, then others
|
|
1001
859
|
const sorted = [...issues].sort((a, b) => {
|
|
1002
860
|
const priority = (issue) => {
|
|
1003
861
|
if (issue.type === 'error')
|
|
@@ -1028,7 +886,7 @@ export class AzureDevOpsService {
|
|
|
1028
886
|
order: record.order,
|
|
1029
887
|
errorCount: record.errorCount,
|
|
1030
888
|
warningCount: record.warningCount,
|
|
1031
|
-
log: record.log ? { id: record.log.id } : null,
|
|
889
|
+
log: record.log ? { id: record.log.id } : null,
|
|
1032
890
|
issues: truncatedIssues.items,
|
|
1033
891
|
issuesTruncated: truncatedIssues.truncated,
|
|
1034
892
|
totalIssueCount: truncatedIssues.totalCount
|
|
@@ -1044,16 +902,14 @@ export class AzureDevOpsService {
|
|
|
1044
902
|
};
|
|
1045
903
|
}
|
|
1046
904
|
/**
|
|
1047
|
-
* Get build logs
|
|
905
|
+
* Get build logs (list or specific log content)
|
|
1048
906
|
* @param project The project name
|
|
1049
907
|
* @param buildId The build ID
|
|
1050
|
-
* @param logId Optional specific log ID
|
|
1051
|
-
* @returns Build logs
|
|
908
|
+
* @param logId Optional specific log ID to retrieve content
|
|
1052
909
|
*/
|
|
1053
910
|
async getBuildLogs(project, buildId, logId) {
|
|
1054
911
|
this.validateProject(project);
|
|
1055
912
|
if (logId !== undefined) {
|
|
1056
|
-
// Get specific log content
|
|
1057
913
|
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs/${logId}?api-version=${this.apiVersion}`);
|
|
1058
914
|
return {
|
|
1059
915
|
buildId,
|
|
@@ -1062,7 +918,6 @@ export class AzureDevOpsService {
|
|
|
1062
918
|
content: response
|
|
1063
919
|
};
|
|
1064
920
|
}
|
|
1065
|
-
// Get list of all logs
|
|
1066
921
|
const response = await this.makeRequest(`${project}/_apis/build/builds/${buildId}/logs?api-version=${this.apiVersion}`);
|
|
1067
922
|
return {
|
|
1068
923
|
buildId,
|
|
@@ -1078,917 +933,469 @@ export class AzureDevOpsService {
|
|
|
1078
933
|
}))
|
|
1079
934
|
};
|
|
1080
935
|
}
|
|
1081
|
-
/**
|
|
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
936
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1252
|
-
//
|
|
937
|
+
// PULL REQUEST OPERATIONS
|
|
1253
938
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1254
939
|
/**
|
|
1255
|
-
* List all
|
|
940
|
+
* List all Git repositories in a project
|
|
1256
941
|
* @param project The project name
|
|
1257
|
-
* @returns List of
|
|
942
|
+
* @returns List of repositories with their IDs
|
|
1258
943
|
*/
|
|
1259
|
-
async
|
|
944
|
+
async listRepositories(project) {
|
|
1260
945
|
this.validateProject(project);
|
|
1261
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
946
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories?api-version=${this.apiVersion}`);
|
|
1262
947
|
return {
|
|
1263
948
|
project,
|
|
1264
949
|
totalCount: response.value.length,
|
|
1265
|
-
|
|
1266
|
-
id:
|
|
1267
|
-
name:
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|
|
950
|
+
repositories: response.value.map((repo) => ({
|
|
951
|
+
id: repo.id,
|
|
952
|
+
name: repo.name,
|
|
953
|
+
url: repo.url,
|
|
954
|
+
defaultBranch: repo.defaultBranch,
|
|
955
|
+
size: repo.size,
|
|
956
|
+
remoteUrl: repo.remoteUrl,
|
|
957
|
+
webUrl: repo.webUrl
|
|
1289
958
|
}))
|
|
1290
959
|
};
|
|
1291
960
|
}
|
|
1292
961
|
/**
|
|
1293
|
-
*
|
|
962
|
+
* List pull requests in a repository
|
|
1294
963
|
* @param project The project name
|
|
1295
|
-
* @param
|
|
1296
|
-
* @
|
|
1297
|
-
|
|
1298
|
-
|
|
964
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
965
|
+
* @param status Filter by status: active, completed, abandoned, all (default: active)
|
|
966
|
+
* @param top Maximum results (default: 25)
|
|
967
|
+
* @param creatorId Filter by creator ID
|
|
968
|
+
* @param reviewerId Filter by reviewer ID
|
|
969
|
+
* @returns List of pull requests
|
|
970
|
+
*/
|
|
971
|
+
async listPullRequests(project, repositoryId, status = 'active', top = 25, creatorId, reviewerId) {
|
|
1299
972
|
this.validateProject(project);
|
|
1300
|
-
|
|
973
|
+
let url = `${project}/_apis/git/repositories/${repositoryId}/pullrequests?searchCriteria.status=${status}&$top=${top}&api-version=${this.apiVersion}`;
|
|
974
|
+
if (creatorId)
|
|
975
|
+
url += `&searchCriteria.creatorId=${creatorId}`;
|
|
976
|
+
if (reviewerId)
|
|
977
|
+
url += `&searchCriteria.reviewerId=${reviewerId}`;
|
|
978
|
+
const response = await this.makeRequest(url);
|
|
1301
979
|
return {
|
|
1302
|
-
id: response.id,
|
|
1303
|
-
name: response.name,
|
|
1304
|
-
type: response.type,
|
|
1305
|
-
url: response.url,
|
|
1306
|
-
description: response.description,
|
|
1307
|
-
isShared: response.isShared,
|
|
1308
|
-
isReady: response.isReady,
|
|
1309
|
-
owner: response.owner,
|
|
1310
|
-
createdBy: response.createdBy?.displayName,
|
|
1311
980
|
project,
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
-
}, {}) : {}
|
|
1325
|
-
} : 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 {
|
|
981
|
+
repositoryId,
|
|
982
|
+
status,
|
|
1337
983
|
totalCount: response.value.length,
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
description:
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
984
|
+
pullRequests: response.value.map((pr) => ({
|
|
985
|
+
pullRequestId: pr.pullRequestId,
|
|
986
|
+
title: pr.title,
|
|
987
|
+
description: pr.description ? this.truncate(pr.description, 200) : null,
|
|
988
|
+
status: pr.status,
|
|
989
|
+
createdBy: pr.createdBy?.displayName,
|
|
990
|
+
creationDate: pr.creationDate,
|
|
991
|
+
closedDate: pr.closedDate,
|
|
992
|
+
sourceBranch: pr.sourceRefName?.replace('refs/heads/', ''),
|
|
993
|
+
targetBranch: pr.targetRefName?.replace('refs/heads/', ''),
|
|
994
|
+
mergeStatus: pr.mergeStatus,
|
|
995
|
+
isDraft: pr.isDraft,
|
|
996
|
+
reviewerCount: pr.reviewers?.length || 0,
|
|
997
|
+
url: pr._links?.web?.href
|
|
1347
998
|
}))
|
|
1348
999
|
};
|
|
1349
1000
|
}
|
|
1350
1001
|
/**
|
|
1351
|
-
*
|
|
1002
|
+
* Get details of a specific pull request
|
|
1352
1003
|
* @param project The project name
|
|
1353
|
-
* @param
|
|
1354
|
-
* @param
|
|
1355
|
-
* @
|
|
1356
|
-
* @returns Created service connection
|
|
1004
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
1005
|
+
* @param pullRequestId The PR ID
|
|
1006
|
+
* @returns Pull request details with reviewers
|
|
1357
1007
|
*/
|
|
1358
|
-
async
|
|
1008
|
+
async getPullRequest(project, repositoryId, pullRequestId) {
|
|
1359
1009
|
this.validateProject(project);
|
|
1360
|
-
|
|
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);
|
|
1010
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`);
|
|
1374
1011
|
return {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
type: response.type,
|
|
1409
|
-
isReady: response.isReady,
|
|
1012
|
+
pullRequestId: response.pullRequestId,
|
|
1013
|
+
title: response.title,
|
|
1014
|
+
description: response.description,
|
|
1015
|
+
status: response.status,
|
|
1016
|
+
createdBy: {
|
|
1017
|
+
displayName: response.createdBy?.displayName,
|
|
1018
|
+
id: response.createdBy?.id,
|
|
1019
|
+
uniqueName: response.createdBy?.uniqueName
|
|
1020
|
+
},
|
|
1021
|
+
creationDate: response.creationDate,
|
|
1022
|
+
closedDate: response.closedDate,
|
|
1023
|
+
sourceBranch: response.sourceRefName?.replace('refs/heads/', ''),
|
|
1024
|
+
targetBranch: response.targetRefName?.replace('refs/heads/', ''),
|
|
1025
|
+
mergeStatus: response.mergeStatus,
|
|
1026
|
+
isDraft: response.isDraft,
|
|
1027
|
+
mergeId: response.lastMergeCommit?.commitId,
|
|
1028
|
+
sourceCommitId: response.lastMergeSourceCommit?.commitId,
|
|
1029
|
+
targetCommitId: response.lastMergeTargetCommit?.commitId,
|
|
1030
|
+
supportsIterations: response.supportsIterations,
|
|
1031
|
+
reviewers: (response.reviewers || []).map((r) => ({
|
|
1032
|
+
displayName: r.displayName,
|
|
1033
|
+
id: r.id,
|
|
1034
|
+
vote: r.vote,
|
|
1035
|
+
voteLabel: this.getVoteLabel(r.vote),
|
|
1036
|
+
isRequired: r.isRequired,
|
|
1037
|
+
hasDeclined: r.hasDeclined
|
|
1038
|
+
})),
|
|
1039
|
+
labels: response.labels?.map((l) => l.name) || [],
|
|
1040
|
+
autoComplete: response.autoCompleteSetBy ? {
|
|
1041
|
+
setBy: response.autoCompleteSetBy.displayName,
|
|
1042
|
+
mergeStrategy: response.completionOptions?.mergeStrategy
|
|
1043
|
+
} : null,
|
|
1044
|
+
url: response._links?.web?.href,
|
|
1410
1045
|
project
|
|
1411
1046
|
};
|
|
1412
1047
|
}
|
|
1413
1048
|
/**
|
|
1414
|
-
*
|
|
1415
|
-
* @param connectionId The service connection ID
|
|
1416
|
-
* @param projectIds Array of project IDs to share with
|
|
1417
|
-
* @returns Updated service connection
|
|
1049
|
+
* Convert numeric vote to label
|
|
1418
1050
|
*/
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1051
|
+
getVoteLabel(vote) {
|
|
1052
|
+
switch (vote) {
|
|
1053
|
+
case -10: return 'Rejected';
|
|
1054
|
+
case -5: return 'Waiting for author';
|
|
1055
|
+
case 0: return 'No response';
|
|
1056
|
+
case 5: return 'Approved with suggestions';
|
|
1057
|
+
case 10: return 'Approved';
|
|
1058
|
+
default: return `Unknown (${vote})`;
|
|
1422
1059
|
}
|
|
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
1060
|
}
|
|
1436
1061
|
/**
|
|
1437
|
-
*
|
|
1062
|
+
* Get commits in a pull request
|
|
1438
1063
|
* @param project The project name
|
|
1439
|
-
* @param
|
|
1440
|
-
* @
|
|
1064
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
1065
|
+
* @param pullRequestId The PR ID
|
|
1066
|
+
* @returns List of commits
|
|
1441
1067
|
*/
|
|
1442
|
-
async
|
|
1068
|
+
async getPullRequestCommits(project, repositoryId, pullRequestId) {
|
|
1443
1069
|
this.validateProject(project);
|
|
1444
|
-
|
|
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');
|
|
1070
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/commits?api-version=${this.apiVersion}`);
|
|
1448
1071
|
return {
|
|
1449
|
-
|
|
1072
|
+
pullRequestId,
|
|
1450
1073
|
project,
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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,
|
|
1512
|
-
project
|
|
1074
|
+
totalCount: response.value.length,
|
|
1075
|
+
commits: response.value.map((c) => ({
|
|
1076
|
+
commitId: c.commitId,
|
|
1077
|
+
comment: c.comment,
|
|
1078
|
+
author: {
|
|
1079
|
+
name: c.author?.name,
|
|
1080
|
+
email: c.author?.email,
|
|
1081
|
+
date: c.author?.date
|
|
1082
|
+
},
|
|
1083
|
+
committer: {
|
|
1084
|
+
name: c.committer?.name,
|
|
1085
|
+
date: c.committer?.date
|
|
1086
|
+
},
|
|
1087
|
+
url: c.url
|
|
1088
|
+
}))
|
|
1513
1089
|
};
|
|
1514
1090
|
}
|
|
1515
1091
|
/**
|
|
1516
|
-
*
|
|
1092
|
+
* Get threads/comments on a pull request
|
|
1517
1093
|
* @param project The project name
|
|
1518
|
-
* @param
|
|
1519
|
-
* @param
|
|
1520
|
-
* @
|
|
1521
|
-
* @param isSecret Whether the variable is secret
|
|
1522
|
-
* @returns Updated variable group
|
|
1094
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
1095
|
+
* @param pullRequestId The PR ID
|
|
1096
|
+
* @returns List of discussion threads
|
|
1523
1097
|
*/
|
|
1524
|
-
async
|
|
1098
|
+
async getPullRequestThreads(project, repositoryId, pullRequestId) {
|
|
1525
1099
|
this.validateProject(project);
|
|
1526
|
-
|
|
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);
|
|
1100
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=${this.apiVersion}`);
|
|
1534
1101
|
return {
|
|
1535
|
-
|
|
1536
|
-
name: response.name,
|
|
1102
|
+
pullRequestId,
|
|
1537
1103
|
project,
|
|
1538
|
-
|
|
1539
|
-
|
|
1104
|
+
totalCount: response.value.length,
|
|
1105
|
+
threads: response.value.map((t) => ({
|
|
1106
|
+
id: t.id,
|
|
1107
|
+
status: t.status,
|
|
1108
|
+
publishedDate: t.publishedDate,
|
|
1109
|
+
lastUpdatedDate: t.lastUpdatedDate,
|
|
1110
|
+
isDeleted: t.isDeleted,
|
|
1111
|
+
threadContext: t.threadContext ? {
|
|
1112
|
+
filePath: t.threadContext.filePath,
|
|
1113
|
+
rightFileStart: t.threadContext.rightFileStart,
|
|
1114
|
+
rightFileEnd: t.threadContext.rightFileEnd
|
|
1115
|
+
} : null,
|
|
1116
|
+
comments: (t.comments || []).filter((c) => !c.isDeleted).map((c) => ({
|
|
1117
|
+
id: c.id,
|
|
1118
|
+
author: c.author?.displayName,
|
|
1119
|
+
content: c.content,
|
|
1120
|
+
publishedDate: c.publishedDate,
|
|
1121
|
+
commentType: c.commentType,
|
|
1122
|
+
parentCommentId: c.parentCommentId
|
|
1123
|
+
}))
|
|
1124
|
+
}))
|
|
1540
1125
|
};
|
|
1541
1126
|
}
|
|
1542
1127
|
/**
|
|
1543
|
-
*
|
|
1128
|
+
* Get file changes in a pull request
|
|
1544
1129
|
* @param project The project name
|
|
1545
|
-
* @param
|
|
1546
|
-
* @param
|
|
1547
|
-
* @
|
|
1130
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
1131
|
+
* @param pullRequestId The PR ID
|
|
1132
|
+
* @param iterationId Iteration ID (default: latest)
|
|
1133
|
+
* @returns List of changed files
|
|
1548
1134
|
*/
|
|
1549
|
-
async
|
|
1135
|
+
async getPullRequestChanges(project, repositoryId, pullRequestId, iterationId) {
|
|
1550
1136
|
this.validateProject(project);
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
throw new Error(`Variable '${variableName}' not found in group ${groupId}`);
|
|
1137
|
+
// If no iteration specified, get the latest
|
|
1138
|
+
let targetIteration = iterationId;
|
|
1139
|
+
if (!targetIteration) {
|
|
1140
|
+
const iterations = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/iterations?api-version=${this.apiVersion}`);
|
|
1141
|
+
if (iterations.value.length > 0) {
|
|
1142
|
+
targetIteration = iterations.value[iterations.value.length - 1].id;
|
|
1143
|
+
}
|
|
1144
|
+
else {
|
|
1145
|
+
throw new Error('No iterations found for this pull request');
|
|
1146
|
+
}
|
|
1562
1147
|
}
|
|
1563
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
1148
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/iterations/${targetIteration}/changes?api-version=${this.apiVersion}`);
|
|
1564
1149
|
return {
|
|
1565
|
-
|
|
1566
|
-
|
|
1150
|
+
pullRequestId,
|
|
1151
|
+
iterationId: targetIteration,
|
|
1567
1152
|
project,
|
|
1568
|
-
|
|
1153
|
+
totalCount: response.changeEntries?.length || 0,
|
|
1154
|
+
changes: (response.changeEntries || []).map((c) => ({
|
|
1155
|
+
changeType: c.changeType,
|
|
1156
|
+
path: c.item?.path,
|
|
1157
|
+
originalPath: c.originalPath,
|
|
1158
|
+
objectId: c.item?.objectId,
|
|
1159
|
+
originalObjectId: c.item?.originalObjectId
|
|
1160
|
+
}))
|
|
1569
1161
|
};
|
|
1570
1162
|
}
|
|
1571
1163
|
/**
|
|
1572
|
-
*
|
|
1164
|
+
* Add a comment thread to a pull request
|
|
1573
1165
|
* @param project The project name
|
|
1574
|
-
* @param
|
|
1575
|
-
* @
|
|
1576
|
-
|
|
1577
|
-
|
|
1166
|
+
* @param repositoryId Repository ID (GUID) or name
|
|
1167
|
+
* @param pullRequestId The PR ID
|
|
1168
|
+
* @param content Comment content (markdown supported)
|
|
1169
|
+
* @param filePath Optional file path for inline comment
|
|
1170
|
+
* @param lineNumber Optional line number (right side) for inline comment
|
|
1171
|
+
* @param status Thread status: active, fixed, wontFix, closed, byDesign, pending (default: active)
|
|
1172
|
+
* @returns Created thread
|
|
1173
|
+
*/
|
|
1174
|
+
async addPullRequestThread(project, repositoryId, pullRequestId, content, filePath, lineNumber, status = 'active') {
|
|
1578
1175
|
this.validateProject(project);
|
|
1579
|
-
if (!this.config.
|
|
1580
|
-
throw new Error('
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
if (poolType) {
|
|
1600
|
-
url += `&poolType=${poolType}`;
|
|
1176
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1177
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1178
|
+
}
|
|
1179
|
+
const threadData = {
|
|
1180
|
+
comments: [
|
|
1181
|
+
{
|
|
1182
|
+
parentCommentId: 0,
|
|
1183
|
+
content: content,
|
|
1184
|
+
commentType: 1 // 1 = text
|
|
1185
|
+
}
|
|
1186
|
+
],
|
|
1187
|
+
status: status
|
|
1188
|
+
};
|
|
1189
|
+
// Add file context for inline comments
|
|
1190
|
+
if (filePath && lineNumber) {
|
|
1191
|
+
threadData.threadContext = {
|
|
1192
|
+
filePath: filePath.startsWith('/') ? filePath : `/${filePath}`,
|
|
1193
|
+
rightFileStart: { line: lineNumber, offset: 1 },
|
|
1194
|
+
rightFileEnd: { line: lineNumber, offset: 1 }
|
|
1195
|
+
};
|
|
1601
1196
|
}
|
|
1602
|
-
const response = await this.makeRequest(
|
|
1197
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads?api-version=${this.apiVersion}`, 'POST', threadData);
|
|
1603
1198
|
return {
|
|
1604
|
-
|
|
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,
|
|
1199
|
+
threadId: response.id,
|
|
1684
1200
|
status: response.status,
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
* @param poolId The pool ID
|
|
1698
|
-
* @param updates Updated settings
|
|
1699
|
-
* @returns Updated agent pool
|
|
1700
|
-
*/
|
|
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.');
|
|
1704
|
-
}
|
|
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
|
|
1201
|
+
publishedDate: response.publishedDate,
|
|
1202
|
+
filePath: response.threadContext?.filePath,
|
|
1203
|
+
comments: response.comments?.map((c) => ({
|
|
1204
|
+
id: c.id,
|
|
1205
|
+
content: c.content,
|
|
1206
|
+
author: c.author?.displayName
|
|
1207
|
+
})),
|
|
1208
|
+
message: filePath
|
|
1209
|
+
? `Inline comment added to ${filePath} at line ${lineNumber}`
|
|
1210
|
+
: 'Comment thread created',
|
|
1211
|
+
pullRequestId,
|
|
1212
|
+
project
|
|
1713
1213
|
};
|
|
1714
1214
|
}
|
|
1715
1215
|
/**
|
|
1716
|
-
*
|
|
1717
|
-
* @param poolId The pool ID
|
|
1718
|
-
* @param agentId The agent ID
|
|
1719
|
-
* @returns Updated agent
|
|
1216
|
+
* Create a new pull request
|
|
1720
1217
|
*/
|
|
1721
|
-
async
|
|
1722
|
-
|
|
1723
|
-
|
|
1218
|
+
async createPullRequest(project, repositoryId, sourceRefName, targetRefName, title, description, reviewerIds, isDraft) {
|
|
1219
|
+
this.validateProject(project);
|
|
1220
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1221
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1724
1222
|
}
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1223
|
+
const body = {
|
|
1224
|
+
sourceRefName,
|
|
1225
|
+
targetRefName,
|
|
1226
|
+
title,
|
|
1227
|
+
description: description || '',
|
|
1228
|
+
isDraft: isDraft || false,
|
|
1731
1229
|
};
|
|
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.');
|
|
1230
|
+
if (reviewerIds && reviewerIds.length > 0) {
|
|
1231
|
+
body.reviewers = reviewerIds.map(id => ({ id }));
|
|
1742
1232
|
}
|
|
1743
|
-
const response = await this.makeRequest(
|
|
1233
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests?api-version=${this.apiVersion}`, 'POST', body, false, { 'Content-Type': 'application/json' });
|
|
1744
1234
|
return {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
enabled: response.enabled,
|
|
1235
|
+
pullRequestId: response.pullRequestId,
|
|
1236
|
+
title: response.title,
|
|
1748
1237
|
status: response.status,
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1753
|
-
// ENVIRONMENT OPERATIONS (DevOps Admin Tools)
|
|
1754
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1755
|
-
/**
|
|
1756
|
-
* List all environments in a project
|
|
1757
|
-
* @param project The project name
|
|
1758
|
-
* @returns List of environments
|
|
1759
|
-
*/
|
|
1760
|
-
async listEnvironments(project) {
|
|
1761
|
-
this.validateProject(project);
|
|
1762
|
-
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments?api-version=${this.apiVersion}`);
|
|
1763
|
-
return {
|
|
1764
|
-
project,
|
|
1765
|
-
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
|
|
1774
|
-
}))
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1777
|
-
/**
|
|
1778
|
-
* Get a specific environment
|
|
1779
|
-
* @param project The project name
|
|
1780
|
-
* @param environmentId The environment ID
|
|
1781
|
-
* @returns Environment details
|
|
1782
|
-
*/
|
|
1783
|
-
async getEnvironment(project, environmentId) {
|
|
1784
|
-
this.validateProject(project);
|
|
1785
|
-
const response = await this.makeRequest(`${project}/_apis/distributedtask/environments/${environmentId}?expands=resourceReferences&api-version=${this.apiVersion}`);
|
|
1786
|
-
return {
|
|
1787
|
-
id: response.id,
|
|
1788
|
-
name: response.name,
|
|
1789
|
-
description: response.description,
|
|
1238
|
+
isDraft: response.isDraft,
|
|
1239
|
+
sourceBranch: response.sourceRefName?.replace('refs/heads/', ''),
|
|
1240
|
+
targetBranch: response.targetRefName?.replace('refs/heads/', ''),
|
|
1790
1241
|
createdBy: response.createdBy?.displayName,
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
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
|
-
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
|
|
1818
|
-
} : 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
|
|
1825
|
-
}))
|
|
1242
|
+
creationDate: response.creationDate,
|
|
1243
|
+
url: response.url,
|
|
1244
|
+
project
|
|
1826
1245
|
};
|
|
1827
1246
|
}
|
|
1828
1247
|
/**
|
|
1829
|
-
*
|
|
1830
|
-
* @param project The project name
|
|
1831
|
-
* @param name Environment name
|
|
1832
|
-
* @param description Optional description
|
|
1833
|
-
* @returns Created environment
|
|
1248
|
+
* Update an existing pull request (title, description, status, draft)
|
|
1834
1249
|
*/
|
|
1835
|
-
async
|
|
1250
|
+
async updatePullRequest(project, repositoryId, pullRequestId, updates) {
|
|
1836
1251
|
this.validateProject(project);
|
|
1837
|
-
if (!this.config.
|
|
1838
|
-
throw new Error('
|
|
1839
|
-
}
|
|
1840
|
-
const
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1252
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1253
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1254
|
+
}
|
|
1255
|
+
const body = {};
|
|
1256
|
+
if (updates.title !== undefined)
|
|
1257
|
+
body.title = updates.title;
|
|
1258
|
+
if (updates.description !== undefined)
|
|
1259
|
+
body.description = updates.description;
|
|
1260
|
+
if (updates.status !== undefined)
|
|
1261
|
+
body.status = updates.status;
|
|
1262
|
+
if (updates.isDraft !== undefined)
|
|
1263
|
+
body.isDraft = updates.isDraft;
|
|
1264
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`, 'PATCH', body, false, { 'Content-Type': 'application/json' });
|
|
1845
1265
|
return {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
description: response.description,
|
|
1266
|
+
pullRequestId: response.pullRequestId,
|
|
1267
|
+
title: response.title,
|
|
1268
|
+
description: response.description ? this.truncate(response.description, 200) : null,
|
|
1269
|
+
status: response.status,
|
|
1270
|
+
isDraft: response.isDraft,
|
|
1849
1271
|
project
|
|
1850
1272
|
};
|
|
1851
1273
|
}
|
|
1852
1274
|
/**
|
|
1853
|
-
*
|
|
1854
|
-
* @param project The project name
|
|
1855
|
-
* @param environmentId The environment ID
|
|
1856
|
-
* @param updates Updated properties
|
|
1857
|
-
* @returns Updated environment
|
|
1275
|
+
* Complete (merge) a pull request
|
|
1858
1276
|
*/
|
|
1859
|
-
async
|
|
1277
|
+
async completePullRequest(project, repositoryId, pullRequestId, mergeStrategy = 'squash', deleteSourceBranch = true, transitionWorkItems = true, mergeCommitMessage) {
|
|
1860
1278
|
this.validateProject(project);
|
|
1861
|
-
if (!this.config.
|
|
1862
|
-
throw new Error('
|
|
1279
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1280
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1281
|
+
}
|
|
1282
|
+
// Fetch current PR to get lastMergeSourceCommit
|
|
1283
|
+
const currentPr = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`);
|
|
1284
|
+
const mergeStrategyMap = {
|
|
1285
|
+
noFastForward: 1,
|
|
1286
|
+
squash: 2,
|
|
1287
|
+
rebase: 3,
|
|
1288
|
+
rebaseMerge: 4,
|
|
1289
|
+
};
|
|
1290
|
+
const body = {
|
|
1291
|
+
status: 'completed',
|
|
1292
|
+
lastMergeSourceCommit: currentPr.lastMergeSourceCommit,
|
|
1293
|
+
completionOptions: {
|
|
1294
|
+
mergeStrategy: mergeStrategyMap[mergeStrategy],
|
|
1295
|
+
deleteSourceBranch,
|
|
1296
|
+
transitionWorkItems,
|
|
1297
|
+
},
|
|
1298
|
+
};
|
|
1299
|
+
if (mergeCommitMessage) {
|
|
1300
|
+
body.completionOptions.mergeCommitMessage = mergeCommitMessage;
|
|
1863
1301
|
}
|
|
1864
|
-
const response = await this.makeRequest(`${project}/_apis/
|
|
1302
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}?api-version=${this.apiVersion}`, 'PATCH', body, false, { 'Content-Type': 'application/json' });
|
|
1865
1303
|
return {
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1304
|
+
pullRequestId: response.pullRequestId,
|
|
1305
|
+
title: response.title,
|
|
1306
|
+
status: response.status,
|
|
1307
|
+
mergeStrategy,
|
|
1308
|
+
closedDate: response.closedDate,
|
|
1869
1309
|
project
|
|
1870
1310
|
};
|
|
1871
1311
|
}
|
|
1872
1312
|
/**
|
|
1873
|
-
*
|
|
1874
|
-
* @param project The project name
|
|
1875
|
-
* @param environmentId The environment ID
|
|
1876
|
-
* @returns Deletion confirmation
|
|
1313
|
+
* Add or remove a reviewer from a pull request
|
|
1877
1314
|
*/
|
|
1878
|
-
async
|
|
1315
|
+
async addOrRemovePrReviewer(project, repositoryId, pullRequestId, reviewerId, isRequired, remove) {
|
|
1879
1316
|
this.validateProject(project);
|
|
1880
|
-
if (!this.config.
|
|
1881
|
-
throw new Error('
|
|
1317
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1318
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1882
1319
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
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
|
|
1914
|
-
}))
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
/**
|
|
1918
|
-
* Add a check to an environment
|
|
1919
|
-
* @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
|
|
1924
|
-
*/
|
|
1925
|
-
async addEnvironmentCheck(project, environmentId, checkType, configuration) {
|
|
1926
|
-
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.');
|
|
1320
|
+
const url = `${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/reviewers/${reviewerId}?api-version=${this.apiVersion}`;
|
|
1321
|
+
if (remove) {
|
|
1322
|
+
await this.makeRequest(url, 'DELETE');
|
|
1323
|
+
return { pullRequestId, reviewerId, action: 'removed', project };
|
|
1929
1324
|
}
|
|
1930
|
-
const
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
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);
|
|
1325
|
+
const body = { id: reviewerId };
|
|
1326
|
+
if (isRequired !== undefined) {
|
|
1327
|
+
body.isRequired = isRequired;
|
|
1328
|
+
}
|
|
1329
|
+
const response = await this.makeRequest(url, 'PUT', body, false, { 'Content-Type': 'application/json' });
|
|
1939
1330
|
return {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1331
|
+
pullRequestId,
|
|
1332
|
+
reviewerId: response.id,
|
|
1333
|
+
displayName: response.displayName,
|
|
1334
|
+
isRequired: response.isRequired,
|
|
1335
|
+
vote: response.vote,
|
|
1336
|
+
action: 'added',
|
|
1943
1337
|
project
|
|
1944
1338
|
};
|
|
1945
1339
|
}
|
|
1946
1340
|
/**
|
|
1947
|
-
*
|
|
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
|
|
1341
|
+
* Submit a vote on a pull request
|
|
1952
1342
|
*/
|
|
1953
|
-
async
|
|
1343
|
+
async votePullRequest(project, repositoryId, pullRequestId, vote, reviewerId) {
|
|
1954
1344
|
this.validateProject(project);
|
|
1955
|
-
if (!this.config.
|
|
1956
|
-
throw new Error('
|
|
1345
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1346
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1347
|
+
}
|
|
1348
|
+
const voteMap = {
|
|
1349
|
+
approve: 10,
|
|
1350
|
+
approveWithSuggestions: 5,
|
|
1351
|
+
noResponse: 0,
|
|
1352
|
+
waitForAuthor: -5,
|
|
1353
|
+
reject: -10,
|
|
1354
|
+
};
|
|
1355
|
+
// If no reviewerId, resolve authenticated user
|
|
1356
|
+
let userId = reviewerId;
|
|
1357
|
+
if (!userId) {
|
|
1358
|
+
const connectionData = await this.makeRequest('_apis/connectionData');
|
|
1359
|
+
userId = connectionData.authenticatedUser?.id;
|
|
1360
|
+
if (!userId) {
|
|
1361
|
+
throw new Error('Could not resolve authenticated user ID. Provide reviewerId explicitly.');
|
|
1362
|
+
}
|
|
1957
1363
|
}
|
|
1958
|
-
|
|
1959
|
-
const
|
|
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);
|
|
1364
|
+
const body = { vote: voteMap[vote] };
|
|
1365
|
+
const response = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/reviewers/${userId}?api-version=${this.apiVersion}`, 'PUT', body, false, { 'Content-Type': 'application/json' });
|
|
1967
1366
|
return {
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1367
|
+
pullRequestId,
|
|
1368
|
+
reviewerId: response.id,
|
|
1369
|
+
displayName: response.displayName,
|
|
1370
|
+
vote: response.vote,
|
|
1371
|
+
voteLabel: vote,
|
|
1972
1372
|
project
|
|
1973
1373
|
};
|
|
1974
1374
|
}
|
|
1975
1375
|
/**
|
|
1976
|
-
*
|
|
1977
|
-
* @param project The project name
|
|
1978
|
-
* @param checkId The check configuration ID
|
|
1979
|
-
* @returns Deletion confirmation
|
|
1376
|
+
* Reply to a PR thread and optionally update thread status
|
|
1980
1377
|
*/
|
|
1981
|
-
async
|
|
1378
|
+
async replyToPrThread(project, repositoryId, pullRequestId, threadId, content, status) {
|
|
1982
1379
|
this.validateProject(project);
|
|
1983
|
-
if (!this.config.
|
|
1984
|
-
throw new Error('
|
|
1380
|
+
if (!this.config.enablePullRequestWrite) {
|
|
1381
|
+
throw new Error('Pull request write operations are disabled. Set AZUREDEVOPS_ENABLE_PR_WRITE=true to enable.');
|
|
1382
|
+
}
|
|
1383
|
+
const results = { pullRequestId, threadId, project };
|
|
1384
|
+
// Add reply comment if content provided
|
|
1385
|
+
if (content) {
|
|
1386
|
+
const commentResponse = await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads/${threadId}/comments?api-version=${this.apiVersion}`, 'POST', { content, parentCommentId: 0, commentType: 1 }, false, { 'Content-Type': 'application/json' });
|
|
1387
|
+
results.comment = {
|
|
1388
|
+
id: commentResponse.id,
|
|
1389
|
+
content: commentResponse.content,
|
|
1390
|
+
author: commentResponse.author?.displayName,
|
|
1391
|
+
};
|
|
1985
1392
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1393
|
+
// Update thread status if provided
|
|
1394
|
+
if (status) {
|
|
1395
|
+
await this.makeRequest(`${project}/_apis/git/repositories/${repositoryId}/pullrequests/${pullRequestId}/threads/${threadId}?api-version=${this.apiVersion}`, 'PATCH', { status }, false, { 'Content-Type': 'application/json' });
|
|
1396
|
+
results.status = status;
|
|
1397
|
+
}
|
|
1398
|
+
return results;
|
|
1992
1399
|
}
|
|
1993
1400
|
}
|
|
1994
1401
|
//# sourceMappingURL=AzureDevOpsService.js.map
|