@daghis/teamcity-mcp 1.10.1 → 1.10.3
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 +528 -27
- 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.3](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.2...teamcity-mcp-v1.10.3) (2025-09-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **tools:** honor trigger_build branch overrides (210) ([#223](https://github.com/Daghis/teamcity-mcp/issues/223)) ([7222c28](https://github.com/Daghis/teamcity-mcp/commit/7222c28c4fc9a307222ee9a50fa518127f5187de))
|
|
9
|
+
|
|
10
|
+
## [1.10.2](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.1...teamcity-mcp-v1.10.2) (2025-09-27)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **tools:** clone_build_config uses manager (215) ([b84d1f8](https://github.com/Daghis/teamcity-mcp/commit/b84d1f80a4233783a93dd1e3ede9a83a7cf57171))
|
|
16
|
+
* **tools:** clone_build_config uses manager (215) ([c4cd959](https://github.com/Daghis/teamcity-mcp/commit/c4cd959a9f35052bf95386162316a9ace5599eb6))
|
|
17
|
+
|
|
3
18
|
## [1.10.1](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.0...teamcity-mcp-v1.10.1) (2025-09-27)
|
|
4
19
|
|
|
5
20
|
|
package/dist/index.js
CHANGED
|
@@ -1516,6 +1516,384 @@ var ArtifactManager = class _ArtifactManager {
|
|
|
1516
1516
|
}
|
|
1517
1517
|
};
|
|
1518
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
|
+
|
|
1519
1897
|
// src/teamcity/build-configuration-update-manager.ts
|
|
1520
1898
|
var ARTIFACT_RULES_SETTINGS_FIELD = "settings/artifactRules";
|
|
1521
1899
|
var ARTIFACT_RULES_LEGACY_FIELD = "artifactRules";
|
|
@@ -38171,7 +38549,12 @@ var DEV_TOOLS = [
|
|
|
38171
38549
|
properties: {
|
|
38172
38550
|
buildTypeId: { type: "string", description: "Build type ID to trigger" },
|
|
38173
38551
|
branchName: { type: "string", description: "Branch to build (optional)" },
|
|
38174
|
-
comment: { type: "string", description: "Build comment (optional)" }
|
|
38552
|
+
comment: { type: "string", description: "Build comment (optional)" },
|
|
38553
|
+
properties: {
|
|
38554
|
+
type: "object",
|
|
38555
|
+
description: "Optional build parameters to set when triggering the build",
|
|
38556
|
+
additionalProperties: { type: "string" }
|
|
38557
|
+
}
|
|
38175
38558
|
},
|
|
38176
38559
|
required: ["buildTypeId"]
|
|
38177
38560
|
},
|
|
@@ -38179,35 +38562,84 @@ var DEV_TOOLS = [
|
|
|
38179
38562
|
const schema = import_zod4.z.object({
|
|
38180
38563
|
buildTypeId: import_zod4.z.string().min(1),
|
|
38181
38564
|
branchName: import_zod4.z.string().min(1).max(255).optional(),
|
|
38182
|
-
comment: import_zod4.z.string().max(500).optional()
|
|
38565
|
+
comment: import_zod4.z.string().max(500).optional(),
|
|
38566
|
+
properties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.string()).optional()
|
|
38183
38567
|
});
|
|
38184
38568
|
return runTool(
|
|
38185
38569
|
"trigger_build",
|
|
38186
38570
|
schema,
|
|
38187
38571
|
async (typed) => {
|
|
38188
38572
|
const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
|
|
38189
|
-
|
|
38190
|
-
|
|
38191
|
-
|
|
38192
|
-
|
|
38193
|
-
|
|
38573
|
+
const directBranch = typed.branchName?.trim();
|
|
38574
|
+
const normalizedDirectBranch = directBranch && directBranch.length > 0 ? directBranch : void 0;
|
|
38575
|
+
const rawPropertyBranch = typed.properties?.["teamcity.build.branch"];
|
|
38576
|
+
const trimmedPropertyBranch = rawPropertyBranch?.trim();
|
|
38577
|
+
const normalizedPropertyBranch = trimmedPropertyBranch && trimmedPropertyBranch.length > 0 ? trimmedPropertyBranch : void 0;
|
|
38578
|
+
const branchName = normalizedDirectBranch ?? normalizedPropertyBranch;
|
|
38579
|
+
if (normalizedDirectBranch && normalizedPropertyBranch && normalizedDirectBranch !== normalizedPropertyBranch) {
|
|
38580
|
+
const errorPayload = {
|
|
38581
|
+
success: false,
|
|
38582
|
+
action: "trigger_build",
|
|
38583
|
+
error: `Conflicting branch overrides: branchName='${normalizedDirectBranch}' vs properties.teamcity.build.branch='${normalizedPropertyBranch}'.`
|
|
38584
|
+
};
|
|
38585
|
+
return {
|
|
38586
|
+
success: false,
|
|
38587
|
+
error: errorPayload.error,
|
|
38588
|
+
content: [{ type: "text", text: JSON.stringify(errorPayload, null, 2) }]
|
|
38589
|
+
};
|
|
38590
|
+
}
|
|
38591
|
+
const propertyEntries = typed.properties ? Object.entries(typed.properties).map(([name, value]) => ({
|
|
38592
|
+
name,
|
|
38593
|
+
value: name === "teamcity.build.branch" && normalizedPropertyBranch ? normalizedPropertyBranch : value
|
|
38594
|
+
})) : [];
|
|
38595
|
+
const propertiesPayload = propertyEntries.length > 0 ? { property: propertyEntries } : void 0;
|
|
38596
|
+
const buildRequest = {
|
|
38597
|
+
buildType: { id: typed.buildTypeId }
|
|
38598
|
+
};
|
|
38599
|
+
if (branchName) {
|
|
38600
|
+
buildRequest.branchName = branchName;
|
|
38601
|
+
}
|
|
38602
|
+
const commentText = typed.comment?.trim();
|
|
38603
|
+
if (commentText && commentText.length > 0) {
|
|
38604
|
+
buildRequest.comment = { text: commentText };
|
|
38605
|
+
}
|
|
38606
|
+
if (propertiesPayload) {
|
|
38607
|
+
buildRequest.properties = propertiesPayload;
|
|
38608
|
+
}
|
|
38609
|
+
const sendXmlFallback = async (error2) => {
|
|
38610
|
+
const escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
38611
|
+
const branchPart = branchName ? `<branchName>${escapeXml(branchName)}</branchName>` : "";
|
|
38612
|
+
const commentPart = commentText ? `<comment><text>${escapeXml(commentText)}</text></comment>` : "";
|
|
38613
|
+
const propertiesPart = propertiesPayload ? `<properties>${propertiesPayload.property.map(
|
|
38614
|
+
(prop) => `<property name="${escapeXml(prop.name)}" value="${escapeXml(prop.value)}"/>`
|
|
38615
|
+
).join("")}</properties>` : "";
|
|
38616
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?><build><buildType id="${escapeXml(
|
|
38617
|
+
typed.buildTypeId
|
|
38618
|
+
)}"/>${branchPart}${commentPart}${propertiesPart}</build>`;
|
|
38619
|
+
const response = await adapter.modules.buildQueue.addBuildToQueue(
|
|
38620
|
+
false,
|
|
38621
|
+
xml,
|
|
38622
|
+
{
|
|
38623
|
+
headers: { "Content-Type": "application/xml", Accept: "application/json" }
|
|
38624
|
+
}
|
|
38194
38625
|
);
|
|
38626
|
+
const build = response.data;
|
|
38195
38627
|
return json({
|
|
38196
38628
|
success: true,
|
|
38197
38629
|
action: "trigger_build",
|
|
38198
38630
|
buildId: String(build.id ?? ""),
|
|
38199
38631
|
state: build.state ?? void 0,
|
|
38200
|
-
status: build.status ?? void 0
|
|
38632
|
+
status: build.status ?? void 0,
|
|
38633
|
+
branchName: build.branchName ?? branchName,
|
|
38634
|
+
fallback: { mode: "xml", reason: error2?.message }
|
|
38201
38635
|
});
|
|
38202
|
-
}
|
|
38203
|
-
|
|
38204
|
-
const commentPart = typed.comment ? `<comment><text>${typed.comment.replace(/</g, "<").replace(/>/g, ">")}</text></comment>` : "";
|
|
38205
|
-
const xml = `<?xml version="1.0" encoding="UTF-8"?><build><buildType id="${typed.buildTypeId}"/>${branchPart}${commentPart}</build>`;
|
|
38636
|
+
};
|
|
38637
|
+
try {
|
|
38206
38638
|
const response = await adapter.modules.buildQueue.addBuildToQueue(
|
|
38207
38639
|
false,
|
|
38208
|
-
|
|
38640
|
+
buildRequest,
|
|
38209
38641
|
{
|
|
38210
|
-
headers: { "Content-Type": "application/
|
|
38642
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" }
|
|
38211
38643
|
}
|
|
38212
38644
|
);
|
|
38213
38645
|
const build = response.data;
|
|
@@ -38216,8 +38648,11 @@ var DEV_TOOLS = [
|
|
|
38216
38648
|
action: "trigger_build",
|
|
38217
38649
|
buildId: String(build.id ?? ""),
|
|
38218
38650
|
state: build.state ?? void 0,
|
|
38219
|
-
status: build.status ?? void 0
|
|
38651
|
+
status: build.status ?? void 0,
|
|
38652
|
+
branchName: build.branchName ?? branchName
|
|
38220
38653
|
});
|
|
38654
|
+
} catch (error2) {
|
|
38655
|
+
return sendXmlFallback(error2);
|
|
38221
38656
|
}
|
|
38222
38657
|
},
|
|
38223
38658
|
args
|
|
@@ -40712,22 +41147,88 @@ var FULL_MODE_TOOLS = [
|
|
|
40712
41147
|
sourceBuildTypeId: { type: "string", description: "Source build type ID" },
|
|
40713
41148
|
name: { type: "string", description: "New build configuration name" },
|
|
40714
41149
|
id: { type: "string", description: "New build configuration ID" },
|
|
40715
|
-
projectId: { type: "string", description: "Target project ID" }
|
|
41150
|
+
projectId: { type: "string", description: "Target project ID" },
|
|
41151
|
+
description: { type: "string", description: "Description for the cloned configuration" },
|
|
41152
|
+
parameters: {
|
|
41153
|
+
type: "object",
|
|
41154
|
+
description: "Optional parameter overrides to apply to the clone",
|
|
41155
|
+
additionalProperties: { type: "string" }
|
|
41156
|
+
},
|
|
41157
|
+
copyBuildCounter: {
|
|
41158
|
+
type: "boolean",
|
|
41159
|
+
description: "Copy the build number counter from the source configuration"
|
|
41160
|
+
}
|
|
40716
41161
|
},
|
|
40717
41162
|
required: ["sourceBuildTypeId", "name", "id"]
|
|
40718
41163
|
},
|
|
40719
41164
|
handler: async (args) => {
|
|
40720
|
-
const
|
|
40721
|
-
|
|
40722
|
-
|
|
40723
|
-
|
|
40724
|
-
|
|
40725
|
-
|
|
40726
|
-
|
|
40727
|
-
|
|
40728
|
-
}
|
|
40729
|
-
|
|
40730
|
-
|
|
41165
|
+
const schema = import_zod4.z.object({
|
|
41166
|
+
sourceBuildTypeId: import_zod4.z.string().min(1),
|
|
41167
|
+
name: import_zod4.z.string().min(1),
|
|
41168
|
+
id: import_zod4.z.string().min(1),
|
|
41169
|
+
projectId: import_zod4.z.string().min(1).optional(),
|
|
41170
|
+
description: import_zod4.z.string().optional(),
|
|
41171
|
+
parameters: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.string()).optional(),
|
|
41172
|
+
copyBuildCounter: import_zod4.z.boolean().optional()
|
|
41173
|
+
}).superRefine((value, ctx) => {
|
|
41174
|
+
if (value.id.trim() === "") {
|
|
41175
|
+
ctx.addIssue({
|
|
41176
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
41177
|
+
message: "id must be a non-empty string.",
|
|
41178
|
+
path: ["id"]
|
|
41179
|
+
});
|
|
41180
|
+
}
|
|
41181
|
+
});
|
|
41182
|
+
return runTool(
|
|
41183
|
+
"clone_build_config",
|
|
41184
|
+
schema,
|
|
41185
|
+
async (typedArgs) => {
|
|
41186
|
+
const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
|
|
41187
|
+
const manager = new BuildConfigurationCloneManager(adapter);
|
|
41188
|
+
const source = await manager.retrieveConfiguration(typedArgs.sourceBuildTypeId);
|
|
41189
|
+
if (!source) {
|
|
41190
|
+
return json({
|
|
41191
|
+
success: false,
|
|
41192
|
+
action: "clone_build_config",
|
|
41193
|
+
error: `Source build configuration not found: ${typedArgs.sourceBuildTypeId}`
|
|
41194
|
+
});
|
|
41195
|
+
}
|
|
41196
|
+
const targetProjectId = typedArgs.projectId ?? source.projectId;
|
|
41197
|
+
if (!targetProjectId) {
|
|
41198
|
+
return json({
|
|
41199
|
+
success: false,
|
|
41200
|
+
action: "clone_build_config",
|
|
41201
|
+
error: "projectId is required when the source configuration does not specify a project."
|
|
41202
|
+
});
|
|
41203
|
+
}
|
|
41204
|
+
try {
|
|
41205
|
+
const cloned = await manager.cloneConfiguration(source, {
|
|
41206
|
+
id: typedArgs.id,
|
|
41207
|
+
name: typedArgs.name,
|
|
41208
|
+
targetProjectId,
|
|
41209
|
+
description: typedArgs.description ?? source.description,
|
|
41210
|
+
parameters: typedArgs.parameters,
|
|
41211
|
+
copyBuildCounter: typedArgs.copyBuildCounter
|
|
41212
|
+
});
|
|
41213
|
+
return json({
|
|
41214
|
+
success: true,
|
|
41215
|
+
action: "clone_build_config",
|
|
41216
|
+
id: cloned.id,
|
|
41217
|
+
name: cloned.name,
|
|
41218
|
+
projectId: cloned.projectId,
|
|
41219
|
+
url: cloned.url,
|
|
41220
|
+
description: cloned.description
|
|
41221
|
+
});
|
|
41222
|
+
} catch (error2) {
|
|
41223
|
+
return json({
|
|
41224
|
+
success: false,
|
|
41225
|
+
action: "clone_build_config",
|
|
41226
|
+
error: error2 instanceof Error ? error2.message : "Failed to clone build configuration."
|
|
41227
|
+
});
|
|
41228
|
+
}
|
|
41229
|
+
},
|
|
41230
|
+
args
|
|
41231
|
+
);
|
|
40731
41232
|
},
|
|
40732
41233
|
mode: "full"
|
|
40733
41234
|
},
|