@daghis/teamcity-mcp 1.9.3 → 1.9.4

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.
@@ -0,0 +1,19 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "node",
5
+ "extra-files": [
6
+ {
7
+ "type": "json",
8
+ "path": "server.json",
9
+ "jsonpath": "$.version"
10
+ },
11
+ {
12
+ "type": "json",
13
+ "path": "server.json",
14
+ "jsonpath": "$.packages[0].version"
15
+ }
16
+ ]
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.9.4"
3
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.9.4](https://github.com/Daghis/teamcity-mcp/compare/v1.9.3...v1.9.4) (2025-09-25)
4
+
5
+ ### Bug Fixes
6
+
7
+ * **tools:** merge build step defaults during update (199) ([#203](https://github.com/Daghis/teamcity-mcp/issues/203)) ([43a668f](https://github.com/Daghis/teamcity-mcp/commit/43a668f0bdc3b0f69c6a3bca0e2a47c49f1cd1a7))
8
+
3
9
  ## [1.9.3](https://github.com/Daghis/teamcity-mcp/compare/v1.9.2...v1.9.3) (2025-09-21)
4
10
 
5
11
 
package/dist/index.js CHANGED
@@ -655,6 +655,7 @@ var import_server = require("@modelcontextprotocol/sdk/server/index.js");
655
655
  var import_types = require("@modelcontextprotocol/sdk/types.js");
656
656
 
657
657
  // src/utils/logger/index.ts
658
+ var import_node_fs = require("node:fs");
658
659
  var import_winston = __toESM(require("winston"));
659
660
  var TeamCityLogger = class _TeamCityLogger {
660
661
  winston;
@@ -758,9 +759,8 @@ var TeamCityLogger = class _TeamCityLogger {
758
759
  */
759
760
  ensureLogDirectory(directory) {
760
761
  try {
761
- const fs2 = require("fs");
762
- if (fs2.existsSync(directory) === false) {
763
- fs2.mkdirSync(directory, { recursive: true });
762
+ if ((0, import_node_fs.existsSync)(directory) === false) {
763
+ (0, import_node_fs.mkdirSync)(directory, { recursive: true });
764
764
  }
765
765
  } catch (error2) {
766
766
  this.winston?.warn?.("Failed to create log directory, using current directory", { error: error2 });
@@ -922,7 +922,7 @@ function debug(message, meta) {
922
922
 
923
923
  // src/tools.ts
924
924
  var import_node_crypto = require("node:crypto");
925
- var import_node_fs = require("node:fs");
925
+ var import_node_fs2 = require("node:fs");
926
926
  var import_node_os = require("node:os");
927
927
  var import_node_path = require("node:path");
928
928
  var import_promises = require("node:stream/promises");
@@ -2763,7 +2763,7 @@ function formatError(err, context) {
2763
2763
  }
2764
2764
  if (err instanceof import_zod2.z.ZodError) {
2765
2765
  error("Validation Error", err, {
2766
- errors: err.errors,
2766
+ errors: err.issues,
2767
2767
  ...context
2768
2768
  });
2769
2769
  return {
@@ -2771,7 +2771,7 @@ function formatError(err, context) {
2771
2771
  error: {
2772
2772
  message: "Validation failed",
2773
2773
  code: "VALIDATION_ERROR",
2774
- data: err.errors
2774
+ data: err.issues
2775
2775
  }
2776
2776
  };
2777
2777
  }
@@ -37237,7 +37237,7 @@ var buildRandomFileName = (artifactName) => {
37237
37237
  };
37238
37238
  var sanitizePathSegments = (artifactPath, fallbackName) => {
37239
37239
  const rawSegments = artifactPath?.split("/") ?? [];
37240
- const sanitizedSegments = rawSegments.map((segment) => segment.trim()).filter((segment) => segment && segment !== "." && segment !== "..").map((segment) => segment.replace(/[^a-zA-Z0-9._-]/g, "_"));
37240
+ const sanitizedSegments = rawSegments.map((segment) => segment.trim()).filter((segment) => segment.length > 0 && segment !== "." && segment !== "..").map((segment) => segment.replace(/[^a-zA-Z0-9._-]/g, "_"));
37241
37241
  if (sanitizedSegments.length === 0) {
37242
37242
  const { sanitizedBase } = sanitizeFileName(fallbackName);
37243
37243
  sanitizedSegments.push(sanitizedBase);
@@ -37250,7 +37250,7 @@ var ensureUniquePath = async (candidate) => {
37250
37250
  const probe = async (attempt) => {
37251
37251
  const next = attempt === 0 ? candidate : `${stem}-${attempt}${ext}`;
37252
37252
  try {
37253
- const handle = await import_node_fs.promises.open(next, "wx");
37253
+ const handle = await import_node_fs2.promises.open(next, "wx");
37254
37254
  await handle.close();
37255
37255
  return next;
37256
37256
  } catch (error2) {
@@ -37264,9 +37264,10 @@ var ensureUniquePath = async (candidate) => {
37264
37264
  return probe(0);
37265
37265
  };
37266
37266
  var resolveStreamOutputPath = async (artifact, options) => {
37267
- if (options.explicitOutputPath) {
37268
- const target = options.explicitOutputPath;
37269
- await import_node_fs.promises.mkdir((0, import_node_path.dirname)(target), { recursive: true });
37267
+ const { explicitOutputPath } = options;
37268
+ if (typeof explicitOutputPath === "string" && explicitOutputPath.length > 0) {
37269
+ const target = explicitOutputPath;
37270
+ await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(target), { recursive: true });
37270
37271
  return target;
37271
37272
  }
37272
37273
  if (options.outputDir) {
@@ -37279,17 +37280,17 @@ var resolveStreamOutputPath = async (artifact, options) => {
37279
37280
  if (relativePath.startsWith("..") || (0, import_node_path.isAbsolute)(relativePath)) {
37280
37281
  throw new Error("Resolved artifact path escapes the configured output directory");
37281
37282
  }
37282
- await import_node_fs.promises.mkdir((0, import_node_path.dirname)(candidate), { recursive: true });
37283
+ await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(candidate), { recursive: true });
37283
37284
  return ensureUniquePath(candidate);
37284
37285
  }
37285
37286
  const tempFilePath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), buildRandomFileName(artifact.name));
37286
- await import_node_fs.promises.mkdir((0, import_node_path.dirname)(tempFilePath), { recursive: true });
37287
+ await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(tempFilePath), { recursive: true });
37287
37288
  return tempFilePath;
37288
37289
  };
37289
37290
  var writeArtifactStreamToDisk = async (artifact, stream, options) => {
37290
37291
  const targetPath = await resolveStreamOutputPath(artifact, options);
37291
- await (0, import_promises.pipeline)(stream, (0, import_node_fs.createWriteStream)(targetPath));
37292
- const stats = await import_node_fs.promises.stat(targetPath);
37292
+ await (0, import_promises.pipeline)(stream, (0, import_node_fs2.createWriteStream)(targetPath));
37293
+ const stats = await import_node_fs2.promises.stat(targetPath);
37293
37294
  return { outputPath: targetPath, bytesWritten: stats.size };
37294
37295
  };
37295
37296
  var buildArtifactPayload = async (artifact, encoding, options) => {
@@ -38028,9 +38029,9 @@ var DEV_TOOLS = [
38028
38029
  const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
38029
38030
  const defaultFileName = `build-log-${safeBuildId}-${startLine}-${(0, import_node_crypto.randomUUID)()}.log`;
38030
38031
  const targetPath = typed.outputPath ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), defaultFileName);
38031
- await import_node_fs.promises.mkdir((0, import_node_path.dirname)(targetPath), { recursive: true });
38032
- await (0, import_promises.pipeline)(stream, (0, import_node_fs.createWriteStream)(targetPath));
38033
- const stats = await import_node_fs.promises.stat(targetPath);
38032
+ await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(targetPath), { recursive: true });
38033
+ await (0, import_promises.pipeline)(stream, (0, import_node_fs2.createWriteStream)(targetPath));
38034
+ const stats = await import_node_fs2.promises.stat(targetPath);
38034
38035
  const page = Math.floor(startLine / effectivePageSize) + 1;
38035
38036
  return json({
38036
38037
  encoding: "stream",
@@ -40492,7 +40493,7 @@ var FULL_MODE_TOOLS = [
40492
40493
  stepId: import_zod4.z.string().min(1).optional(),
40493
40494
  name: import_zod4.z.string().optional(),
40494
40495
  type: import_zod4.z.string().optional(),
40495
- properties: import_zod4.z.record(import_zod4.z.unknown()).optional()
40496
+ properties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
40496
40497
  }).superRefine((value, ctx) => {
40497
40498
  if (value.action === "update" || value.action === "delete") {
40498
40499
  if (!value.stepId || value.stepId.trim() === "") {
@@ -40539,24 +40540,56 @@ var FULL_MODE_TOOLS = [
40539
40540
  });
40540
40541
  }
40541
40542
  case "update": {
40543
+ const existingStepResponse = await adapter.modules.buildTypes.getBuildStep(
40544
+ typedArgs.buildTypeId,
40545
+ typedArgs.stepId,
40546
+ "id,name,type,disabled,properties(property(name,value))",
40547
+ {
40548
+ headers: {
40549
+ Accept: "application/json"
40550
+ }
40551
+ }
40552
+ );
40553
+ const existingStep = existingStepResponse.data;
40554
+ const toRecord2 = (collection) => {
40555
+ if (!collection || !Array.isArray(collection.property)) {
40556
+ return {};
40557
+ }
40558
+ const entries = collection.property.filter((item) => {
40559
+ return Boolean(item?.name);
40560
+ }).map((item) => {
40561
+ return [item.name, item.value != null ? String(item.value) : ""];
40562
+ });
40563
+ return Object.fromEntries(entries);
40564
+ };
40565
+ const existingProperties = toRecord2(existingStep?.properties);
40542
40566
  const updatePayload = {};
40543
- if (typedArgs.name != null) {
40544
- updatePayload["name"] = typedArgs.name;
40567
+ const mergedName = typedArgs.name ?? existingStep?.name;
40568
+ if (mergedName != null) {
40569
+ updatePayload["name"] = mergedName;
40545
40570
  }
40546
- if (typedArgs.type != null) {
40547
- updatePayload["type"] = typedArgs.type;
40571
+ const mergedType = typedArgs.type ?? existingStep?.type;
40572
+ if (mergedType != null) {
40573
+ updatePayload["type"] = mergedType;
40574
+ }
40575
+ if (existingStep?.disabled != null) {
40576
+ updatePayload["disabled"] = existingStep.disabled;
40548
40577
  }
40549
40578
  const rawProps = typedArgs.properties ?? {};
40550
- const stepProps = Object.fromEntries(
40579
+ const providedProps = Object.fromEntries(
40551
40580
  Object.entries(rawProps).map(([k, v]) => [k, String(v)])
40552
40581
  );
40553
- if (stepProps["script.content"]) {
40554
- stepProps["use.custom.script"] = stepProps["use.custom.script"] ?? "true";
40555
- stepProps["script.type"] = stepProps["script.type"] ?? "customScript";
40582
+ const mergedProps = {
40583
+ ...existingProperties,
40584
+ ...providedProps
40585
+ };
40586
+ if (mergedProps["script.content"] && mergedType === "simpleRunner") {
40587
+ mergedProps["use.custom.script"] = mergedProps["use.custom.script"] ?? "true";
40588
+ mergedProps["script.type"] = mergedProps["script.type"] ?? "customScript";
40556
40589
  }
40557
- if (Object.keys(stepProps).length > 0) {
40590
+ if (Object.keys(mergedProps).length > 0) {
40558
40591
  updatePayload["properties"] = {
40559
- property: Object.entries(stepProps).map(([name, value]) => ({ name, value }))
40592
+ property: Object.entries(mergedProps).map(([name, value]) => ({ name, value }))
40560
40593
  };
40561
40594
  }
40562
40595
  if (Object.keys(updatePayload).length === 0) {
@@ -40710,7 +40743,9 @@ var FULL_MODE_TOOLS = [
40710
40743
  const queue = await adapter.modules.buildQueue.getAllQueuedBuilds();
40711
40744
  const builds = queue.data?.build ?? [];
40712
40745
  const ids = new Set(typed.buildTypeIds);
40713
- const toCancel = builds.filter((b) => b.buildTypeId && ids.has(b.buildTypeId));
40746
+ const toCancel = builds.filter(
40747
+ (build) => typeof build.buildTypeId === "string" && ids.has(build.buildTypeId)
40748
+ );
40714
40749
  for (const b of toCancel) {
40715
40750
  if (b.id == null) continue;
40716
40751
  await adapter.modules.buildQueue.deleteQueuedBuild(String(b.id));