@ganakailabs/cloudeval-cli 0.28.0 → 0.28.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.
@@ -38,10 +38,10 @@ import {
38
38
  } from "./chunk-USSCB2ZU.js";
39
39
  import {
40
40
  Banner
41
- } from "./chunk-GGHX5LSI.js";
41
+ } from "./chunk-L5ICTZHW.js";
42
42
  import {
43
43
  CLI_VERSION
44
- } from "./chunk-JFJQZGZH.js";
44
+ } from "./chunk-XDMPAWK2.js";
45
45
  import {
46
46
  raisedButtonStyle,
47
47
  terminalTheme
@@ -3,8 +3,8 @@ import {
3
3
  bannerMetaColor,
4
4
  bannerSegmentColor,
5
5
  splitBannerLineSegments
6
- } from "./chunk-GGHX5LSI.js";
7
- import "./chunk-JFJQZGZH.js";
6
+ } from "./chunk-L5ICTZHW.js";
7
+ import "./chunk-XDMPAWK2.js";
8
8
  import "./chunk-ZDKRIOMB.js";
9
9
  export {
10
10
  Banner,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CLI_VERSION
3
- } from "./chunk-JFJQZGZH.js";
3
+ } from "./chunk-XDMPAWK2.js";
4
4
  import {
5
5
  shouldUseColor,
6
6
  terminalTheme
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var CLI_VERSION = "0.28.0";
2
+ var CLI_VERSION = "0.28.1";
3
3
 
4
4
  export {
5
5
  CLI_VERSION
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  } from "./chunk-USSCB2ZU.js";
40
40
  import {
41
41
  CLI_VERSION
42
- } from "./chunk-JFJQZGZH.js";
42
+ } from "./chunk-XDMPAWK2.js";
43
43
 
44
44
  // src/runtime/prepareInk.ts
45
45
  import fs from "fs";
@@ -609,6 +609,7 @@ var cliCommands = [
609
609
  "--wait",
610
610
  "--poll-interval",
611
611
  "--wait-timeout",
612
+ "--progress",
612
613
  "--location",
613
614
  ...outputOptions,
614
615
  ...authOptions,
@@ -3883,12 +3884,13 @@ var registerReviewCommand = (program2, deps) => {
3883
3884
  filesWritten
3884
3885
  });
3885
3886
  if (data.gate.status === "fail") {
3886
- process.exit(1);
3887
+ process.exitCode = 1;
3888
+ return;
3887
3889
  }
3888
- process.exit(0);
3890
+ process.exitCode = 0;
3889
3891
  } catch (error) {
3890
3892
  console.error(error?.message ?? "Review failed");
3891
- process.exit(1);
3893
+ process.exitCode = 1;
3892
3894
  }
3893
3895
  });
3894
3896
  };
