@decantr/cli 1.10.0 → 2.1.0

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.
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  collectCheckIssues
3
- } from "./chunk-RSDCWAHD.js";
3
+ } from "./chunk-X2HIXQAY.js";
4
+ import {
5
+ sendProjectHealthCiFailedTelemetry,
6
+ sendProjectHealthPromptTelemetry,
7
+ sendProjectHealthReportTelemetry
8
+ } from "./chunk-ZUUJ24YU.js";
4
9
 
5
10
  // src/commands/health.ts
6
11
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -62,7 +67,7 @@ function normalizeCliPackageSpecifier(version) {
62
67
  const versionToken = value.startsWith("@decantr/cli@") ? value.slice("@decantr/cli@".length) : value;
63
68
  if (!/^[A-Za-z0-9._~^*-]+$/.test(versionToken)) {
64
69
  throw new Error(
65
- "Invalid --cli-version value. Use a package version or dist-tag such as latest, 1.10.0, or next."
70
+ "Invalid --cli-version value. Use a package version or dist-tag such as latest, 2.0.0, or next."
66
71
  );
67
72
  }
68
73
  return `@decantr/cli@${versionToken}`;
@@ -92,17 +97,45 @@ function validateArtifactPath(value, flag) {
92
97
  }
93
98
  return normalized;
94
99
  }
100
+ function validateProjectPath(value) {
101
+ if (value === void 0) return void 0;
102
+ const raw = value.trim();
103
+ if (!raw || raw === ".") return void 0;
104
+ const normalized = raw.replace(/^\.\/+/, "").replace(/\/+$/, "");
105
+ if (!normalized || normalized.startsWith("/") || normalized.startsWith("-") || normalized.includes("..") || normalized.includes("\\") || /\s/.test(normalized) || !/^[A-Za-z0-9._@/-]+$/.test(normalized)) {
106
+ throw new Error(
107
+ "Invalid --project value. Use a relative project path without spaces or parent-directory segments."
108
+ );
109
+ }
110
+ const segments = normalized.split("/");
111
+ if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
112
+ throw new Error(
113
+ "Invalid --project value. Use a relative project path without empty or parent-directory segments."
114
+ );
115
+ }
116
+ return normalized;
117
+ }
118
+ function prefixArtifactPath(projectPath, artifactPath) {
119
+ return projectPath ? `${projectPath}/${artifactPath}` : artifactPath;
120
+ }
95
121
  function renderProjectHealthCiWorkflow(options = {}) {
96
122
  const failOn = normalizeHealthFailOn(options.failOn);
123
+ const projectPath = validateProjectPath(options.projectPath);
124
+ const reportPath = validateArtifactPath(
125
+ options.reportPath || DEFAULT_HEALTH_CI_REPORT_PATH,
126
+ "--report-path"
127
+ );
128
+ const jsonPath = validateArtifactPath(options.jsonPath || DEFAULT_HEALTH_CI_JSON_PATH, "--json-path");
97
129
  const template = loadHealthTemplate("decantr-health.workflow.yml.template");
98
130
  return renderTemplate(template, {
99
131
  CLI_PACKAGE: normalizeCliPackageSpecifier(options.cliVersion),
100
132
  FAIL_ON: failOn,
101
- REPORT_PATH: validateArtifactPath(
102
- options.reportPath || DEFAULT_HEALTH_CI_REPORT_PATH,
103
- "--report-path"
104
- ),
105
- JSON_PATH: validateArtifactPath(options.jsonPath || DEFAULT_HEALTH_CI_JSON_PATH, "--json-path")
133
+ PROJECT_WORKING_DIRECTORY: projectPath ? ` working-directory: ${projectPath}
134
+ ` : "",
135
+ REPORT_PATH: reportPath,
136
+ JSON_PATH: jsonPath,
137
+ REPORT_ARTIFACT_PATH: prefixArtifactPath(projectPath, reportPath),
138
+ JSON_ARTIFACT_PATH: prefixArtifactPath(projectPath, jsonPath)
106
139
  });
107
140
  }
