@daghis/teamcity-mcp 1.10.0 → 1.10.2
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/index.js +492 -25
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.2](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.1...teamcity-mcp-v1.10.2) (2025-09-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **tools:** clone_build_config uses manager (215) ([b84d1f8](https://github.com/Daghis/teamcity-mcp/commit/b84d1f80a4233783a93dd1e3ede9a83a7cf57171))
|
|
9
|
+
* **tools:** clone_build_config uses manager (215) ([c4cd959](https://github.com/Daghis/teamcity-mcp/commit/c4cd959a9f35052bf95386162316a9ace5599eb6))
|
|
10
|
+
|
|
11
|
+
## [1.10.1](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.0...teamcity-mcp-v1.10.1) (2025-09-27)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **tools:** support get_build_status buildNumber (209) ([#219](https://github.com/Daghis/teamcity-mcp/issues/219)) ([efb9a00](https://github.com/Daghis/teamcity-mcp/commit/efb9a00ad697335239e7cd87c9436259df27a49c))
|
|
17
|
+
|
|
3
18
|
## [1.10.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.9.6...teamcity-mcp-v1.10.0) (2025-09-27)
|
|
4
19
|
|
|
5
20
|
|
package/dist/index.js
CHANGED
|
@@ -261,17 +261,11 @@ var init_build_status_manager = __esm({
|
|
|
261
261
|
buildData = response.data;
|
|
262
262
|
} else {
|
|
263
263
|
const locator = this.buildLocator(options);
|
|
264
|
-
const response = await this.client.builds.
|
|
264
|
+
const response = await this.client.builds.getBuild(
|
|
265
265
|
locator,
|
|
266
266
|
this.getFieldSelection(options)
|
|
267
267
|
);
|
|
268
|
-
|
|
269
|
-
if (!Array.isArray(data.build) || data.build.length === 0) {
|
|
270
|
-
throw new BuildNotFoundError(
|
|
271
|
-
`No build found for number ${options.buildNumber} in ${options.buildTypeId}`
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
buildData = data.build[0];
|
|
268
|
+
buildData = response.data;
|
|
275
269
|
}
|
|
276
270
|
if (buildData == null) {
|
|
277
271
|
throw new BuildNotFoundError("Build data is undefined");
|
|
@@ -1522,6 +1516,384 @@ var ArtifactManager = class _ArtifactManager {
|
|
|
1522
1516
|
}
|
|
1523
1517
|
};
|
|
1524
1518
|
|
|
1519
|
+
// src/teamcity/types/api-responses.ts
|
|
1520
|
+
function isBuildTypeData(data) {
|
|
1521
|
+
return typeof data === "object" && data !== null && ("id" in data || "name" in data || "projectId" in data);
|
|
1522
|
+
}
|
|
1523
|
+
function isVcsRootsResponse(data) {
|
|
1524
|
+
return typeof data === "object" && data !== null && "vcs-root" in data;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// src/teamcity/build-configuration-clone-manager.ts
|
|
1528
|
+
var BuildConfigurationCloneManager = class {
|
|
1529
|
+
client;
|
|
1530
|
+
constructor(client) {
|
|
1531
|
+
this.client = client;
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Retrieve complete build configuration from TeamCity
|
|
1535
|
+
*/
|
|
1536
|
+
async retrieveConfiguration(configId) {
|
|
1537
|
+
try {
|
|
1538
|
+
const response = await this.client.modules.buildTypes.getBuildType(
|
|
1539
|
+
configId,
|
|
1540
|
+
"$long,steps($long),triggers($long),features($long),artifact-dependencies($long),snapshot-dependencies($long),parameters($long),vcs-root-entries($long)"
|
|
1541
|
+
);
|
|
1542
|
+
if (response.data == null || !isBuildTypeData(response.data)) {
|
|
1543
|
+
return null;
|
|
1544
|
+
}
|
|
1545
|
+
const config2 = response.data;
|
|
1546
|
+
let vcsRootId;
|
|
1547
|
+
const vcsRootEntries = config2["vcs-root-entries"];
|
|
1548
|
+
if (vcsRootEntries?.["vcs-root-entry"] && vcsRootEntries["vcs-root-entry"].length > 0) {
|
|
1549
|
+
const firstEntry = vcsRootEntries["vcs-root-entry"][0];
|
|
1550
|
+
if (firstEntry?.["vcs-root"]?.id) {
|
|
1551
|
+
vcsRootId = firstEntry["vcs-root"].id;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
const parameters = {};
|
|
1555
|
+
if (config2.parameters?.property) {
|
|
1556
|
+
for (const param of config2.parameters.property) {
|
|
1557
|
+
if (param.name && param.value) {
|
|
1558
|
+
parameters[param.name] = param.value;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
const cfgId = config2.id;
|
|
1563
|
+
const cfgName = config2.name;
|
|
1564
|
+
if (!cfgId || !cfgName) {
|
|
1565
|
+
throw new Error("Source configuration missing id or name");
|
|
1566
|
+
}
|
|
1567
|
+
return {
|
|
1568
|
+
id: cfgId,
|
|
1569
|
+
name: cfgName,
|
|
1570
|
+
projectId: config2.projectId ?? config2.project?.id ?? "",
|
|
1571
|
+
description: config2.description,
|
|
1572
|
+
vcsRootId,
|
|
1573
|
+
parameters,
|
|
1574
|
+
templateId: config2.templates?.buildType?.[0]?.id,
|
|
1575
|
+
steps: config2.steps?.step,
|
|
1576
|
+
triggers: config2.triggers?.trigger,
|
|
1577
|
+
features: config2.features?.feature,
|
|
1578
|
+
artifactDependencies: config2["artifact-dependencies"]?.["artifact-dependency"],
|
|
1579
|
+
snapshotDependencies: config2["snapshot-dependencies"]?.["snapshot-dependency"],
|
|
1580
|
+
buildNumberCounter: (() => {
|
|
1581
|
+
const counterProp = config2.settings?.property?.find(
|
|
1582
|
+
(p) => p.name === "buildNumberCounter"
|
|
1583
|
+
);
|
|
1584
|
+
return counterProp?.value ? parseInt(counterProp.value, 10) : void 0;
|
|
1585
|
+
})(),
|
|
1586
|
+
buildNumberFormat: config2.settings?.property?.find(
|
|
1587
|
+
(p) => p.name === "buildNumberPattern"
|
|
1588
|
+
)?.value
|
|
1589
|
+
};
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
const axiosError = err;
|
|
1592
|
+
if (axiosError.response?.status === 404) {
|
|
1593
|
+
debug("Build configuration not found", { configId });
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
if (axiosError.response?.status === 403) {
|
|
1597
|
+
throw new Error("Permission denied: No access to source configuration");
|
|
1598
|
+
}
|
|
1599
|
+
throw err;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
/**
|
|
1603
|
+
* Validate target project exists and user has permissions
|
|
1604
|
+
*/
|
|
1605
|
+
async validateTargetProject(projectId) {
|
|
1606
|
+
try {
|
|
1607
|
+
const response = await this.client.modules.projects.getProject(projectId, "$short");
|
|
1608
|
+
const id = response.data?.id;
|
|
1609
|
+
const name = response.data?.name;
|
|
1610
|
+
if (id && name) {
|
|
1611
|
+
return { id, name };
|
|
1612
|
+
}
|
|
1613
|
+
return null;
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
const axiosError = err;
|
|
1616
|
+
if (axiosError.response?.status === 404) {
|
|
1617
|
+
debug("Target project not found", { projectId });
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1620
|
+
if (axiosError.response?.status === 403) {
|
|
1621
|
+
debug("No permission to access target project", { projectId });
|
|
1622
|
+
return null;
|
|
1623
|
+
}
|
|
1624
|
+
throw err;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Handle VCS root cloning or reuse
|
|
1629
|
+
*/
|
|
1630
|
+
async handleVcsRoot(vcsRootId, handling, targetProjectId) {
|
|
1631
|
+
if (handling === "reuse") {
|
|
1632
|
+
return { id: vcsRootId, name: "Reused VCS Root" };
|
|
1633
|
+
}
|
|
1634
|
+
try {
|
|
1635
|
+
const vcsRootsResponse = await this.client.modules.vcsRoots.getAllVcsRoots(
|
|
1636
|
+
`id:${vcsRootId}`,
|
|
1637
|
+
"$long,vcsRoot($long,properties($long))"
|
|
1638
|
+
);
|
|
1639
|
+
if (vcsRootsResponse.data == null || !isVcsRootsResponse(vcsRootsResponse.data)) {
|
|
1640
|
+
throw new Error("Invalid VCS root response");
|
|
1641
|
+
}
|
|
1642
|
+
const vcsRoots = vcsRootsResponse.data["vcs-root"] ?? [];
|
|
1643
|
+
if (vcsRoots.length === 0) {
|
|
1644
|
+
throw new Error("VCS root not found");
|
|
1645
|
+
}
|
|
1646
|
+
const sourceVcsRoot = vcsRoots[0];
|
|
1647
|
+
if (sourceVcsRoot == null) {
|
|
1648
|
+
throw new Error("VCS root data is invalid");
|
|
1649
|
+
}
|
|
1650
|
+
const clonedVcsRootName = `${sourceVcsRoot.name}_Clone_${Date.now()}`;
|
|
1651
|
+
const clonedVcsRoot = {
|
|
1652
|
+
name: clonedVcsRootName,
|
|
1653
|
+
vcsName: sourceVcsRoot.vcsName,
|
|
1654
|
+
project: {
|
|
1655
|
+
id: targetProjectId
|
|
1656
|
+
},
|
|
1657
|
+
properties: sourceVcsRoot.properties
|
|
1658
|
+
};
|
|
1659
|
+
const createResponse = await this.client.modules.vcsRoots.addVcsRoot(
|
|
1660
|
+
void 0,
|
|
1661
|
+
clonedVcsRoot
|
|
1662
|
+
);
|
|
1663
|
+
const newId = createResponse.data.id;
|
|
1664
|
+
const newName = createResponse.data.name;
|
|
1665
|
+
if (!newId || !newName) {
|
|
1666
|
+
throw new Error("Failed to obtain cloned VCS root id/name");
|
|
1667
|
+
}
|
|
1668
|
+
return { id: newId, name: newName };
|
|
1669
|
+
} catch (err) {
|
|
1670
|
+
error("Failed to clone VCS root", err);
|
|
1671
|
+
throw new Error(`Failed to clone VCS root: ${err.message}`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Apply parameter overrides to configuration
|
|
1676
|
+
*/
|
|
1677
|
+
async applyParameterOverrides(sourceParameters, overrides) {
|
|
1678
|
+
const mergedParameters = { ...sourceParameters };
|
|
1679
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1680
|
+
if (!this.isValidParameterName(key)) {
|
|
1681
|
+
throw new Error(`Invalid parameter name: ${key}`);
|
|
1682
|
+
}
|
|
1683
|
+
mergedParameters[key] = value;
|
|
1684
|
+
}
|
|
1685
|
+
return mergedParameters;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Clone the build configuration
|
|
1689
|
+
*/
|
|
1690
|
+
async cloneConfiguration(source, options) {
|
|
1691
|
+
const configId = options.id ?? this.generateBuildConfigId(options.targetProjectId, options.name);
|
|
1692
|
+
const configPayload = {
|
|
1693
|
+
id: configId,
|
|
1694
|
+
name: options.name,
|
|
1695
|
+
project: {
|
|
1696
|
+
id: options.targetProjectId
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
if (options.description) {
|
|
1700
|
+
configPayload.description = options.description;
|
|
1701
|
+
}
|
|
1702
|
+
if (source.templateId) {
|
|
1703
|
+
configPayload.templates = {
|
|
1704
|
+
buildType: [{ id: source.templateId }]
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
if (options.vcsRootId) {
|
|
1708
|
+
configPayload["vcs-root-entries"] = {
|
|
1709
|
+
"vcs-root-entry": [
|
|
1710
|
+
{
|
|
1711
|
+
"vcs-root": { id: options.vcsRootId },
|
|
1712
|
+
"checkout-rules": ""
|
|
1713
|
+
}
|
|
1714
|
+
]
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
if (source.steps && source.steps.length > 0) {
|
|
1718
|
+
configPayload.steps = {
|
|
1719
|
+
step: this.cloneBuildSteps(source.steps)
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
if (source.triggers && source.triggers.length > 0) {
|
|
1723
|
+
configPayload.triggers = {
|
|
1724
|
+
trigger: this.cloneTriggers(source.triggers)
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
if (source.features && source.features.length > 0) {
|
|
1728
|
+
configPayload.features = {
|
|
1729
|
+
feature: source.features.map((f) => this.deepCloneConfiguration(f))
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
if (source.artifactDependencies && source.artifactDependencies.length > 0) {
|
|
1733
|
+
configPayload["artifact-dependencies"] = {
|
|
1734
|
+
"artifact-dependency": this.updateDependencyReferences(
|
|
1735
|
+
source.artifactDependencies,
|
|
1736
|
+
source.id,
|
|
1737
|
+
configId
|
|
1738
|
+
)
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
if (source.snapshotDependencies && source.snapshotDependencies.length > 0) {
|
|
1742
|
+
configPayload["snapshot-dependencies"] = {
|
|
1743
|
+
"snapshot-dependency": this.updateDependencyReferences(
|
|
1744
|
+
source.snapshotDependencies,
|
|
1745
|
+
source.id,
|
|
1746
|
+
configId
|
|
1747
|
+
)
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
if (options.parameters && Object.keys(options.parameters).length > 0) {
|
|
1751
|
+
configPayload.parameters = {
|
|
1752
|
+
property: Object.entries(options.parameters).map(([name, value]) => ({
|
|
1753
|
+
name,
|
|
1754
|
+
value
|
|
1755
|
+
}))
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
if (options.copyBuildCounter && source.buildNumberCounter) {
|
|
1759
|
+
if (configPayload.settings == null) {
|
|
1760
|
+
configPayload.settings = { property: [] };
|
|
1761
|
+
}
|
|
1762
|
+
configPayload.settings.property?.push({
|
|
1763
|
+
name: "buildNumberCounter",
|
|
1764
|
+
value: source.buildNumberCounter.toString()
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
if (source.buildNumberFormat) {
|
|
1768
|
+
if (configPayload.settings == null) {
|
|
1769
|
+
configPayload.settings = { property: [] };
|
|
1770
|
+
}
|
|
1771
|
+
configPayload.settings.property?.push({
|
|
1772
|
+
name: "buildNumberPattern",
|
|
1773
|
+
value: source.buildNumberFormat
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
const response = await this.client.modules.buildTypes.createBuildType(
|
|
1778
|
+
void 0,
|
|
1779
|
+
this.prepareBuildTypePayload(configPayload)
|
|
1780
|
+
);
|
|
1781
|
+
const teamcityUrl = getTeamCityUrl();
|
|
1782
|
+
const id = response.data.id;
|
|
1783
|
+
const name = response.data.name;
|
|
1784
|
+
if (!id || !name) {
|
|
1785
|
+
throw new Error("Clone response missing id or name");
|
|
1786
|
+
}
|
|
1787
|
+
const result = {
|
|
1788
|
+
id,
|
|
1789
|
+
name,
|
|
1790
|
+
projectId: response.data.projectId ?? options.targetProjectId,
|
|
1791
|
+
description: response.data.description,
|
|
1792
|
+
vcsRootId: options.vcsRootId,
|
|
1793
|
+
parameters: options.parameters,
|
|
1794
|
+
url: `${teamcityUrl}/viewType.html?buildTypeId=${id}`
|
|
1795
|
+
};
|
|
1796
|
+
info("Build configuration cloned", {
|
|
1797
|
+
id: result.id,
|
|
1798
|
+
name: result.name,
|
|
1799
|
+
sourceId: source.id
|
|
1800
|
+
});
|
|
1801
|
+
return result;
|
|
1802
|
+
} catch (err) {
|
|
1803
|
+
const error2 = err;
|
|
1804
|
+
if (error2.response?.status === 409) {
|
|
1805
|
+
throw new Error(`Build configuration already exists with ID: ${configId}`);
|
|
1806
|
+
}
|
|
1807
|
+
if (error2.response?.status === 403) {
|
|
1808
|
+
throw new Error("Permission denied: You need project edit permissions");
|
|
1809
|
+
}
|
|
1810
|
+
if (error2.response?.status === 400) {
|
|
1811
|
+
const message = error2.response?.data?.message ?? "Invalid configuration";
|
|
1812
|
+
throw new Error(`Invalid configuration: ${message}`);
|
|
1813
|
+
}
|
|
1814
|
+
error(
|
|
1815
|
+
"Failed to clone build configuration",
|
|
1816
|
+
error2 instanceof Error ? error2 : new Error(String(error2))
|
|
1817
|
+
);
|
|
1818
|
+
throw error2;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Normalize cloned payload into the generated BuildType shape expected by the API
|
|
1823
|
+
*/
|
|
1824
|
+
prepareBuildTypePayload(payload) {
|
|
1825
|
+
const clone = typeof structuredClone === "function" ? structuredClone(payload) : JSON.parse(JSON.stringify(payload));
|
|
1826
|
+
if (typeof clone.id !== "string" || typeof clone.name !== "string") {
|
|
1827
|
+
throw new Error("Invalid build configuration payload: missing id or name");
|
|
1828
|
+
}
|
|
1829
|
+
if (typeof clone.project?.id !== "string") {
|
|
1830
|
+
throw new Error("Invalid build configuration payload: missing project id");
|
|
1831
|
+
}
|
|
1832
|
+
return clone;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Deep clone configuration object and remove server-generated fields
|
|
1836
|
+
*/
|
|
1837
|
+
deepCloneConfiguration(config2) {
|
|
1838
|
+
const cloned = JSON.parse(JSON.stringify(config2));
|
|
1839
|
+
delete cloned.href;
|
|
1840
|
+
delete cloned.webUrl;
|
|
1841
|
+
delete cloned.locator;
|
|
1842
|
+
delete cloned.uuid;
|
|
1843
|
+
delete cloned.links;
|
|
1844
|
+
delete cloned._links;
|
|
1845
|
+
return cloned;
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Clone build steps with new IDs
|
|
1849
|
+
*/
|
|
1850
|
+
cloneBuildSteps(steps) {
|
|
1851
|
+
return steps.map((step, index) => {
|
|
1852
|
+
const clonedStep = this.deepCloneConfiguration(step);
|
|
1853
|
+
clonedStep.id = `RUNNER_${index + 1}`;
|
|
1854
|
+
return clonedStep;
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Clone triggers with new IDs
|
|
1859
|
+
*/
|
|
1860
|
+
cloneTriggers(triggers) {
|
|
1861
|
+
return triggers.map((trigger, index) => {
|
|
1862
|
+
const clonedTrigger = this.deepCloneConfiguration(trigger);
|
|
1863
|
+
clonedTrigger.id = `TRIGGER_${index + 1}`;
|
|
1864
|
+
return clonedTrigger;
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Update internal references in dependencies
|
|
1869
|
+
*/
|
|
1870
|
+
updateDependencyReferences(dependencies, oldId, newId) {
|
|
1871
|
+
return dependencies.map((dep) => {
|
|
1872
|
+
const clonedDep = this.deepCloneConfiguration(dep);
|
|
1873
|
+
if (clonedDep.sourceBuildTypeId === oldId) {
|
|
1874
|
+
clonedDep.sourceBuildTypeId = newId;
|
|
1875
|
+
}
|
|
1876
|
+
if (clonedDep.dependsOnBuildTypeId === oldId) {
|
|
1877
|
+
clonedDep.dependsOnBuildTypeId = newId;
|
|
1878
|
+
}
|
|
1879
|
+
return clonedDep;
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Generate a unique build configuration ID
|
|
1884
|
+
*/
|
|
1885
|
+
generateBuildConfigId(projectId, name) {
|
|
1886
|
+
const cleanName = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
1887
|
+
return `${projectId}_${cleanName}`;
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* Validate parameter name according to TeamCity rules
|
|
1891
|
+
*/
|
|
1892
|
+
isValidParameterName(name) {
|
|
1893
|
+
return /^[a-zA-Z0-9._-]+$/.test(name);
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1525
1897
|
// src/teamcity/build-configuration-update-manager.ts
|
|
1526
1898
|
var ARTIFACT_RULES_SETTINGS_FIELD = "settings/artifactRules";
|
|
1527
1899
|
var ARTIFACT_RULES_LEGACY_FIELD = "artifactRules";
|
|
@@ -38262,6 +38634,14 @@ var DEV_TOOLS = [
|
|
|
38262
38634
|
type: "object",
|
|
38263
38635
|
properties: {
|
|
38264
38636
|
buildId: { type: "string", description: "Build ID" },
|
|
38637
|
+
buildNumber: {
|
|
38638
|
+
type: "string",
|
|
38639
|
+
description: "Human build number (requires buildTypeId when provided)"
|
|
38640
|
+
},
|
|
38641
|
+
buildTypeId: {
|
|
38642
|
+
type: "string",
|
|
38643
|
+
description: "Build configuration identifier (required when using buildNumber)"
|
|
38644
|
+
},
|
|
38265
38645
|
includeTests: { type: "boolean", description: "Include test summary" },
|
|
38266
38646
|
includeProblems: { type: "boolean", description: "Include build problems" },
|
|
38267
38647
|
includeQueueTotals: {
|
|
@@ -38272,16 +38652,32 @@ var DEV_TOOLS = [
|
|
|
38272
38652
|
type: "boolean",
|
|
38273
38653
|
description: "Include waitReason for the queued item (extra API call when queued)"
|
|
38274
38654
|
}
|
|
38275
|
-
}
|
|
38276
|
-
required: ["buildId"]
|
|
38655
|
+
}
|
|
38277
38656
|
},
|
|
38278
38657
|
handler: async (args) => {
|
|
38279
38658
|
const schema = import_zod4.z.object({
|
|
38280
|
-
buildId: import_zod4.z.string().min(1),
|
|
38659
|
+
buildId: import_zod4.z.string().min(1).optional(),
|
|
38660
|
+
buildNumber: import_zod4.z.string().min(1).optional(),
|
|
38661
|
+
buildTypeId: import_zod4.z.string().min(1).optional(),
|
|
38281
38662
|
includeTests: import_zod4.z.boolean().optional(),
|
|
38282
38663
|
includeProblems: import_zod4.z.boolean().optional(),
|
|
38283
38664
|
includeQueueTotals: import_zod4.z.boolean().optional(),
|
|
38284
38665
|
includeQueueReason: import_zod4.z.boolean().optional()
|
|
38666
|
+
}).superRefine((value, ctx) => {
|
|
38667
|
+
if (!value.buildId && !value.buildNumber) {
|
|
38668
|
+
ctx.addIssue({
|
|
38669
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
38670
|
+
path: ["buildId"],
|
|
38671
|
+
message: "Either buildId or buildNumber must be provided"
|
|
38672
|
+
});
|
|
38673
|
+
}
|
|
38674
|
+
if (value.buildNumber && !value.buildTypeId) {
|
|
38675
|
+
ctx.addIssue({
|
|
38676
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
38677
|
+
path: ["buildTypeId"],
|
|
38678
|
+
message: "buildTypeId is required when querying by buildNumber"
|
|
38679
|
+
});
|
|
38680
|
+
}
|
|
38285
38681
|
});
|
|
38286
38682
|
return runTool(
|
|
38287
38683
|
"get_build_status",
|
|
@@ -38291,6 +38687,8 @@ var DEV_TOOLS = [
|
|
|
38291
38687
|
const statusManager = new (await Promise.resolve().then(() => (init_build_status_manager(), build_status_manager_exports))).BuildStatusManager(adapter);
|
|
38292
38688
|
const result = await statusManager.getBuildStatus({
|
|
38293
38689
|
buildId: typed.buildId,
|
|
38690
|
+
buildNumber: typed.buildNumber,
|
|
38691
|
+
buildTypeId: typed.buildTypeId,
|
|
38294
38692
|
includeTests: typed.includeTests,
|
|
38295
38693
|
includeProblems: typed.includeProblems
|
|
38296
38694
|
});
|
|
@@ -38311,8 +38709,11 @@ var DEV_TOOLS = [
|
|
|
38311
38709
|
}
|
|
38312
38710
|
if (typed.includeQueueReason) {
|
|
38313
38711
|
try {
|
|
38314
|
-
const
|
|
38315
|
-
|
|
38712
|
+
const targetBuildId = typed.buildId ?? result.buildId;
|
|
38713
|
+
if (targetBuildId) {
|
|
38714
|
+
const qb = await adapter.modules.buildQueue.getQueuedBuild(targetBuildId);
|
|
38715
|
+
enrich.waitReason = qb.data.waitReason;
|
|
38716
|
+
}
|
|
38316
38717
|
} catch {
|
|
38317
38718
|
}
|
|
38318
38719
|
}
|
|
@@ -40689,22 +41090,88 @@ var FULL_MODE_TOOLS = [
|
|
|
40689
41090
|
sourceBuildTypeId: { type: "string", description: "Source build type ID" },
|
|
40690
41091
|
name: { type: "string", description: "New build configuration name" },
|
|
40691
41092
|
id: { type: "string", description: "New build configuration ID" },
|
|
40692
|
-
projectId: { type: "string", description: "Target project ID" }
|
|
41093
|
+
projectId: { type: "string", description: "Target project ID" },
|
|
41094
|
+
description: { type: "string", description: "Description for the cloned configuration" },
|
|
41095
|
+
parameters: {
|
|
41096
|
+
type: "object",
|
|
41097
|
+
description: "Optional parameter overrides to apply to the clone",
|
|
41098
|
+
additionalProperties: { type: "string" }
|
|
41099
|
+
},
|
|
41100
|
+
copyBuildCounter: {
|
|
41101
|
+
type: "boolean",
|
|
41102
|
+
description: "Copy the build number counter from the source configuration"
|
|
41103
|
+
}
|
|
40693
41104
|
},
|
|
40694
41105
|
required: ["sourceBuildTypeId", "name", "id"]
|
|
40695
41106
|
},
|
|
40696
41107
|
handler: async (args) => {
|
|
40697
|
-
const
|
|
40698
|
-
|
|
40699
|
-
|
|
40700
|
-
|
|
40701
|
-
|
|
40702
|
-
|
|
40703
|
-
|
|
40704
|
-
|
|
40705
|
-
}
|
|
40706
|
-
|
|
40707
|
-
|
|
41108
|
+
const schema = import_zod4.z.object({
|
|
41109
|
+
sourceBuildTypeId: import_zod4.z.string().min(1),
|
|
41110
|
+
name: import_zod4.z.string().min(1),
|
|
41111
|
+
id: import_zod4.z.string().min(1),
|
|
41112
|
+
projectId: import_zod4.z.string().min(1).optional(),
|
|
41113
|
+
description: import_zod4.z.string().optional(),
|
|
41114
|
+
parameters: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.string()).optional(),
|
|
41115
|
+
copyBuildCounter: import_zod4.z.boolean().optional()
|
|
41116
|
+
}).superRefine((value, ctx) => {
|
|
41117
|
+
if (value.id.trim() === "") {
|
|
41118
|
+
ctx.addIssue({
|
|
41119
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
41120
|
+
message: "id must be a non-empty string.",
|
|
41121
|
+
path: ["id"]
|
|
41122
|
+
});
|
|
41123
|
+
}
|
|
41124
|
+
});
|
|
41125
|
+
return runTool(
|
|
41126
|
+
"clone_build_config",
|
|
41127
|
+
schema,
|
|
41128
|
+
async (typedArgs) => {
|
|
41129
|
+
const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
|
|
41130
|
+
const manager = new BuildConfigurationCloneManager(adapter);
|
|
41131
|
+
const source = await manager.retrieveConfiguration(typedArgs.sourceBuildTypeId);
|
|
41132
|
+
if (!source) {
|
|
41133
|
+
return json({
|
|
41134
|
+
success: false,
|
|
41135
|
+
action: "clone_build_config",
|
|
41136
|
+
error: `Source build configuration not found: ${typedArgs.sourceBuildTypeId}`
|
|
41137
|
+
});
|
|
41138
|
+
}
|
|
41139
|
+
const targetProjectId = typedArgs.projectId ?? source.projectId;
|
|
41140
|
+
if (!targetProjectId) {
|
|
41141
|
+
return json({
|
|
41142
|
+
success: false,
|
|
41143
|
+
action: "clone_build_config",
|
|
41144
|
+
error: "projectId is required when the source configuration does not specify a project."
|
|
41145
|
+
});
|
|
41146
|
+
}
|
|
41147
|
+
try {
|
|
41148
|
+
const cloned = await manager.cloneConfiguration(source, {
|
|
41149
|
+
id: typedArgs.id,
|
|
41150
|
+
name: typedArgs.name,
|
|
41151
|
+
targetProjectId,
|
|
41152
|
+
description: typedArgs.description ?? source.description,
|
|
41153
|
+
parameters: typedArgs.parameters,
|
|
41154
|
+
copyBuildCounter: typedArgs.copyBuildCounter
|
|
41155
|
+
});
|
|
41156
|
+
return json({
|
|
41157
|
+
success: true,
|
|
41158
|
+
action: "clone_build_config",
|
|
41159
|
+
id: cloned.id,
|
|
41160
|
+
name: cloned.name,
|
|
41161
|
+
projectId: cloned.projectId,
|
|
41162
|
+
url: cloned.url,
|
|
41163
|
+
description: cloned.description
|
|
41164
|
+
});
|
|
41165
|
+
} catch (error2) {
|
|
41166
|
+
return json({
|
|
41167
|
+
success: false,
|
|
41168
|
+
action: "clone_build_config",
|
|
41169
|
+
error: error2 instanceof Error ? error2.message : "Failed to clone build configuration."
|
|
41170
|
+
});
|
|
41171
|
+
}
|
|
41172
|
+
},
|
|
41173
|
+
args
|
|
41174
|
+
);
|
|
40708
41175
|
},
|
|
40709
41176
|
mode: "full"
|
|
40710
41177
|
},
|