@@ -7837,6 +7839,51 @@ var isSuccessfulJobStatus = (value) => ["completed", "succeeded"].includes(norma
7837
7839
  var compactRecord = (value) => Object.fromEntries(
7838
7840
  Object.entries(value).filter(([, item]) => item !== void 0)
7839
7841
  );
7842
+ var basenameFromPath = (value) => {
7843
+ const trimmed = value?.trim();
7844
+ if (!trimmed) {
7845
+ return void 0;
7846
+ }
7847
+ const normalized = trimmed.replace(/\\/g, "/");
7848
+ return normalized.split("/").filter(Boolean).pop() ?? trimmed;
7849
+ };
7850
+ var isBackendTempLocation = (value) => {
7851
+ const trimmed = value?.trim();
7852
+ if (!trimmed) {
7853
+ return false;
7854
+ }
7855
+ const normalized = trimmed.replace(/\\/g, "/").toLowerCase();
7856
+ const basename = basenameFromPath(trimmed) ?? "";
7857
+ return normalized.startsWith("/tmp/") || normalized.startsWith("/private/tmp/") || normalized.includes("/var/folders/") || normalized.includes("/appdata/local/temp/") || normalized.includes("/windows/temp/") || /^tmp[\w.-]*\.json$/i.test(basename);
7858
+ };
7859
+ var displayTemplateLocation = (rawLocation, context) => {
7860
+ const trimmed = rawLocation?.trim();
7861
+ if (!trimmed) {
7862
+ return void 0;
7863
+ }
7864
+ if (isBackendTempLocation(trimmed)) {
7865
+ return basenameFromPath(context?.templatePath);
7866
+ }
7867
+ return trimmed;
7868
+ };
7869
+ var sanitizeTemplateLocationFields = (value, context) => {
7870
+ if (Array.isArray(value)) {
7871
+ return value.map((item) => sanitizeTemplateLocationFields(item, context));
7872
+ }
7873
+ const record = recordValue(value);
7874
+ if (!record) {
7875
+ return value;
7876
+ }
7877
+ return Object.fromEntries(
7878
+ Object.entries(record).flatMap(([key, item]) => {
7879
+ if (["file_path", "filePath", "location"].includes(key) && typeof item === "string") {
7880
+ const publicLocation = displayTemplateLocation(item, context);
7881
+ return publicLocation === void 0 ? [] : [[key, publicLocation]];
7882
+ }
7883
+ return [[key, sanitizeTemplateLocationFields(item, context)]];
7884
+ })
7885
+ );
7886
+ };
7840
7887
  var arrayValue = (value) => Array.isArray(value) ? value : [];
7841
7888
  var firstString = (record, fields) => {
7842
7889
  for (const field of fields) {
@@ -7847,6 +7894,283 @@ var firstString = (record, fields) => {
7847
7894
  }
7848
7895
  return void 0;
7849
7896
  };
7897
+ var publicValidationText = (value) => value?.replace(/\bPSRule(?:\s+for\s+Azure)?\b/gi, "validation rules").replace(/\bARM\s+TTK\b/gi, "template validation");
7898
+ var sanitizeTemplateOperationText = (value) => {
7899
+ if (typeof value === "string") {
7900
+ return publicValidationText(value) ?? value;
7901
+ }
7902
+ if (Array.isArray(value)) {
7903
+ return value.map(sanitizeTemplateOperationText);
7904
+ }
7905
+ const record = recordValue(value);
7906
+ if (record) {
7907
+ return Object.fromEntries(
7908
+ Object.entries(record).map(([key, item]) => [
7909
+ key,
7910
+ sanitizeTemplateOperationText(item)
7911
+ ])
7912
+ );
7913
+ }
7914
+ return value;
7915
+ };
7916
+ var firstNumber = (record, fields) => {
7917
+ for (const field of fields) {
7918
+ const value = record?.[field];
7919
+ if (typeof value === "number" && Number.isFinite(value)) {
7920
+ return value;
7921
+ }
7922
+ if (typeof value === "string" && value.trim()) {
7923
+ const parsed = Number(value);
7924
+ if (Number.isFinite(parsed)) {
7925
+ return parsed;
7926
+ }
7927
+ }
7928
+ }
7929
+ return void 0;
7930
+ };
7931
+ var firstArray2 = (record, fields) => {
7932
+ for (const field of fields) {
7933
+ const value = record?.[field];
7934
+ if (Array.isArray(value)) {
7935
+ return value;
7936
+ }
7937
+ }
7938
+ return [];
7939
+ };
7940
+ var normalizeProgressItem = (value, context) => {
7941
+ const record = recordValue(value) ?? {};
7942
+ const passed = typeof record.passed === "boolean" ? record.passed : void 0;
7943
+ return compactRecord({
7944
+ name: firstString(record, [
7945
+ "name",
7946
+ "test_name",
7947
+ "testName",
7948
+ "rule_name",
7949
+ "ruleName",
7950
+ "item",
7951
+ "current_item",
7952
+ "display_name",
7953
+ "displayName"
7954
+ ]),
7955
+ status: firstString(record, ["status", "outcome", "result"]) ?? (passed === void 0 ? void 0 : passed ? "Pass" : "Fail"),
7956
+ passed,
7957
+ category: firstString(record, ["category", "test_category", "testCategory"]),
7958
+ severity: firstString(record, ["severity", "level"]),
7959
+ message: publicValidationText(firstString(record, ["message", "description"])),
7960
+ recommendation: publicValidationText(
7961
+ firstString(record, ["recommendation", "remediation"])
7962
+ ),
7963
+ location: displayTemplateLocation(
7964
+ firstString(record, ["file_path", "filePath", "location"]),
7965
+ context
7966
+ ),
7967
+ durationMs: firstNumber(record, ["duration_ms", "durationMs"]),
7968
+ documentationUrl: firstString(record, [
7969
+ "documentation_url",
7970
+ "documentationUrl",
7971
+ "help_url",
7972
+ "helpUrl"
7973
+ ])
7974
+ });
7975
+ };
7976
+ var operationFromResult = (result) => {
7977
+ const record = recordValue(result);
7978
+ if (!record) {
7979
+ return void 0;
7980
+ }
7981
+ if ("test_results" in record || "total_tests" in record) {
7982
+ return "template_test";
7983
+ }
7984
+ if ("filtered_results" in record || "results" in record || "summary" in record) {
7985
+ return "template_validate";
7986
+ }
7987
+ return void 0;
7988
+ };
7989
+ var progressEventFromStatus = (input) => {
7990
+ const statusRecord = recordValue(input.status);
7991
+ return compactRecord({
7992
+ phase: input.phase,
7993
+ jobId: input.jobId,
7994
+ operation: firstString(statusRecord, ["operation", "operation_type", "operationType"]),
7995
+ status: firstString(statusRecord, ["status", "state"]),
7996
+ progress: firstNumber(statusRecord, [
7997
+ "progress",
7998
+ "progress_percent",
7999
+ "progressPercent",
8000
+ "percentage",
8001
+ "percent"
8002
+ ]),
8003
+ completed: firstNumber(statusRecord, [
8004
+ "completed_items",
8005
+ "completedItems",
8006
+ "completed_tests",
8007
+ "completedTests",
8008
+ "completed_rules",
8009
+ "completedRules"
8010
+ ]),
8011
+ total: firstNumber(statusRecord, [
8012
+ "total_items",
8013
+ "totalItems",
8014
+ "total_tests",
8015
+ "totalTests",
8016
+ "total_rules",
8017
+ "totalRules"
8018
+ ]),
8019
+ currentItem: firstString(statusRecord, [
8020
+ "current_item",
8021
+ "currentItem",
8022
+ "current_test",
8023
+ "currentTest",
8024
+ "current_rule",
8025
+ "currentRule"
8026
+ ]),
8027
+ message: publicValidationText(
8028
+ firstString(statusRecord, ["message", "detail", "description"])
8029
+ ),
8030
+ items: firstArray2(statusRecord, [
8031
+ "recent_events",
8032
+ "recentEvents",
8033
+ "events",
8034
+ "progress_events",
8035
+ "progressEvents"
8036
+ ]).map((item) => normalizeProgressItem(item, input.context)).filter((item) => item.name || item.message),
8037
+ elapsedMs: input.elapsedMs
8038
+ });
8039
+ };
8040
+ var failedStatus = (status) => ["fail", "failed", "error"].includes(String(status ?? "").trim().toLowerCase());
8041
+ var passedStatus = (status) => ["pass", "passed", "success", "succeeded"].includes(
8042
+ String(status ?? "").trim().toLowerCase()
8043
+ );
8044
+ var progressItemsFromDetails = (details, nameFields, context) => {
8045
+ const targetLocation = (target) => {
8046
+ const name = firstString(target, ["name", "id"]);
8047
+ const type = firstString(target, ["type"]);
8048
+ if (name && type) {
8049
+ return `${name} (${type})`;
8050
+ }
8051
+ return name ?? type;
8052
+ };
8053
+ return details.filter((detail) => failedStatus(firstString(detail, ["status", "outcome", "result"]))).map((detail) => {
8054
+ const evidence = recordValue(detail.evidence);
8055
+ return compactRecord({
8056
+ name: firstString(detail, nameFields),
8057
+ status: firstString(detail, ["status", "outcome", "result"]),
8058
+ category: firstString(detail, ["category", "test_category", "testCategory"]),
8059
+ severity: firstString(detail, ["severity", "level"]),
8060
+ message: publicValidationText(
8061
+ firstString(detail, ["message", "description"]) ?? firstString(evidence, ["description", "synopsis"])
8062
+ ),
8063
+ recommendation: publicValidationText(
8064
+ firstString(detail, ["recommendation", "remediation"]) ?? firstString(evidence, ["recommendation", "remediation"])
8065
+ ),
8066
+ location: displayTemplateLocation(
8067
+ firstString(detail, ["file_path", "filePath", "location"]),
8068
+ context
8069
+ ) ?? targetLocation(recordValue(detail.target)),
8070
+ durationMs: firstNumber(detail, ["duration_ms", "durationMs"]),
8071
+ documentationUrl: firstString(detail, [
8072
+ "documentation_url",
8073
+ "documentationUrl",
8074
+ "help_url",
8075
+ "helpUrl"
8076
+ ]) ?? firstString(evidence, [
8077
+ "documentation_url",
8078
+ "documentationUrl",
8079
+ "help_url",
8080
+ "helpUrl"
8081
+ ])
8082
+ });
8083
+ }).filter((item) => item.name || item.message);
8084
+ };
8085
+ var resultProgressSummary = (result, operation, context) => {
8086
+ const resultRecord = recordValue(result) ?? {};
8087
+ const summary = recordValue(resultRecord.summary) ?? {};
8088
+ const detectedOperation = operationFromResult(result);
8089
+ const resolvedOperation = detectedOperation ?? operation;
8090
+ const operationText = String(resolvedOperation ?? "").toLowerCase();
8091
+ if (detectedOperation === "template_test" || operationText.includes("test")) {
8092
+ const details2 = normalizeTemplateTestDetails(result, context);
8093
+ const passed2 = firstNumber(resultRecord, ["passed_tests", "passedTests"]) ?? details2.filter((detail) => detail.passed === true || passedStatus(firstString(detail, ["status"]))).length;
8094
+ const failed2 = firstNumber(resultRecord, ["failed_tests", "failedTests"]) ?? details2.filter((detail) => detail.passed === false || failedStatus(firstString(detail, ["status"]))).length;
8095
+ const skipped = firstNumber(resultRecord, ["skipped_tests", "skippedTests"]) ?? 0;
8096
+ return {
8097
+ operation: resolvedOperation,
8098
+ progress: 100,
8099
+ message: `Template tests complete: ${passed2} passed, ${failed2} failed, ${skipped} skipped`,
8100
+ items: progressItemsFromDetails(details2, ["test_name", "testName", "name"], context)
8101
+ };
8102
+ }
8103
+ const details = normalizeTemplateValidationDetails(result);
8104
+ const passed = firstNumber(summary, ["passed_rules", "passedRules"]) ?? details.filter((detail) => passedStatus(firstString(detail, ["status"]))).length;
8105
+ const failed = firstNumber(summary, ["failed_rules", "failedRules"]) ?? details.filter((detail) => failedStatus(firstString(detail, ["status"]))).length;
8106
+ const total = firstNumber(summary, ["total_rules", "totalRules"]) ?? details.length;
8107
+ return {
8108
+ operation: resolvedOperation ?? "template_validate",
8109
+ progress: 100,
8110
+ message: `Validation complete: ${passed} passed, ${failed} failed across ${total} checks`,
8111
+ items: progressItemsFromDetails(details, ["rule_id", "rule_name", "ruleName", "name"], context)
8112
+ };
8113
+ };
8114
+ var formatTemplateProgressEvent = (event, command) => {
8115
+ const appendItemDetails = (lines2, item) => {
8116
+ const metadata = [
8117
+ item.category ? `category: ${item.category}` : void 0,
8118
+ item.severity ? `severity: ${item.severity}` : void 0,
8119
+ item.location ? `location: ${item.location}` : void 0,
8120
+ typeof item.durationMs === "number" ? `duration: ${item.durationMs}ms` : void 0
8121
+ ].filter(Boolean);
8122
+ if (metadata.length) {
8123
+ lines2.push(` ${metadata.join(" | ")}`);
8124
+ }
8125
+ if (item.name && item.message) {
8126
+ lines2.push(` message: ${item.message}`);
8127
+ }
8128
+ if (item.recommendation) {
8129
+ lines2.push(` recommendation: ${item.recommendation}`);
8130
+ }
8131
+ if (item.documentationUrl) {
8132
+ lines2.push(` docs: ${item.documentationUrl}`);
8133
+ }
8134
+ };
8135
+ if (event.phase === "submitted") {
8136
+ return [`${command} job ${event.jobId} submitted`];
8137
+ }
8138
+ if (event.phase === "result") {
8139
+ const lines2 = event.message ? [event.message] : [`${command} job ${event.jobId} complete`];
8140
+ for (const item of event.items ?? []) {
8141
+ const status2 = item.status ? `${item.status} ` : "";
8142
+ lines2.push(` - ${status2}${item.name ?? item.message ?? "item"}`);
8143
+ appendItemDetails(lines2, item);
8144
+ }
8145
+ return lines2;
8146
+ }
8147
+ const status = event.status ? event.status.toUpperCase() : "RUNNING";
8148
+ const progress = typeof event.progress === "number" ? ` ${Math.round(event.progress)}%` : "";
8149
+ const completed = typeof event.completed === "number" && typeof event.total === "number" ? ` (${event.completed}/${event.total})` : "";
8150
+ const current = event.currentItem ? ` current: ${event.currentItem}` : "";
8151
+ const message = event.message ? ` ${event.message}` : "";
8152
+ const lines = [
8153
+ `${command} job ${event.jobId}: ${status}${progress}${completed}${current}${message}`
8154
+ ];
8155
+ for (const item of event.items ?? []) {
8156
+ const statusText = item.status ? `${item.status} ` : "";
8157
+ const passedText = item.status || item.passed === void 0 ? "" : item.passed ? "Pass " : "Fail ";
8158
+ lines.push(` - ${statusText}${passedText}${item.name ?? item.message ?? "item"}`);
8159
+ appendItemDetails(lines, item);
8160
+ }
8161
+ return lines;
8162
+ };
8163
+ var templateProgressEventKey = (event) => JSON.stringify({
8164
+ phase: event.phase,
8165
+ operation: event.operation,
8166
+ status: event.status,
8167
+ progress: event.progress,
8168
+ completed: event.completed,
8169
+ total: event.total,
8170
+ currentItem: event.currentItem,
8171
+ message: event.message,
8172
+ items: event.items
8173
+ });
7850
8174
  var unwrapTemplateOperationResult = (value) => {
7851
8175
  const record = recordValue(value);
7852
8176
  if (!record) {
@@ -7858,9 +8182,9 @@ var unwrapTemplateOperationResult = (value) => {
7858
8182
  }
7859
8183
  const nestedResult = recordValue(result.result);
7860
8184
  if (nestedResult) {
7861
- return nestedResult;
8185
+ return sanitizeTemplateOperationText(nestedResult);
7862
8186
  }
7863
- return result;
8187
+ return sanitizeTemplateOperationText(result);
7864
8188
  };
7865
8189
  var resolvedOperationResult = (value) => recordValue(unwrapTemplateOperationResult(value));
7866
8190
  var validationResults = (result) => {
@@ -7892,9 +8216,15 @@ var normalizeTemplateValidationDetails = (result) => validationResults(recordVal
7892
8216
  pillar: firstString(row, ["pillar"]),
7893
8217
  ...Object.keys(target).length ? { target } : {},
7894
8218
  evidence: compactRecord({
7895
- description: firstString(row, ["description", "message"]) ?? firstString(info, ["description"]),
7896
- synopsis: firstString(row, ["synopsis"]) ?? firstString(info, ["synopsis"]),
7897
- recommendation: firstString(row, ["recommendation", "remediation"]),
8219
+ description: publicValidationText(
8220
+ firstString(row, ["description", "message"]) ?? firstString(info, ["description"])
8221
+ ),
8222
+ synopsis: publicValidationText(
8223
+ firstString(row, ["synopsis"]) ?? firstString(info, ["synopsis"])
8224
+ ),
8225
+ recommendation: publicValidationText(
8226
+ firstString(row, ["recommendation", "remediation"])
8227
+ ),
7898
8228
  documentation_url: firstString(row, [
7899
8229
  "documentation_url",
7900
8230
  "documentationUrl",
@@ -7921,7 +8251,7 @@ var withTemplateValidationDetails = (value) => {
7921
8251
  details: normalizeTemplateValidationDetails(result)
7922
8252
  });
7923
8253
  };
7924
- var normalizeTemplateTestDetails = (result) => arrayValue(recordValue(result)?.test_results).map((item) => {
8254
+ var normalizeTemplateTestDetails = (result, context) => arrayValue(recordValue(result)?.test_results).map((item) => {
7925
8255
  const row = recordValue(item) ?? {};
7926
8256
  const passed = typeof row.passed === "boolean" ? row.passed : void 0;
7927
8257
  return compactRecord({
@@ -7931,17 +8261,23 @@ var normalizeTemplateTestDetails = (result) => arrayValue(recordValue(result)?.t
7931
8261
  status: passed === void 0 ? firstString(row, ["status"]) : passed ? "Pass" : "Fail",
7932
8262
  passed,
7933
8263
  severity: firstString(row, ["severity", "level"]),
7934
- message: firstString(row, ["message", "description"]),
7935
- recommendation: firstString(row, ["recommendation", "remediation"]),
8264
+ message: publicValidationText(firstString(row, ["message", "description"])),
8265
+ recommendation: publicValidationText(
8266
+ firstString(row, ["recommendation", "remediation"])
8267
+ ),
7936
8268
  duration_ms: typeof row.duration_ms === "number" ? row.duration_ms : typeof row.durationMs === "number" ? row.durationMs : void 0,
7937
- file_path: firstString(row, ["file_path", "filePath"])
8269
+ file_path: displayTemplateLocation(
8270
+ firstString(row, ["file_path", "filePath"]),
8271
+ context
8272
+ )
7938
8273
  });
7939
8274
  });
7940
- var withTemplateTestDetails = (value) => {
7941
- const result = resolvedOperationResult(value);
7942
- if (!result) {
8275
+ var withTemplateTestDetails = (value, context) => {
8276
+ const rawResult = resolvedOperationResult(value);
8277
+ if (!rawResult) {
7943
8278
  return value;
7944
8279
  }
8280
+ const result = recordValue(sanitizeTemplateLocationFields(rawResult, context)) ?? rawResult;
7945
8281
  const original = recordValue(value);
7946
8282
  const jobFields = original && ("jobId" in original || "status" in original) ? compactRecord({
7947
8283
  submitted: original.submitted,
@@ -7957,7 +8293,7 @@ var withTemplateTestDetails = (value) => {
7957
8293
  failed_tests: result.failed_tests,
7958
8294
  skipped_tests: result.skipped_tests
7959
8295
  }),
7960
- details: normalizeTemplateTestDetails(result)
8296
+ details: normalizeTemplateTestDetails(result, context)
7961
8297
  });
7962
8298
  };
7963
8299
  var getTemplateValidationJobStatus = async (input) => fetchCloudEvalJson({
@@ -7979,10 +8315,34 @@ var waitForTemplateValidationResult = async (input) => {
7979
8315
  }
7980
8316
  const waitTimeoutMs = Math.max(1, input.waitTimeoutMs ?? 6e5);
7981
8317
  const pollIntervalMs = Math.max(500, input.pollIntervalMs ?? 2500);
8318
+ const progressContext = {
8319
+ templatePath: input.templatePath,
8320
+ parametersPath: input.parametersPath
8321
+ };
7982
8322
  const deadline = Date.now() + waitTimeoutMs;
8323
+ const startedAt = Date.now();
8324
+ const elapsedMs = () => Date.now() - startedAt;
8325
+ await input.onProgress?.(
8326
+ progressEventFromStatus({
8327
+ phase: "submitted",
8328
+ jobId,
8329
+ status: input.submitted,
8330
+ elapsedMs: elapsedMs(),
8331
+ context: progressContext
8332
+ })
8333
+ );
7983
8334
  let status;
7984
8335
  for (; ; ) {
7985
8336
  status = await getTemplateValidationJobStatus({ ...input, jobId });
8337
+ await input.onProgress?.(
8338
+ progressEventFromStatus({
8339
+ phase: "status",
8340
+ jobId,
8341
+ status,
8342
+ elapsedMs: elapsedMs(),
8343
+ context: progressContext
8344
+ })
8345
+ );
7986
8346
  if (isTerminalJobStatus3(status)) {
7987
8347
  break;
7988
8348
  }
@@ -7998,13 +8358,32 @@ var waitForTemplateValidationResult = async (input) => {
7998
8358
  `Template validation job ${jobId} ended with status ${normalizedStatus(status) || "unknown"}.`
7999
8359
  );
8000
8360
  }
8361
+ const result = sanitizeTemplateLocationFields(
8362
+ unwrapTemplateOperationResult(
8363
+ await getTemplateValidationJobResult({ ...input, jobId })
8364
+ ),
8365
+ progressContext
8366
+ );
8367
+ const statusEvent = progressEventFromStatus({
8368
+ phase: "status",
8369
+ jobId,
8370
+ status,
8371
+ elapsedMs: elapsedMs(),
8372
+ context: progressContext
8373
+ });
8374
+ await input.onProgress?.(
8375
+ compactRecord({
8376
+ ...statusEvent,
8377
+ phase: "result",
8378
+ ...resultProgressSummary(result, statusEvent.operation, progressContext),
8379
+ elapsedMs: elapsedMs()
8380
+ })
8381
+ );
8001
8382
  return {
8002
8383
  submitted: input.submitted,
8003
8384
  jobId,
8004
8385
  status,
8005
- result: unwrapTemplateOperationResult(
8006
- await getTemplateValidationJobResult({ ...input, jobId })
8007
- )
8386
+ result
8008
8387
  };
8009
8388
  };
8010
8389
  var templateTestRequestBody = async (files, options) => {
@@ -9379,7 +9758,7 @@ var mcpToolDefinitions = [
9379
9758
  },
9380
9759
  wait: {
9381
9760
  type: "boolean",
9382
- description: "Poll an async validation job until results are ready.",
9761
+ description: "Poll an async validation job until results are ready. When the MCP call includes _meta.progressToken, wait progress is emitted as notifications/progress.",
9383
9762
  default: false
9384
9763
  },
9385
9764
  pollIntervalMs: {
@@ -9436,7 +9815,7 @@ var mcpToolDefinitions = [
9436
9815
  verbose: { type: "boolean", default: false },
9437
9816
  wait: {
9438
9817
  type: "boolean",
9439
- description: "Poll an async template test job until results are ready.",
9818
+ description: "Poll an async template test job until results are ready. When the MCP call includes _meta.progressToken, wait progress is emitted as notifications/progress.",
9440
9819
  default: false
9441
9820
  },
9442
9821
  pollIntervalMs: {
@@ -10898,7 +11277,7 @@ var buildToolHandlers = (serverOptions) => {
10898
11277
  });
10899
11278
  return withEnvelope({ command: "projects graph sync-runs", data });
10900
11279
  });
10901
- handlers.set("template_validate", async (args) => {
11280
+ handlers.set("template_validate", async (args, context) => {
10902
11281
  const config = await resolveInvocationConfig(serverOptions, args);
10903
11282
  const auth = await resolveAuth(config, { requireUser: true });
10904
11283
  const templatePath = stringValue(args.templatePath);
@@ -10931,14 +11310,17 @@ var buildToolHandlers = (serverOptions) => {
10931
11310
  userId: auth.user.id,
10932
11311
  submitted,
10933
11312
  pollIntervalMs: numberValue(args.pollIntervalMs),
10934
- waitTimeoutMs: numberValue(args.waitTimeoutMs)
11313
+ waitTimeoutMs: numberValue(args.waitTimeoutMs),
11314
+ templatePath,
11315
+ parametersPath: stringValue(args.parametersPath),
11316
+ onProgress: context?.sendProgress ? (event) => context.sendProgress(event, "validate template") : void 0
10935
11317
  }) : submitted;
10936
11318
  return withEnvelope({
10937
11319
  command: "validate template",
10938
11320
  data: booleanValue(args.details) ? withTemplateValidationDetails(data) : data
10939
11321
  });
10940
11322
  });
10941
- handlers.set("template_test", async (args) => {
11323
+ handlers.set("template_test", async (args, context) => {
10942
11324
  const config = await resolveInvocationConfig(serverOptions, args);
10943
11325
  const auth = await resolveAuth(config, { requireUser: true });
10944
11326
  const templatePath = stringValue(args.templatePath);
@@ -10963,11 +11345,17 @@ var buildToolHandlers = (serverOptions) => {
10963
11345
  userId: auth.user.id,
10964
11346
  submitted,
10965
11347
  pollIntervalMs: numberValue(args.pollIntervalMs),
10966
- waitTimeoutMs: numberValue(args.waitTimeoutMs)
11348
+ waitTimeoutMs: numberValue(args.waitTimeoutMs),
11349
+ templatePath,
11350
+ parametersPath: stringValue(args.parametersPath),
11351
+ onProgress: context?.sendProgress ? (event) => context.sendProgress(event, "validate tests") : void 0
10967
11352
  }) : submitted;
10968
11353
  return withEnvelope({
10969
11354
  command: "validate tests",
10970
- data: withTemplateTestDetails(data)
11355
+ data: withTemplateTestDetails(data, {
11356
+ templatePath,
11357
+ parametersPath: stringValue(args.parametersPath)
11358
+ })
10971
11359
  });
10972
11360
  });
10973
11361
  handlers.set("template_parse", async (args) => {
@@ -12158,6 +12546,34 @@ var serveMcpServer = async (options) => {
12158
12546
  transport: outputTransport
12159
12547
  });
12160
12548
  };
12549
+ const progressTokenFromParams = (params) => {
12550
+ const meta = isObject(params?._meta) ? params?._meta : void 0;
12551
+ const token = meta?.progressToken;
12552
+ if (typeof token === "string") {
12553
+ return token;
12554
+ }
12555
+ if (typeof token === "number" && Number.isFinite(token)) {
12556
+ return token;
12557
+ }
12558
+ return void 0;
12559
+ };
12560
+ const sendTemplateProgress = (progressToken, event, command) => {
12561
+ if (progressToken === void 0) {
12562
+ return;
12563
+ }
12564
+ const progress = typeof event.progress === "number" ? event.progress : typeof event.completed === "number" ? event.completed : event.phase === "result" ? 100 : 0;
12565
+ const total = typeof event.progress === "number" ? 100 : typeof event.total === "number" ? event.total : event.phase === "result" ? 100 : void 0;
12566
+ send({
12567
+ jsonrpc: "2.0",
12568
+ method: "notifications/progress",
12569
+ params: {
12570
+ progressToken,
12571
+ progress,
12572
+ ...total === void 0 ? {} : { total },
12573
+ message: formatTemplateProgressEvent(event, command).join("\n")
12574
+ }
12575
+ });
12576
+ };
12161
12577
  const handleRequest = async (request) => {
12162
12578
  debug("request received", {
12163
12579
  id: request.id,
@@ -12241,9 +12657,23 @@ var serveMcpServer = async (options) => {
12241
12657
  `Tool has no handler: ${name}`
12242
12658
  );
12243
12659
  }
12660
+ const progressToken = progressTokenFromParams(request.params);
12661
+ let lastProgressKey;
12244
12662
  const startedAt = Date.now();
12245
12663
  try {
12246
- const envelope = await handler(args);
12664
+ const envelope = await handler(args, {
12665
+ progressToken,
12666
+ sendProgress: (event, command) => {
12667
+ if (event.phase === "status") {
12668
+ const key = templateProgressEventKey(event);
12669
+ if (key === lastProgressKey) {
12670
+ return;
12671
+ }
12672
+ lastProgressKey = key;
12673
+ }
12674
+ sendTemplateProgress(progressToken, event, command);
12675
+ }
12676
+ });
12247
12677
  debug("tool call completed", { tool: name, ok: envelope.ok });
12248
12678
  await options.telemetry?.track("cli.mcp.tool", {
12249
12679
  command: "mcp",
@@ -13282,6 +13712,47 @@ var registerAgentsCommand = (program2, deps) => {
13282
13712
  };
13283
13713
 
13284
13714
  // src/validateCommand.ts
13715
+ var normalizeTemplateProgressMode = (value) => {
13716
+ if (value === true) {
13717
+ return "stderr";
13718
+ }
13719
+ if (value === void 0 || value === false || value === null) {
13720
+ return "none";
13721
+ }
13722
+ const mode = String(value).trim().toLowerCase();
13723
+ if (mode === "auto" || mode === "stderr" || mode === "ndjson" || mode === "none") {
13724
+ return mode;
13725
+ }
13726
+ throw new Error("--progress must be one of: auto, stderr, ndjson, none");
13727
+ };
13728
+ var createTemplateProgressReporter = (command, progress) => {
13729
+ const requestedMode = normalizeTemplateProgressMode(progress);
13730
+ const mode = requestedMode === "auto" ? process.stderr.isTTY ? "stderr" : "none" : requestedMode;
13731
+ if (mode === "none") {
13732
+ return void 0;
13733
+ }
13734
+ let lastStatusKey;
13735
+ return (event) => {
13736
+ if (event.phase === "status") {
13737
+ const key = templateProgressEventKey(event);
13738
+ if (key === lastStatusKey) {
13739
+ return;
13740
+ }
13741
+ lastStatusKey = key;
13742
+ }
13743
+ if (mode === "ndjson") {
13744
+ process.stderr.write(
13745
+ `${JSON.stringify({ type: "template_progress", command, ...event })}
13746
+ `
13747
+ );
13748
+ return;
13749
+ }
13750
+ for (const line of formatTemplateProgressEvent(event, command)) {
13751
+ process.stderr.write(`${line}
13752
+ `);
13753
+ }
13754
+ };
13755
+ };
13285
13756
  var addCommon5 = (command, deps) => addAuthOptions(command, deps.defaultBaseUrl).requiredOption("--template-file <path>", "Cloud template JSON file").option("--parameters-file <path>", "Optional parameters JSON file").option("--format <format>", "Output format: text, json, ndjson, markdown", "text").option("--output <file>", "Output file");
13286
13757
  var parsePositiveInteger2 = (value, optionName = "--max-results") => {
13287
13758
  if (!value) {
@@ -13303,7 +13774,11 @@ var registerValidateCommand = (program2, deps) => {
13303
13774
  "--rule <id>",
13304
13775
  "Run a specific validation check id; repeat for multiple checks",
13305
13776
  collectRule
13306
- ).option("--category <name>", "Validation category filter").option("--pillar <name>", "Architecture pillar filter").option("--min-severity <level>", "Minimum severity level").option("--max-results <count>", "Maximum validation results").option("--project <id>", "Project id for saved validation results").option("--save-report", "Persist validation results when a project is provided", false).option("--details", "Include frontend-style per-check evidence details", false).option("--wait", "Poll an async validation job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").action(async (options, command) => {
13777
+ ).option("--category <name>", "Validation category filter").option("--pillar <name>", "Architecture pillar filter").option("--min-severity <level>", "Minimum severity level").option("--max-results <count>", "Maximum validation results").option("--project <id>", "Project id for saved validation results").option("--save-report", "Persist validation results when a project is provided", false).option("--details", "Include frontend-style per-check evidence details", false).option("--wait", "Poll an async validation job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").option(
13778
+ "--progress [mode]",
13779
+ "Progress events while waiting: auto, stderr, ndjson, none",
13780
+ "none"
13781
+ ).action(async (options, command) => {
13307
13782
  try {
13308
13783
  const context = requireAuthUser(await resolveAuthContext(options, command, deps));
13309
13784
  const submitted = await validateTemplate({
@@ -13333,6 +13808,12 @@ var registerValidateCommand = (program2, deps) => {
13333
13808
  waitTimeoutMs: parsePositiveInteger2(
13334
13809
  options.waitTimeout,
13335
13810
  "--wait-timeout"
13811
+ ),
13812
+ templatePath: options.templateFile,
13813
+ parametersPath: options.parametersFile,
13814
+ onProgress: createTemplateProgressReporter(
13815
+ "validate template",
13816
+ options.progress
13336
13817
  )
13337
13818
  }) : submitted;
13338
13819
  const outputData = options.details ? withTemplateValidationDetails(data) : data;
@@ -13370,7 +13851,11 @@ var registerValidateCommand = (program2, deps) => {
13370
13851
  process.exit(1);
13371
13852
  }
13372
13853
  });
13373
- addCommon5(validate.command("tests").description("Run cloud template test checks"), deps).option("--test <name>", "Run a specific template test; repeat for multiple tests", collectRule).option("--skip-test <name>", "Skip a specific template test; repeat for multiple tests", collectRule).option("--category <name>", "Template test category").option("--group <name>", "Template test group; repeat for multiple groups", collectRule).option("--verbose", "Request verbose template test output", false).option("--wait", "Poll an async template test job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").action(async (options, command) => {
13854
+ addCommon5(validate.command("tests").description("Run cloud template test checks"), deps).option("--test <name>", "Run a specific template test; repeat for multiple tests", collectRule).option("--skip-test <name>", "Skip a specific template test; repeat for multiple tests", collectRule).option("--category <name>", "Template test category").option("--group <name>", "Template test group; repeat for multiple groups", collectRule).option("--verbose", "Request verbose template test output", false).option("--wait", "Poll an async template test job until results are ready", false).option("--poll-interval <ms>", "Polling interval when --wait is set", "2500").option("--wait-timeout <ms>", "Maximum time to wait when --wait is set", "600000").option(
13855
+ "--progress [mode]",
13856
+ "Progress events while waiting: auto, stderr, ndjson, none",
13857
+ "none"
13858
+ ).action(async (options, command) => {
13374
13859
  try {
13375
13860
  const context = requireAuthUser(await resolveAuthContext(options, command, deps));
13376
13861
  const submitted = await testTemplate({
@@ -13397,11 +13882,20 @@ var registerValidateCommand = (program2, deps) => {
13397
13882
  waitTimeoutMs: parsePositiveInteger2(
13398
13883
  options.waitTimeout,
13399
13884
  "--wait-timeout"
13885
+ ),
13886
+ templatePath: options.templateFile,
13887
+ parametersPath: options.parametersFile,
13888
+ onProgress: createTemplateProgressReporter(
13889
+ "validate tests",
13890
+ options.progress
13400
13891
  )
13401
13892
  }) : submitted;
13402
13893
  await writeFormattedOutput({
13403
13894
  command: "validate tests",
13404
- data: withTemplateTestDetails(data),
13895
+ data: withTemplateTestDetails(data, {
13896
+ templatePath: options.templateFile,
13897
+ parametersPath: options.parametersFile
13898
+ }),
13405
13899
  format: options.format,
13406
13900
  output: options.output
13407
13901
  });
@@ -15771,7 +16265,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
15771
16265
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15772
16266
  const [{ render }, { App }] = await Promise.all([
15773
16267
  import("ink"),
15774
- import("./App-3RDW53BW.js")
16268
+ import("./App-FRLV34U4.js")
15775
16269
  ]);
15776
16270
  const baseUrl = await resolveBaseUrl(options, command);
15777
16271
  assertSecureBaseUrl(baseUrl);
@@ -15829,7 +16323,7 @@ program.command("chat").description("Start an interactive chat session").option(
15829
16323
  const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
15830
16324
  const [{ render }, { App }] = await Promise.all([
15831
16325
  import("ink"),
15832
- import("./App-3RDW53BW.js")
16326
+ import("./App-FRLV34U4.js")
15833
16327
  ]);
15834
16328
  const baseUrl = await resolveBaseUrl(options, command);
15835
16329
  assertSecureBaseUrl(baseUrl);
@@ -16583,7 +17077,7 @@ Error: ${errorMsg}
16583
17077
  program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
16584
17078
  const { render } = await import("ink");
16585
17079
  const BannerPreview = React.lazy(async () => ({
16586
- default: (await import("./Banner-HGG5NHXF.js")).Banner
17080
+ default: (await import("./Banner-IFLO2NC6.js")).Banner
16587
17081
  }));
16588
17082
  render(
16589
17083
  /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(BannerPreview, { disable: false }) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganakailabs/cloudeval-cli",
3
- "version": "0.28.0",
3
+ "version": "0.28.1",
4
4
  "license": "LicenseRef-CloudEval-CLI",
5
5
  "type": "module",
6
6
  "description": "CloudEval CLI for cloud architecture, cost, report, automation, and MCP workflows.",
package/sbom.spdx.json CHANGED
@@ -14,7 +14,7 @@
14
14
  {
15
15
  "SPDXID": "SPDXRef-Package-CloudEval-CLI",
16
16
  "name": "CloudEval CLI",
17
- "versionInfo": "0.28.0",
17
+ "versionInfo": "0.28.1",
18
18
  "downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
19
19
  "filesAnalyzed": false,
20
20
  "licenseConcluded": "LicenseRef-CloudEval-CLI",