108
141
  function writeProjectHealthCiWorkflow(projectRoot, options = {}) {
@@ -118,12 +151,15 @@ function writeProjectHealthCiWorkflow(projectRoot, options = {}) {
118
151
  }
119
152
  mkdirSync(dirname(workflowPath), { recursive: true });
120
153
  writeFileSync(workflowPath, renderProjectHealthCiWorkflow(options), "utf-8");
121
- return {
154
+ const projectPath = validateProjectPath(options.projectPath);
155
+ const result = {
122
156
  path: workflowRelativePath,
123
157
  created: !alreadyExists,
124
158
  cliPackage: normalizeCliPackageSpecifier(options.cliVersion),
125
159
  failOn: normalizeHealthFailOn(options.failOn)
126
160
  };
161
+ if (projectPath) result.projectPath = projectPath;
162
+ return result;
127
163
  }
128
164
  function collectDeclaredRoutes(essence) {
129
165
  if (!essence || typeof essence !== "object") return [];
@@ -490,6 +526,9 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
490
526
  const action = result.created ? "Created" : "Updated";
491
527
  console.log(`${GREEN}${action} Decantr Project Health workflow:${RESET} ${result.path}`);
492
528
  console.log(`${DIM}CLI package: ${result.cliPackage}${RESET}`);
529
+ if (result.projectPath) {
530
+ console.log(`${DIM}Project: ${result.projectPath}${RESET}`);
531
+ }
493
532
  console.log(`${DIM}CI gate: decantr health --ci --fail-on ${result.failOn}${RESET}`);
494
533
  } catch (e) {
495
534
  console.error(`${RED}${e.message}${RESET}`);
@@ -497,9 +536,22 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
497
536
  }
498
537
  return;
499
538
  }
539
+ const startedAt = Date.now();
500
540
  const report = await createProjectHealthReport(projectRoot);
501
541
  if (options.promptId) {
502
542
  const finding = report.findings.find((entry) => entry.id === options.promptId);
543
+ await sendProjectHealthReportTelemetry({
544
+ ci: options.ci ?? false,
545
+ durationMs: Date.now() - startedAt,
546
+ projectRoot,
547
+ report
548
+ });
549
+ await sendProjectHealthPromptTelemetry({
550
+ ci: options.ci ?? false,
551
+ finding,
552
+ projectRoot,
553
+ report
554
+ });
503
555
  if (!finding) {
504
556
  console.error(`${RED}No health finding found for id: ${options.promptId}${RESET}`);
505
557
  process.exitCode = 1;
@@ -509,6 +561,7 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
509
561
  return;
510
562
  }
511
563
  const format = resolveFormat(options);
564
+ const failOn = options.failOn ?? "error";
512
565
  const payload = format === "json" ? formatProjectHealthJson(report) : format === "markdown" ? formatProjectHealthMarkdown(report) : formatProjectHealthText(report);
513
566
  if (options.output) {
514
567
  writeFileSync(options.output, payload, "utf-8");
@@ -518,7 +571,27 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
518
571
  } else {
519
572
  process.stdout.write(payload);
520
573
  }
521
- if (options.ci && shouldFailHealth(report, options.failOn ?? "error")) {
574
+ await sendProjectHealthReportTelemetry({
575
+ ci: options.ci ?? false,
576
+ durationMs: Date.now() - startedAt,
577
+ failOn,
578
+ format,
579
+ outputWritten: Boolean(options.output),
580
+ projectRoot,
581
+ report
582
+ });
583
+ if (options.ci && shouldFailHealth(report, failOn)) {
584
+ if (failOn !== "none") {
585
+ await sendProjectHealthCiFailedTelemetry({
586
+ ci: true,
587
+ durationMs: Date.now() - startedAt,
588
+ failOn,
589
+ format,
590
+ outputWritten: Boolean(options.output),
591
+ projectRoot,
592
+ report
593
+ });
594
+ }
522
595
  process.exitCode = 1;
523
596
  }
524
597
  }
@@ -552,9 +625,14 @@ function parseHealthArgs(args) {
552
625
  options.initCi.jsonPath = args[++index];
553
626
  } else if (arg.startsWith("--json-path=")) {
554
627
  options.initCi.jsonPath = arg.split("=")[1];
628
+ } else if (arg === "--project" && args[index + 1]) {
629
+ options.initCi.projectPath = args[++index];
630
+ } else if (arg.startsWith("--project=")) {
631
+ options.initCi.projectPath = arg.split("=")[1];
555
632
  }
556
633
  }
557
634
  normalizeHealthFailOn(options.initCi.failOn);
635
+ validateProjectPath(options.initCi.projectPath);
558
636
  return options;
559
637
  }
560
638
  for (let index = 1; index < args.length; index += 1) {