@daghis/teamcity-mcp 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.9.1](https://github.com/Daghis/teamcity-mcp/compare/v1.9.0...v1.9.1) (2025-09-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **tools:** allow manage_build_steps updates ([#182](https://github.com/Daghis/teamcity-mcp/issues/182)) ([2c6bea0](https://github.com/Daghis/teamcity-mcp/commit/2c6bea0cf6770d22068b9e4b733542a056399148))
9
+
10
+ ## [1.9.0](https://github.com/Daghis/teamcity-mcp/compare/v1.8.2...v1.9.0) (2025-09-20)
11
+
12
+
13
+ ### Features
14
+
15
+ * **teamcity:** add runtime guards for manager responses ([#179](https://github.com/Daghis/teamcity-mcp/issues/179)) ([9d7eaef](https://github.com/Daghis/teamcity-mcp/commit/9d7eaef161e675246d317603df47f7f39407b7d8))
16
+
3
17
  ## [1.8.2](https://github.com/Daghis/teamcity-mcp/compare/v1.8.1...v1.8.2) (2025-09-20)
4
18
 
5
19
 
package/dist/index.js CHANGED
@@ -938,11 +938,15 @@ var ResolutionTypeEnum = {
938
938
 
939
939
  // src/teamcity/artifact-manager.ts
940
940
  var import_axios = require("axios");
941
+ init_errors();
941
942
 
942
943
  // src/teamcity/utils/build-locator.ts
943
944
  var toBuildLocator = (buildId) => buildId.includes(":") ? buildId : `id:${buildId}`;
944
945
 
945
946
  // src/teamcity/artifact-manager.ts
947
+ var isRecord = (value) => {
948
+ return typeof value === "object" && value !== null;
949
+ };
946
950
  var ArtifactManager = class _ArtifactManager {
947
951
  client;
948
952
  cache = /* @__PURE__ */ new Map();
@@ -979,12 +983,8 @@ var ArtifactManager = class _ArtifactManager {
979
983
  "file(name,fullName,size,modificationTime,href,children(file(name,fullName,size,modificationTime,href)))"
980
984
  );
981
985
  const baseUrl = this.getBaseUrl();
982
- let artifacts = this.parseArtifacts(
983
- response.data ?? {},
984
- buildId,
985
- options.includeNested,
986
- baseUrl
987
- );
986
+ const artifactPayload = this.ensureArtifactListingResponse(response.data, buildId);
987
+ let artifacts = this.parseArtifacts(artifactPayload, buildId, options.includeNested, baseUrl);
988
988
  artifacts = this.applyFilters(artifacts, options);
989
989
  if (options.limit ?? options.offset) {
990
990
  artifacts = this.paginate(
@@ -1183,6 +1183,39 @@ var ArtifactManager = class _ArtifactManager {
1183
1183
  /**
1184
1184
  * Parse artifacts from API response
1185
1185
  */
1186
+ ensureArtifactListingResponse(data, buildId) {
1187
+ if (!isRecord(data)) {
1188
+ throw new TeamCityAPIError(
1189
+ "TeamCity returned a non-object artifact listing response",
1190
+ "INVALID_RESPONSE",
1191
+ void 0,
1192
+ { buildId }
1193
+ );
1194
+ }
1195
+ const payload = data;
1196
+ const { file } = payload;
1197
+ if (file !== void 0 && !Array.isArray(file)) {
1198
+ throw new TeamCityAPIError(
1199
+ "TeamCity artifact listing response contains a non-array file field",
1200
+ "INVALID_RESPONSE",
1201
+ void 0,
1202
+ { buildId }
1203
+ );
1204
+ }
1205
+ if (Array.isArray(file)) {
1206
+ file.forEach((entry, index) => {
1207
+ if (!isRecord(entry)) {
1208
+ throw new TeamCityAPIError(
1209
+ "TeamCity artifact listing response contains a non-object file entry",
1210
+ "INVALID_RESPONSE",
1211
+ void 0,
1212
+ { buildId, index }
1213
+ );
1214
+ }
1215
+ });
1216
+ }
1217
+ return payload;
1218
+ }
1186
1219
  parseArtifacts(data, buildId, includeNested, baseUrl) {
1187
1220
  const artifacts = [];
1188
1221
  const files = data.file ?? [];
@@ -1699,6 +1732,10 @@ var BuildConfigurationUpdateManager = class {
1699
1732
  };
1700
1733
 
1701
1734
  // src/teamcity/build-results-manager.ts
1735
+ init_errors();
1736
+ var isRecord2 = (value) => {
1737
+ return typeof value === "object" && value !== null;
1738
+ };
1702
1739
  var BuildResultsManager = class _BuildResultsManager {
1703
1740
  client;
1704
1741
  cache = /* @__PURE__ */ new Map();
@@ -1783,15 +1820,14 @@ var BuildResultsManager = class _BuildResultsManager {
1783
1820
  toBuildLocator(buildId),
1784
1821
  _BuildResultsManager.fields
1785
1822
  );
1786
- return response.data;
1823
+ return this.ensureBuildSummary(response.data, buildId);
1787
1824
  }
1788
1825
  /**
1789
1826
  * Transform build data to result format
1790
1827
  */
1791
- transformBuildData(data) {
1792
- const buildData = data;
1828
+ transformBuildData(buildData) {
1793
1829
  const build = {
1794
- id: buildData.id,
1830
+ id: typeof buildData.id === "string" ? Number.parseInt(buildData.id, 10) : buildData.id,
1795
1831
  number: buildData.number,
1796
1832
  status: buildData.status,
1797
1833
  state: buildData.state,
@@ -1820,16 +1856,107 @@ var BuildResultsManager = class _BuildResultsManager {
1820
1856
  build.duration = finish - start;
1821
1857
  }
1822
1858
  if (buildData.triggered) {
1823
- build.triggered = {
1859
+ const triggered = {
1824
1860
  type: buildData.triggered.type,
1825
1861
  date: buildData.triggered.date
1826
1862
  };
1827
- if (buildData.triggered.user) {
1828
- build.triggered.user = buildData.triggered.user.username ?? buildData.triggered.user.name;
1863
+ const triggeredUser = buildData.triggered.user;
1864
+ if (triggeredUser) {
1865
+ const username = triggeredUser.username ?? triggeredUser.name;
1866
+ if (username) {
1867
+ triggered.user = username;
1868
+ }
1829
1869
  }
1870
+ build.triggered = triggered;
1830
1871
  }
1831
1872
  return build;
1832
1873
  }
1874
+ ensureBuildSummary(data, buildId) {
1875
+ if (!isRecord2(data)) {
1876
+ throw new TeamCityAPIError(
1877
+ "TeamCity returned a non-object build summary response",
1878
+ "INVALID_RESPONSE",
1879
+ void 0,
1880
+ { buildId, expected: "object with build fields", receivedType: typeof data }
1881
+ );
1882
+ }
1883
+ const summary = data;
1884
+ const { id, number, status, state, buildTypeId, webUrl, triggered } = summary;
1885
+ if (typeof id !== "number" && typeof id !== "string" || typeof number !== "string" || typeof status !== "string" || typeof state !== "string" || typeof buildTypeId !== "string" || typeof webUrl !== "string") {
1886
+ throw new TeamCityAPIError(
1887
+ "TeamCity build summary response is missing required fields",
1888
+ "INVALID_RESPONSE",
1889
+ void 0,
1890
+ { buildId, receivedKeys: Object.keys(summary) }
1891
+ );
1892
+ }
1893
+ let normalizedTriggered;
1894
+ if (triggered !== void 0 && triggered !== null) {
1895
+ if (!isRecord2(triggered)) {
1896
+ throw new TeamCityAPIError(
1897
+ "TeamCity build summary response contains an invalid triggered payload",
1898
+ "INVALID_RESPONSE",
1899
+ void 0,
1900
+ { buildId, receivedType: typeof triggered }
1901
+ );
1902
+ }
1903
+ const { type, date, user } = triggered;
1904
+ if (typeof type !== "string" || typeof date !== "string") {
1905
+ throw new TeamCityAPIError(
1906
+ "TeamCity build summary response contains an invalid triggered payload",
1907
+ "INVALID_RESPONSE",
1908
+ void 0,
1909
+ { buildId }
1910
+ );
1911
+ }
1912
+ if (user !== void 0 && user !== null && !isRecord2(user)) {
1913
+ throw new TeamCityAPIError(
1914
+ "TeamCity build summary response contains an invalid trigger user payload",
1915
+ "INVALID_RESPONSE",
1916
+ void 0,
1917
+ { buildId }
1918
+ );
1919
+ }
1920
+ let normalizedUser;
1921
+ if (user !== void 0 && user !== null) {
1922
+ const userRecord = user;
1923
+ const username = userRecord["username"];
1924
+ const name = userRecord["name"];
1925
+ const normalizedUsername = typeof username === "string" ? username : void 0;
1926
+ const normalizedName = typeof name === "string" ? name : void 0;
1927
+ if (normalizedUsername !== void 0 || normalizedName !== void 0) {
1928
+ normalizedUser = {};
1929
+ if (normalizedUsername) {
1930
+ normalizedUser.username = normalizedUsername;
1931
+ }
1932
+ if (normalizedName) {
1933
+ normalizedUser.name = normalizedName;
1934
+ }
1935
+ }
1936
+ }
1937
+ normalizedTriggered = {
1938
+ type,
1939
+ date,
1940
+ ...normalizedUser ? { user: normalizedUser } : {}
1941
+ };
1942
+ }
1943
+ const normalized = {
1944
+ id,
1945
+ number,
1946
+ status,
1947
+ state,
1948
+ buildTypeId,
1949
+ statusText: typeof summary["statusText"] === "string" ? summary["statusText"] : void 0,
1950
+ webUrl,
1951
+ projectId: typeof summary["projectId"] === "string" ? summary["projectId"] : void 0,
1952
+ branchName: typeof summary["branchName"] === "string" ? summary["branchName"] : void 0,
1953
+ startDate: typeof summary["startDate"] === "string" ? summary["startDate"] : void 0,
1954
+ finishDate: typeof summary["finishDate"] === "string" ? summary["finishDate"] : void 0,
1955
+ queuedDate: typeof summary["queuedDate"] === "string" ? summary["queuedDate"] : void 0,
1956
+ triggered: normalizedTriggered
1957
+ };
1958
+ return normalized;
1959
+ }
1833
1960
  /**
1834
1961
  * Fetch build artifacts
1835
1962
  */
@@ -1839,7 +1966,7 @@ var BuildResultsManager = class _BuildResultsManager {
1839
1966
  const response = await this.client.modules.builds.getFilesListOfBuild(
1840
1967
  toBuildLocator(buildId)
1841
1968
  );
1842
- const artifactListing = response.data;
1969
+ const artifactListing = this.ensureArtifactListResponse(response.data, buildId);
1843
1970
  let artifacts = artifactListing.file ?? [];
1844
1971
  if (options.artifactFilter) {
1845
1972
  artifacts = this.filterArtifacts(artifacts, options.artifactFilter);
@@ -1881,7 +2008,11 @@ var BuildResultsManager = class _BuildResultsManager {
1881
2008
  );
1882
2009
  return result;
1883
2010
  } catch (error2) {
1884
- warn("Failed to fetch artifacts", { error: error2, buildId });
2011
+ warn("Failed to fetch artifacts", {
2012
+ error: error2 instanceof Error ? error2.message : error2,
2013
+ buildId,
2014
+ expected: "file[]"
2015
+ });
1885
2016
  return [];
1886
2017
  }
1887
2018
  }
@@ -1894,6 +2025,137 @@ var BuildResultsManager = class _BuildResultsManager {
1894
2025
  );
1895
2026
  return artifacts.filter((a) => regex.test(a.name));
1896
2027
  }
2028
+ ensureArtifactListResponse(data, buildId) {
2029
+ if (!isRecord2(data)) {
2030
+ throw new TeamCityAPIError(
2031
+ "TeamCity returned a non-object artifact list response",
2032
+ "INVALID_RESPONSE",
2033
+ void 0,
2034
+ { buildId, expected: "object with file[]" }
2035
+ );
2036
+ }
2037
+ const { file } = data;
2038
+ if (file !== void 0 && !Array.isArray(file)) {
2039
+ throw new TeamCityAPIError(
2040
+ "TeamCity artifact list response contains a non-array file field",
2041
+ "INVALID_RESPONSE",
2042
+ void 0,
2043
+ { buildId, receivedType: typeof file }
2044
+ );
2045
+ }
2046
+ return data;
2047
+ }
2048
+ ensureStatisticsResponse(data, buildId) {
2049
+ if (!isRecord2(data)) {
2050
+ throw new TeamCityAPIError(
2051
+ "TeamCity returned a non-object statistics response",
2052
+ "INVALID_RESPONSE",
2053
+ void 0,
2054
+ { buildId, expected: "object with property[]" }
2055
+ );
2056
+ }
2057
+ const { property } = data;
2058
+ if (property === void 0) {
2059
+ return {};
2060
+ }
2061
+ if (!Array.isArray(property)) {
2062
+ throw new TeamCityAPIError(
2063
+ "TeamCity statistics response contains a non-array property field",
2064
+ "INVALID_RESPONSE",
2065
+ void 0,
2066
+ { buildId, receivedType: typeof property }
2067
+ );
2068
+ }
2069
+ property.forEach((entry, index) => {
2070
+ if (!isRecord2(entry)) {
2071
+ throw new TeamCityAPIError(
2072
+ "TeamCity statistics response contains a non-object property entry",
2073
+ "INVALID_RESPONSE",
2074
+ void 0,
2075
+ { buildId, index }
2076
+ );
2077
+ }
2078
+ const { name, value } = entry;
2079
+ if (typeof name !== "string" || typeof value !== "string") {
2080
+ throw new TeamCityAPIError(
2081
+ "TeamCity statistics response property entry is missing required fields",
2082
+ "INVALID_RESPONSE",
2083
+ void 0,
2084
+ { buildId, index, receivedKeys: Object.keys(entry) }
2085
+ );
2086
+ }
2087
+ });
2088
+ return { property };
2089
+ }
2090
+ ensureChangesResponse(data, buildId) {
2091
+ if (!isRecord2(data)) {
2092
+ throw new TeamCityAPIError(
2093
+ "TeamCity returned a non-object changes response",
2094
+ "INVALID_RESPONSE",
2095
+ void 0,
2096
+ { buildId, expected: "object with change[]" }
2097
+ );
2098
+ }
2099
+ const { change } = data;
2100
+ if (change !== void 0 && !Array.isArray(change)) {
2101
+ throw new TeamCityAPIError(
2102
+ "TeamCity changes response contains a non-array change field",
2103
+ "INVALID_RESPONSE",
2104
+ void 0,
2105
+ { buildId, receivedType: typeof change }
2106
+ );
2107
+ }
2108
+ return data;
2109
+ }
2110
+ ensureDependenciesResponse(data, buildId) {
2111
+ if (!isRecord2(data)) {
2112
+ throw new TeamCityAPIError(
2113
+ "TeamCity returned a non-object dependencies response",
2114
+ "INVALID_RESPONSE",
2115
+ void 0,
2116
+ { buildId, expected: "object with build[]" }
2117
+ );
2118
+ }
2119
+ const { build } = data;
2120
+ if (build !== void 0 && !Array.isArray(build)) {
2121
+ throw new TeamCityAPIError(
2122
+ "TeamCity dependencies response contains a non-array build field",
2123
+ "INVALID_RESPONSE",
2124
+ void 0,
2125
+ { buildId, receivedType: typeof build }
2126
+ );
2127
+ }
2128
+ if (Array.isArray(build)) {
2129
+ build.forEach((entry, index) => {
2130
+ if (!isRecord2(entry)) {
2131
+ throw new TeamCityAPIError(
2132
+ "TeamCity dependencies response contains a non-object build entry",
2133
+ "INVALID_RESPONSE",
2134
+ void 0,
2135
+ { buildId, index }
2136
+ );
2137
+ }
2138
+ const { id, number, buildTypeId, status } = entry;
2139
+ if (typeof id !== "number" && typeof id !== "string" || typeof number !== "string" || typeof buildTypeId !== "string" || typeof status !== "string") {
2140
+ throw new TeamCityAPIError(
2141
+ "TeamCity dependencies response is missing required fields on build entry",
2142
+ "INVALID_RESPONSE",
2143
+ void 0,
2144
+ { buildId, index, receivedKeys: Object.keys(entry) }
2145
+ );
2146
+ }
2147
+ if (typeof id === "string" && Number.isNaN(Number.parseInt(id, 10))) {
2148
+ throw new TeamCityAPIError(
2149
+ "TeamCity dependencies response contains a non-numeric id value",
2150
+ "INVALID_RESPONSE",
2151
+ void 0,
2152
+ { buildId, index, receivedValue: id }
2153
+ );
2154
+ }
2155
+ });
2156
+ }
2157
+ return data;
2158
+ }
1897
2159
  /**
1898
2160
  * Fetch build statistics
1899
2161
  */
@@ -1902,7 +2164,7 @@ var BuildResultsManager = class _BuildResultsManager {
1902
2164
  const response = await this.client.modules.builds.getBuildStatisticValues(
1903
2165
  toBuildLocator(buildId)
1904
2166
  );
1905
- const payload = response.data;
2167
+ const payload = this.ensureStatisticsResponse(response.data, buildId);
1906
2168
  const properties = payload.property ?? [];
1907
2169
  const stats = {};
1908
2170
  for (const prop of properties) {
@@ -1934,7 +2196,11 @@ var BuildResultsManager = class _BuildResultsManager {
1934
2196
  }
1935
2197
  return stats;
1936
2198
  } catch (error2) {
1937
- warn("Failed to fetch statistics", { error: error2, buildId });
2199
+ warn("Failed to fetch statistics", {
2200
+ error: error2 instanceof Error ? error2.message : error2,
2201
+ buildId,
2202
+ expected: "property[]"
2203
+ });
1938
2204
  return {};
1939
2205
  }
1940
2206
  }
@@ -1944,7 +2210,7 @@ var BuildResultsManager = class _BuildResultsManager {
1944
2210
  async fetchChanges(buildId) {
1945
2211
  try {
1946
2212
  const response = await this.client.modules.changes.getAllChanges(`build:(id:${buildId})`);
1947
- const changePayload = response.data;
2213
+ const changePayload = this.ensureChangesResponse(response.data, buildId);
1948
2214
  const changes = changePayload.change ?? [];
1949
2215
  return changes.map((change) => ({
1950
2216
  revision: change.version,
@@ -1957,7 +2223,11 @@ var BuildResultsManager = class _BuildResultsManager {
1957
2223
  }))
1958
2224
  }));
1959
2225
  } catch (error2) {
1960
- warn("Failed to fetch changes", { error: error2, buildId });
2226
+ warn("Failed to fetch changes", {
2227
+ error: error2 instanceof Error ? error2.message : error2,
2228
+ buildId,
2229
+ expected: "change[]"
2230
+ });
1961
2231
  return [];
1962
2232
  }
1963
2233
  }
@@ -1970,16 +2240,20 @@ var BuildResultsManager = class _BuildResultsManager {
1970
2240
  `snapshotDependency:(to:(id:${buildId}))`,
1971
2241
  "build(id,number,buildTypeId,status)"
1972
2242
  );
1973
- const depsData = response.data;
2243
+ const depsData = this.ensureDependenciesResponse(response.data, buildId);
1974
2244
  const builds = depsData.build ?? [];
1975
2245
  return builds.map((build) => ({
1976
- buildId: build.id,
2246
+ buildId: typeof build.id === "string" ? Number.parseInt(build.id, 10) : build.id,
1977
2247
  buildNumber: build.number,
1978
2248
  buildTypeId: build.buildTypeId,
1979
2249
  status: build.status
1980
2250
  }));
1981
2251
  } catch (error2) {
1982
- warn("Failed to fetch dependencies", { error: error2, buildId });
2252
+ warn("Failed to fetch dependencies", {
2253
+ error: error2 instanceof Error ? error2.message : error2,
2254
+ buildId,
2255
+ expected: "build[]"
2256
+ });
1983
2257
  return [];
1984
2258
  }
1985
2259
  }
@@ -40126,103 +40400,122 @@ var FULL_MODE_TOOLS = [
40126
40400
  required: ["buildTypeId", "action"]
40127
40401
  },
40128
40402
  handler: async (args) => {
40129
- const typedArgs = args;
40130
- const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
40131
- switch (typedArgs.action) {
40132
- case "add": {
40133
- const stepProps = Object.fromEntries(
40134
- Object.entries(typedArgs.properties ?? {}).map(([k, v]) => [k, String(v)])
40135
- );
40136
- if (typedArgs.type === "simpleRunner" && stepProps["script.content"]) {
40137
- stepProps["use.custom.script"] = stepProps["use.custom.script"] ?? "true";
40138
- }
40139
- const step = {
40140
- name: typedArgs.name,
40141
- type: typedArgs.type,
40142
- properties: {
40143
- property: Object.entries(stepProps).map(([k, v]) => ({ name: k, value: v }))
40144
- }
40145
- };
40146
- await adapter.modules.buildTypes.addBuildStepToBuildType(
40147
- typedArgs.buildTypeId,
40148
- void 0,
40149
- step,
40150
- {
40151
- headers: { "Content-Type": "application/json", Accept: "application/json" }
40152
- }
40153
- );
40154
- return json({
40155
- success: true,
40156
- action: "add_build_step",
40157
- buildTypeId: typedArgs.buildTypeId
40158
- });
40159
- }
40160
- case "update": {
40161
- if (typedArgs.stepId == null || typedArgs.stepId === "") {
40162
- return json({
40163
- success: false,
40164
- action: "update_build_step",
40165
- error: "Step ID is required for update action"
40166
- });
40167
- }
40168
- const updatePayload = {};
40169
- if (typedArgs.name != null) {
40170
- updatePayload["name"] = typedArgs.name;
40171
- }
40172
- if (typedArgs.type != null) {
40173
- updatePayload["type"] = typedArgs.type;
40174
- }
40175
- const rawProps = typedArgs.properties ?? {};
40176
- const stepProps = Object.fromEntries(
40177
- Object.entries(rawProps).map(([k, v]) => [k, String(v)])
40178
- );
40179
- if (stepProps["script.content"]) {
40180
- stepProps["use.custom.script"] = stepProps["use.custom.script"] ?? "true";
40181
- stepProps["script.type"] = stepProps["script.type"] ?? "customScript";
40182
- }
40183
- if (Object.keys(stepProps).length > 0) {
40184
- updatePayload["properties"] = {
40185
- property: Object.entries(stepProps).map(([name, value]) => ({ name, value }))
40186
- };
40187
- }
40188
- if (Object.keys(updatePayload).length === 0) {
40189
- return json({
40190
- success: false,
40191
- action: "update_build_step",
40192
- error: "No update fields provided"
40403
+ const schema = import_zod4.z.object({
40404
+ buildTypeId: import_zod4.z.string().min(1, "buildTypeId is required"),
40405
+ action: import_zod4.z.enum(["add", "update", "delete"]),
40406
+ stepId: import_zod4.z.string().min(1).optional(),
40407
+ name: import_zod4.z.string().optional(),
40408
+ type: import_zod4.z.string().optional(),
40409
+ properties: import_zod4.z.record(import_zod4.z.unknown()).optional()
40410
+ }).superRefine((value, ctx) => {
40411
+ if (value.action === "update" || value.action === "delete") {
40412
+ if (!value.stepId || value.stepId.trim() === "") {
40413
+ ctx.addIssue({
40414
+ code: import_zod4.z.ZodIssueCode.custom,
40415
+ message: "stepId is required for update or delete actions",
40416
+ path: ["stepId"]
40193
40417
  });
40194
40418
  }
40195
- await adapter.modules.buildTypes.replaceBuildStep(
40196
- typedArgs.buildTypeId,
40197
- typedArgs.stepId,
40198
- void 0,
40199
- updatePayload
40200
- );
40201
- return json({
40202
- success: true,
40203
- action: "update_build_step",
40204
- buildTypeId: typedArgs.buildTypeId,
40205
- stepId: typedArgs.stepId
40206
- });
40207
40419
  }
40208
- case "delete":
40209
- if (typedArgs.stepId == null || typedArgs.stepId === "") {
40210
- return json({
40211
- success: false,
40212
- action: "delete_build_step",
40213
- error: "Step ID is required for delete action"
40214
- });
40420
+ });
40421
+ return runTool(
40422
+ "manage_build_steps",
40423
+ schema,
40424
+ async (typedArgs) => {
40425
+ const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
40426
+ switch (typedArgs.action) {
40427
+ case "add": {
40428
+ const stepProps = Object.fromEntries(
40429
+ Object.entries(typedArgs.properties ?? {}).map(([k, v]) => [k, String(v)])
40430
+ );
40431
+ if (typedArgs.type === "simpleRunner" && stepProps["script.content"]) {
40432
+ stepProps["use.custom.script"] = stepProps["use.custom.script"] ?? "true";
40433
+ }
40434
+ const step = {
40435
+ name: typedArgs.name,
40436
+ type: typedArgs.type,
40437
+ properties: {
40438
+ property: Object.entries(stepProps).map(([k, v]) => ({ name: k, value: v }))
40439
+ }
40440
+ };
40441
+ await adapter.modules.buildTypes.addBuildStepToBuildType(
40442
+ typedArgs.buildTypeId,
40443
+ void 0,
40444
+ step,
40445
+ {
40446
+ headers: { "Content-Type": "application/json", Accept: "application/json" }
40447
+ }
40448
+ );
40449
+ return json({
40450
+ success: true,
40451
+ action: "add_build_step",
40452
+ buildTypeId: typedArgs.buildTypeId
40453
+ });
40454
+ }
40455
+ case "update": {
40456
+ const updatePayload = {};
40457
+ if (typedArgs.name != null) {
40458
+ updatePayload["name"] = typedArgs.name;
40459
+ }
40460
+ if (typedArgs.type != null) {
40461
+ updatePayload["type"] = typedArgs.type;
40462
+ }
40463
+ const rawProps = typedArgs.properties ?? {};
40464
+ const stepProps = Object.fromEntries(
40465
+ Object.entries(rawProps).map(([k, v]) => [k, String(v)])
40466
+ );
40467
+ if (stepProps["script.content"]) {
40468
+ stepProps["use.custom.script"] = stepProps["use.custom.script"] ?? "true";
40469
+ stepProps["script.type"] = stepProps["script.type"] ?? "customScript";
40470
+ }
40471
+ if (Object.keys(stepProps).length > 0) {
40472
+ updatePayload["properties"] = {
40473
+ property: Object.entries(stepProps).map(([name, value]) => ({ name, value }))
40474
+ };
40475
+ }
40476
+ if (Object.keys(updatePayload).length === 0) {
40477
+ return json({
40478
+ success: false,
40479
+ action: "update_build_step",
40480
+ error: "No update fields provided"
40481
+ });
40482
+ }
40483
+ await adapter.modules.buildTypes.replaceBuildStep(
40484
+ typedArgs.buildTypeId,
40485
+ typedArgs.stepId,
40486
+ void 0,
40487
+ updatePayload,
40488
+ {
40489
+ headers: {
40490
+ "Content-Type": "application/json",
40491
+ Accept: "application/json"
40492
+ }
40493
+ }
40494
+ );
40495
+ return json({
40496
+ success: true,
40497
+ action: "update_build_step",
40498
+ buildTypeId: typedArgs.buildTypeId,
40499
+ stepId: typedArgs.stepId
40500
+ });
40501
+ }
40502
+ case "delete":
40503
+ await adapter.modules.buildTypes.deleteBuildStep(
40504
+ typedArgs.buildTypeId,
40505
+ typedArgs.stepId
40506
+ );
40507
+ return json({
40508
+ success: true,
40509
+ action: "delete_build_step",
40510
+ buildTypeId: typedArgs.buildTypeId,
40511
+ stepId: typedArgs.stepId
40512
+ });
40513
+ default:
40514
+ return json({ success: false, error: "Invalid action" });
40215
40515
  }
40216
- await adapter.modules.buildTypes.deleteBuildStep(typedArgs.buildTypeId, typedArgs.stepId);
40217
- return json({
40218
- success: true,
40219
- action: "delete_build_step",
40220
- buildTypeId: typedArgs.buildTypeId,
40221
- stepId: typedArgs.stepId
40222
- });
40223
- default:
40224
- return json({ success: false, error: "Invalid action" });
40225
- }
40516
+ },
40517
+ args
40518
+ );
40226
40519
  },
40227
40520
  mode: "full"
40228
40521
  },